Profile editing now working

Still need to allow profile saving and understanding state changes
throughout
master
James Gao 2014-11-01 20:01:17 -07:00
rodzic fae13053ee
commit f7a8a77edb
5 zmienionych plików z 396 dodań i 77 usunięć

Wyświetl plik

@ -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; } );
}
}
})();

Wyświetl plik

@ -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)

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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]);
}
module.Profile.prototype.delNode = function() {
}
this.update();
}
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 || {}));

Wyświetl plik

@ -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>