kopia lustrzana https://github.com/jamesgao/kiln_controller
Profile editing now working
Still need to allow profile saving and understanding state changes throughoutmaster
rodzic
fae13053ee
commit
f7a8a77edb
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* juration - a natural language duration parser
|
||||
* https://github.com/domchristie/juration
|
||||
*
|
||||
* Copyright 2011, Dom Christie
|
||||
* Licenced under the MIT licence
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var UNITS = {
|
||||
seconds: {
|
||||
patterns: ['second', 'sec', 's'],
|
||||
value: 1,
|
||||
formats: {
|
||||
'chrono': '',
|
||||
'micro': 's',
|
||||
'short': 'sec',
|
||||
'long': 'second'
|
||||
}
|
||||
},
|
||||
minutes: {
|
||||
patterns: ['minute', 'min', 'm(?!s)'],
|
||||
value: 60,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'm',
|
||||
'short': 'min',
|
||||
'long': 'minute'
|
||||
}
|
||||
},
|
||||
hours: {
|
||||
patterns: ['hour', 'hr', 'h'],
|
||||
value: 3600,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'h',
|
||||
'short': 'hr',
|
||||
'long': 'hour'
|
||||
}
|
||||
},
|
||||
days: {
|
||||
patterns: ['day', 'dy', 'd'],
|
||||
value: 86400,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'd',
|
||||
'short': 'day',
|
||||
'long': 'day'
|
||||
}
|
||||
},
|
||||
weeks: {
|
||||
patterns: ['week', 'wk', 'w'],
|
||||
value: 604800,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'w',
|
||||
'short': 'wk',
|
||||
'long': 'week'
|
||||
}
|
||||
},
|
||||
months: {
|
||||
patterns: ['month', 'mon', 'mo', 'mth'],
|
||||
value: 2628000,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'm',
|
||||
'short': 'mth',
|
||||
'long': 'month'
|
||||
}
|
||||
},
|
||||
years: {
|
||||
patterns: ['year', 'yr', 'y'],
|
||||
value: 31536000,
|
||||
formats: {
|
||||
'chrono': ':',
|
||||
'micro': 'y',
|
||||
'short': 'yr',
|
||||
'long': 'year'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var stringify = function(seconds, options) {
|
||||
|
||||
if(!_isNumeric(seconds)) {
|
||||
throw "juration.stringify(): Unable to stringify a non-numeric value";
|
||||
}
|
||||
|
||||
if((typeof options === 'object' && options.format !== undefined) && (options.format !== 'micro' && options.format !== 'short' && options.format !== 'long' && options.format !== 'chrono')) {
|
||||
throw "juration.stringify(): format cannot be '" + options.format + "', and must be either 'micro', 'short', or 'long'";
|
||||
}
|
||||
|
||||
var defaults = {
|
||||
format: 'short'
|
||||
};
|
||||
|
||||
var opts = _extend(defaults, options);
|
||||
|
||||
var units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'], values = [];
|
||||
var remaining = seconds;
|
||||
for(var i = 0, len = units.length; i < len; i++) {
|
||||
var unit = UNITS[units[i]];
|
||||
values[i] = Math.floor(remaining / unit.value);
|
||||
|
||||
if(opts.format === 'micro' || opts.format === 'chrono') {
|
||||
values[i] += unit.formats[opts.format];
|
||||
}
|
||||
else {
|
||||
values[i] += ' ' + _pluralize(values[i], unit.formats[opts.format]);
|
||||
}
|
||||
remaining = remaining % unit.value;
|
||||
}
|
||||
var output = '';
|
||||
for(i = 0, len = values.length; i < len; i++) {
|
||||
if(values[i].charAt(0) !== "0" && opts.format != 'chrono') {
|
||||
output += values[i] + ' ';
|
||||
}
|
||||
else if (opts.format == 'chrono') {
|
||||
output += _padLeft(values[i]+'', '0', i==values.length-1 ? 2 : 3);
|
||||
}
|
||||
}
|
||||
return output.replace(/\s+$/, '').replace(/^(00:)+/g, '').replace(/^0/, '');
|
||||
};
|
||||
|
||||
var parse = function(string) {
|
||||
|
||||
// returns calculated values separated by spaces
|
||||
for(var unit in UNITS) {
|
||||
for(var i = 0, mLen = UNITS[unit].patterns.length; i < mLen; i++) {
|
||||
var regex = new RegExp("((?:\\d+\\.\\d+)|\\d+)\\s?(" + UNITS[unit].patterns[i] + "s?(?=\\s|\\d|\\b))", 'gi');
|
||||
string = string.replace(regex, function(str, p1, p2) {
|
||||
return " " + (p1 * UNITS[unit].value).toString() + " ";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var sum = 0,
|
||||
numbers = string
|
||||
.replace(/(?!\.)\W+/g, ' ') // replaces non-word chars (excluding '.') with whitespace
|
||||
.replace(/^\s+|\s+$|(?:and|plus|with)\s?/g, '') // trim L/R whitespace, replace known join words with ''
|
||||
.split(' ');
|
||||
|
||||
for(var j = 0, nLen = numbers.length; j < nLen; j++) {
|
||||
if(numbers[j] && isFinite(numbers[j])) {
|
||||
sum += parseFloat(numbers[j]);
|
||||
} else if(!numbers[j]) {
|
||||
throw "juration.parse(): Unable to parse: a falsey value";
|
||||
} else {
|
||||
// throw an exception if it's not a valid word/unit
|
||||
throw "juration.parse(): Unable to parse: " + numbers[j].replace(/^\d+/g, '');
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// _padLeft('5', '0', 2); // 05
|
||||
var _padLeft = function(s, c, n) {
|
||||
if (! s || ! c || s.length >= n) {
|
||||
return s;
|
||||
}
|
||||
|
||||
var max = (n - s.length)/c.length;
|
||||
for (var i = 0; i < max; i++) {
|
||||
s = c + s;
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
var _pluralize = function(count, singular) {
|
||||
return count == 1 ? singular : singular + "s";
|
||||
};
|
||||
|
||||
var _isNumeric = function(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
};
|
||||
|
||||
var _extend = function(obj, extObj) {
|
||||
for (var i in extObj) {
|
||||
if(extObj[i] !== undefined) {
|
||||
obj[i] = extObj[i];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
var juration = {
|
||||
parse: parse,
|
||||
stringify: stringify,
|
||||
humanize: stringify
|
||||
};
|
||||
|
||||
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
|
||||
//loaders that implement the Node module pattern (including browserify)
|
||||
module.exports = juration;
|
||||
} else {
|
||||
// Otherwise expose juration
|
||||
window.juration = juration;
|
||||
|
||||
// Register as a named AMD module
|
||||
if ( typeof define === "function" && define.amd ) {
|
||||
define("juration", [], function () { return juration; } );
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -84,11 +84,11 @@ var tempgraph = (function(module) {
|
|||
|
||||
if (marker !== undefined && marker) {
|
||||
var selector = className.replace(" ", ".");
|
||||
var marker = this.axes.append("g")
|
||||
.selectAll("."+selector+".dot").data(data)
|
||||
.enter().append("circle")
|
||||
var key = data.id === undefined ? undefined : function(d){ return d.id;};
|
||||
var marker = this.axes.append("g").selectAll("."+selector+".dot")
|
||||
.data(data, key).enter().append("circle")
|
||||
.attr("class", className+" dot")
|
||||
.attr("r", 5)
|
||||
.attr("r", 10)
|
||||
.attr("cx", function(d) { return this.x(d.x); }.bind(this))
|
||||
.attr("cy", function(d) { return this.y(d.y); }.bind(this));
|
||||
}
|
||||
|
@ -154,7 +154,21 @@ var tempgraph = (function(module) {
|
|||
this.lines[className].data = data;
|
||||
this.axes.select("path."+className).datum(data)
|
||||
.attr("d", this.lines[className].line);
|
||||
|
||||
var join, selector;
|
||||
if (this.lines[className].marker) {
|
||||
selector = className.replace(" ", ".");
|
||||
join = this.axes.selectAll("."+selector+".dot")
|
||||
.data(data, function(d){ return d.id;});
|
||||
join.enter().append("circle")
|
||||
.attr("class", className+" dot")
|
||||
.attr("r", 10);
|
||||
join.exit().remove();
|
||||
join.attr("cx", function(d) { return this.x(d.x); }.bind(this))
|
||||
.attr("cy", function(d) { return this.y(d.y); }.bind(this));
|
||||
}
|
||||
this.draw();
|
||||
return join;
|
||||
}
|
||||
module.Graph.prototype.xlim = function(min, max) {
|
||||
if (min === undefined)
|
||||
|
|
|
@ -191,6 +191,8 @@ var tempgraph = (function(module) {
|
|||
this.scale = function(temp) { return temp * 9 / 5 + 32; }
|
||||
this.inverse = function(temp) { return (temp - 32) * 5 / 9;}
|
||||
this.print = function(t) { return t+"°F"}
|
||||
} else if (name == "cone") {
|
||||
|
||||
}
|
||||
}
|
||||
module.TempScale.C_to_cone = function(temp) {
|
||||
|
|
|
@ -14,7 +14,31 @@ var tempgraph = (function(module) {
|
|||
|
||||
this.graph = graph;
|
||||
this.scalefunc = scale;
|
||||
this.setupGraph();
|
||||
|
||||
//immediately view range from 10 min before to end time of profile
|
||||
var now = new Date();
|
||||
var rstart = new Date(now.getTime() - 10*60*100);
|
||||
var rend = this.time_finish(now);
|
||||
this.graph.xlim(rstart, rend);
|
||||
|
||||
this.pane = this.graph.pane.insert("rect", ":first-child")
|
||||
.attr("class", "profile-pane")
|
||||
.attr("height", this.graph.height)
|
||||
.attr("clip-path", "url(#pane)")
|
||||
|
||||
this.line = this.graph.plot(this._schedule(), "profile-line", true);
|
||||
|
||||
this.drag = d3.behavior.drag().origin(function(d) {
|
||||
return {x:this.graph.x(d.x), y:this.graph.y(d.y)};
|
||||
}.bind(this)).on("dragstart", function(d) {
|
||||
d3.event.sourceEvent.stopPropagation();
|
||||
this._node = this._findNode(d);
|
||||
}.bind(this)).on("drag", this.dragNode.bind(this));
|
||||
|
||||
this.update();
|
||||
|
||||
//events
|
||||
this._bindUI();
|
||||
}
|
||||
module.Profile.prototype.time_finish = function(now) {
|
||||
if (this.time_start instanceof Date) {
|
||||
|
@ -27,50 +51,6 @@ var tempgraph = (function(module) {
|
|||
this.scalefunc = scale;
|
||||
this.update();
|
||||
}
|
||||
module.Profile.prototype.setupGraph = function() {
|
||||
//immediately view range from 10 min before to end time of profile
|
||||
var now = new Date();
|
||||
var rstart = new Date(now.getTime() - 10*60*100);
|
||||
var rend = this.time_finish(now);
|
||||
this.graph.xlim(rstart, rend);
|
||||
|
||||
this.pane = this.graph.pane.insert("rect", ":first-child")
|
||||
.attr("class", "profile-pane")
|
||||
.attr("height", this.graph.height)
|
||||
|
||||
this.line = this.graph.plot(this._schedule(), "profile-line", true);
|
||||
this.update();
|
||||
|
||||
//events
|
||||
this.drag = d3.behavior.drag().origin(function(d) {
|
||||
return {x:this.graph.x(d.x), y:this.graph.y(d.y)};
|
||||
}.bind(this)).on("dragstart", function(d) {
|
||||
d3.event.sourceEvent.stopPropagation();
|
||||
this._node = this._findNode(d);
|
||||
}.bind(this)).on("drag", this.dragNode.bind(this));
|
||||
this.line.marker.call(this.drag);
|
||||
|
||||
var hide_info = function() {
|
||||
this.hide_timeout = setTimeout(function() { $("#profile-node-info").hide(); }, 250);
|
||||
};
|
||||
this.graph.zoom.on("zoom.profile", this.update.bind(this));
|
||||
this.line.marker.on("mouseover", this.hoverNode.bind(this));
|
||||
this.line.marker.on("mouseout", hide_info.bind(this));
|
||||
$("#profile-node-info").on("mouseout.profile", hide_info.bind(this));
|
||||
$("#profile-node-info").on("mouseover.profile", function() {
|
||||
clearTimeout(this.hide_timeout);
|
||||
}.bind(this));
|
||||
}
|
||||
module.Profile.prototype._schedule = function() {
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
var schedule = [];
|
||||
for (var i = 0; i < this.schedule.length; i++) {
|
||||
var time = new Date(start_time.getTime() + this.schedule[i][0]*1000);
|
||||
var temp = this.scalefunc.scale(this.schedule[i][1]);
|
||||
schedule.push({x:time, y:temp});
|
||||
}
|
||||
return schedule;
|
||||
}
|
||||
module.Profile.prototype.update = function() {
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
var end_time = new Date(start_time.getTime()+this.length*1000);
|
||||
|
@ -78,49 +58,164 @@ var tempgraph = (function(module) {
|
|||
this.pane.attr("width", width)
|
||||
.attr("transform","translate("+this.graph.x(start_time)+",0)");
|
||||
|
||||
this.graph.update("profile-line", this._schedule());
|
||||
var join = this.graph.update("profile-line", this._schedule());
|
||||
join.on("mouseover.profile", this.hoverNode.bind(this))
|
||||
.on("mouseout.profile", this._hideInfo.bind(this))
|
||||
.on("dblclick.profile", this.delNode.bind(this));
|
||||
join.call(this.drag);
|
||||
}
|
||||
module.Profile.prototype.setScale = function(scale) {
|
||||
this.scalefunc = scale;
|
||||
this.update();
|
||||
|
||||
module.Profile.prototype._bindUI = function() {
|
||||
// Info pane events
|
||||
var updateNode = function() {
|
||||
clearTimeout(this.timeout_infoedit);
|
||||
var time = juration.parse($("#profile-node-info input.time").val());
|
||||
var temp = parseFloat($("#profile-node-info input.temp").val());
|
||||
this._updateNode(this._node, time, temp);
|
||||
}.bind(this)
|
||||
|
||||
$("#profile-node-info").on("mouseout.profile", this._hideInfo.bind(this));
|
||||
$("#profile-node-info").on("mouseover.profile", function() {
|
||||
clearTimeout(this.timeout_infohide);
|
||||
}.bind(this));
|
||||
$("#profile-node-info input").on("keypress", function(e) {
|
||||
clearTimeout(this.timeout_infoedit);
|
||||
if (e.keyCode == 13) {
|
||||
updateNode();
|
||||
} else {
|
||||
this.timeout_infoedit = setTimeout(updateNode, 2000);
|
||||
}
|
||||
}.bind(this));
|
||||
$("#profile-node-info input").on("blur", function() {
|
||||
this._focused = false;
|
||||
updateNode();
|
||||
this._hideInfo();
|
||||
}.bind(this));
|
||||
$("#profile-node-info input").on("focus", function() {
|
||||
this._focused = true;
|
||||
}.bind(this));
|
||||
|
||||
//Graph events
|
||||
this.graph.zoom.on("zoom.profile", this.update.bind(this));
|
||||
this.line.marker.on("dblclick", this.delNode.bind(this));
|
||||
this.graph.pane.on("dblclick", this.addNode.bind(this));
|
||||
}
|
||||
module.Profile.prototype._schedule = function() {
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
var schedule = [];
|
||||
for (var i = 0; i < this.schedule.length; i++) {
|
||||
var time = new Date(start_time.getTime() + this.schedule[i][0]*1000);
|
||||
var temp = this.scalefunc.scale(this.schedule[i][1]);
|
||||
schedule.push({id:i, x:time, y:temp});
|
||||
}
|
||||
return schedule;
|
||||
}
|
||||
module.Profile.prototype._hideInfo = function() {
|
||||
this.timeout_infohide = setTimeout(function() {
|
||||
if (!this._focused)
|
||||
$("#profile-node-info").fadeOut(100);
|
||||
}.bind(this), 250);
|
||||
}
|
||||
module.Profile.prototype._findNode = function(d) {
|
||||
var time, temp,
|
||||
start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
for (var i = 0; i < this.schedule.length; i++) {
|
||||
time = new Date((start_time.getTime() + this.schedule[i][0]*1000));
|
||||
temp = this.schedule[i][1];
|
||||
//if time is within 10 seconds and temperature matches exactly
|
||||
if ((time - d.x) < 10000 && d.y == this.scalefunc.scale(temp))
|
||||
return i;
|
||||
return d.id;
|
||||
}
|
||||
module.Profile.prototype._updateNode = function(node, time, temp) {
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
if (!(time instanceof Date)) {
|
||||
//This is probably just a direct offset, no need to compute time
|
||||
this.schedule[node][0] = time;
|
||||
} else if (node == 0) {
|
||||
this.schedule[node][0] = 0;
|
||||
} else {
|
||||
var newtime = (time - start_time.getTime()) / 1000;
|
||||
this.schedule[node][0] = newtime;
|
||||
}
|
||||
//update length only if we're editing the final node
|
||||
if (node == this.schedule.length-1) {
|
||||
this.length = this.schedule[node][0];
|
||||
}
|
||||
this.schedule[node][1] = this.scalefunc.inverse(temp);
|
||||
|
||||
//if we're dragging this node "behind" the previous, push it back as well
|
||||
//except if the previous one is the first node, in which case just set it to zero
|
||||
if (node > 0 && this.schedule[node-1][0] >= newtime) {
|
||||
if (node-1 == 0)
|
||||
this.schedule[node][0] = 0;
|
||||
else
|
||||
this.schedule[node-1][0] = newtime;
|
||||
} else if (node < this.schedule.length-1 && this.schedule[node+1][0] < newtime){
|
||||
this.schedule[node+1][0] = newtime;
|
||||
if (node+1 == this.schedule.length-1)
|
||||
this.length = this.schedule[node+1][0];
|
||||
}
|
||||
this._showInfo(node);
|
||||
this.update();
|
||||
|
||||
//Unlock the save buttons and names
|
||||
|
||||
}
|
||||
module.Profile.prototype._showInfo = function(node) {
|
||||
this._node = node;
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
var time = new Date(this.schedule[node][0]*1000 + start_time.getTime());
|
||||
var temp = this.scalefunc.scale(this.schedule[node][1]);
|
||||
$("#profile-node-info")
|
||||
.css('left', this.graph.x(time)+80)
|
||||
.css('top', this.graph.y(temp)+50)
|
||||
.fadeIn(100);
|
||||
|
||||
$("#profile-node-info div.name").text("Set point "+(node+1));
|
||||
$("#profile-node-info input.temp").val(this.scalefunc.print(Math.round(temp*100)/100));
|
||||
var timestr;
|
||||
try {
|
||||
timestr = juration.stringify(this.schedule[node][0]);
|
||||
} catch (e) {
|
||||
timestr = 0;
|
||||
}
|
||||
$("#profile-node-info input.time").val(timestr);
|
||||
}
|
||||
module.Profile.prototype.addNode = function() {
|
||||
d3.event.stopPropagation();
|
||||
var mouse = d3.mouse(this.graph.pane[0][0]);
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
|
||||
var secs = (this.graph.x.invert(mouse[0]) - start_time) / 1000;
|
||||
|
||||
var start, end;
|
||||
for (var i = 0; i < this.schedule.length-1; i++) {
|
||||
start = this.schedule[i][0];
|
||||
end = this.schedule[i+1][0];
|
||||
if (start < secs && secs < end) {
|
||||
var t2 = this.schedule[i+1][1], t1 = this.schedule[i][1];
|
||||
var frac = (secs - start) / (end - start);
|
||||
var temp = frac * (t2 - t1) + t1;
|
||||
this.schedule.splice(i+1, 0, [secs, temp]);
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
module.Profile.prototype.delNode = function() {
|
||||
|
||||
module.Profile.prototype.delNode = function(d) {
|
||||
d3.event.stopPropagation();
|
||||
var node = this._findNode(d);
|
||||
//ignore attempts to delete the starting and ending nodes
|
||||
if (node != 0 && this.schedule.length > 2) {
|
||||
this.schedule.splice(node, 1);
|
||||
if (node == this.schedule.length) {
|
||||
this.length = this.schedule[node-1][0];
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
module.Profile.prototype.dragNode = function(d) {
|
||||
var time = this.graph.x.invert(d3.event.x);
|
||||
var temp = this.graph.y.invert(d3.event.y);
|
||||
var start_time = this.time_start instanceof Date ? this.time_start : new Date();
|
||||
this.schedule[this._node][0] = (time - start_time) / 1000;
|
||||
this.schedule[this._node][1] = this.scalefunc.inverse(temp);
|
||||
this.update();
|
||||
this._updateNode(d.id, time, temp);
|
||||
}
|
||||
module.Profile.prototype.hoverNode = function(d) {
|
||||
clearTimeout(this.hide_timeout);
|
||||
var node = this._findNode(d);
|
||||
$("#profile-node-info")
|
||||
.css('left', this.graph.x(d.x)+80)
|
||||
.css('top', this.graph.y(d.y)+50)
|
||||
.show();
|
||||
|
||||
$("#profile-node-info div.name").text("Set point "+(node+1));
|
||||
$("#profile-node-info input.temp").val(this.scalefunc.scale(this.schedule[node][1]));
|
||||
$("#profile-node-info input.time");
|
||||
clearTimeout(this.timeout_infohide);
|
||||
this._showInfo(d.id);
|
||||
}
|
||||
|
||||
|
||||
return module;
|
||||
}(tempgraph || {}));
|
||||
|
|
|
@ -130,6 +130,7 @@
|
|||
<script src="js/jquery.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/d3.v3.min.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="js/juration.js"></script>
|
||||
<script type="text/javascript" src="js/temp_graph.js"></script>
|
||||
<script type="text/javascript" src="js/temp_profile.js"></script>
|
||||
<script type="text/javascript" src="js/temp_monitor.js"></script>
|
||||
|
|
Ładowanie…
Reference in New Issue