Better support for non-continous data (see #53).

This commit is contained in:
Olly Smith 2012-05-10 07:35:24 +01:00
parent fa8f8c60e5
commit f13e9e838f
4 changed files with 122 additions and 50 deletions

View File

@ -0,0 +1,41 @@
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://raw.github.com/DmitryBaranovskiy/raphael/300aa589f5a0ba7fce667cd62c7cdda0bd5ad904/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="lib/prettify.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
</head>
<body>
<h1>Non-continuous data</h1>
<p>Null or missing series values will be skipped when rendering.</p>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407},
{"period": "2012-09-30", "sorned": 629},
{"period": "2012-09-29", "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": null},
{"period": "2012-09-18", "licensed": 3248},
{"period": "2012-09-17", "sorned": 660},
{"period": "2012-09-16", "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
/* custom label formatting with `xLabelFormat` */
xLabelFormat: function(d) { return (d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear(); },
/* setting `xLabels` is recommended when using xLabelFormat */
xLabels: 'day'
});
</pre>
</body>

View File

@ -82,7 +82,7 @@ class Morris.Line
for ykey in @options.ykeys
series_data = []
for d in @options.data
series_data.push(d[ykey])
series_data.push(d[ykey] or null)
@series.push(series_data)
# translate x labels into nominal dates

126
morris.js
View File

@ -1,3 +1,4 @@
// Generated by CoffeeScript 1.3.1
(function() {
var $, Morris, minutesSpecHelper, secondsSpecHelper,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@ -8,12 +9,21 @@
Morris.Line = (function() {
Line.name = 'Line';
function Line(options) {
this.updateHilight = __bind(this.updateHilight, this);
this.hilight = __bind(this.hilight, this);
this.updateHover = __bind(this.updateHover, this);
this.transY = __bind(this.transY, this);
this.transX = __bind(this.transX, this); if (!(this instanceof Morris.Line)) return new Morris.Line(options);
this.transX = __bind(this.transX, this);
if (!(this instanceof Morris.Line)) {
return new Morris.Line(options);
}
if (typeof options.element === 'string') {
this.el = $(document.getElementById(options.element));
} else {
@ -23,7 +33,9 @@
if (typeof this.options.units === 'string') {
this.options.postUnits = options.units;
}
if (this.options.data === void 0 || this.options.data.length === 0) return;
if (this.options.data === void 0 || this.options.data.length === 0) {
return;
}
this.el.addClass('graph-initialised');
this.precalc();
this.redraw();
@ -66,7 +78,7 @@
};
Line.prototype.precalc = function() {
var d, series_data, touchHandler, ykey, ymax, ymin, _i, _j, _k, _len, _len2, _ref, _ref2, _ref3, _results,
var d, series_data, touchHandler, ykey, ymax, ymin, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _results,
_this = this;
this.options.data.sort(function(a, b) {
return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]);
@ -80,10 +92,10 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
ykey = _ref[_i];
series_data = [];
_ref2 = this.options.data;
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
d = _ref2[_j];
series_data.push(d[ykey]);
_ref1 = this.options.data;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
d = _ref1[_j];
series_data.push(d[ykey] || null);
}
this.series.push(series_data);
}
@ -94,7 +106,7 @@
} else {
this.xvals = (function() {
_results = [];
for (var _k = _ref3 = this.columnLabels.length - 1; _ref3 <= 0 ? _k <= 0 : _k >= 0; _ref3 <= 0 ? _k++ : _k--){ _results.push(_k); }
for (var _k = _ref2 = this.columnLabels.length - 1; _ref2 <= 0 ? _k <= 0 : _k >= 0; _ref2 <= 0 ? _k++ : _k--){ _results.push(_k); }
return _results;
}).apply(this);
}
@ -225,12 +237,12 @@
};
Line.prototype.drawGrid = function() {
var drawLabel, firstY, i, l, labelText, lastY, lineY, prevLabelMargin, v, xLabelMargin, y, yInterval, ypos, _i, _len, _ref, _ref2, _results, _results2,
var drawLabel, firstY, i, l, labelText, lastY, lineY, prevLabelMargin, v, xLabelMargin, y, yInterval, ypos, _i, _j, _k, _len, _ref, _ref1, _results, _results1,
_this = this;
yInterval = (this.options.ymax - this.options.ymin) / (this.options.numLines - 1);
firstY = Math.ceil(this.options.ymin / yInterval) * yInterval;
lastY = Math.floor(this.options.ymax / yInterval) * yInterval;
for (lineY = firstY; firstY <= lastY ? lineY <= lastY : lineY >= lastY; lineY += yInterval) {
for (lineY = _i = firstY; firstY <= lastY ? _i <= lastY : _i >= lastY; lineY = _i += yInterval) {
v = Math.floor(lineY);
y = this.transY(v);
this.r.text(this.left - this.options.marginLeft / 2, y, this.yLabelFormat(v)).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
@ -255,25 +267,25 @@
} else {
_ref = Morris.labelSeries(this.xmin, this.xmax, this.width, this.options.xLabels, this.options.xLabelFormat);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
l = _ref[_i];
for (_j = 0, _len = _ref.length; _j < _len; _j++) {
l = _ref[_j];
_results.push(drawLabel(l[0], l[1]));
}
return _results;
}
} else {
_results2 = [];
for (i = 0, _ref2 = this.columnLabels.length; 0 <= _ref2 ? i <= _ref2 : i >= _ref2; 0 <= _ref2 ? i++ : i--) {
_results1 = [];
for (i = _k = 0, _ref1 = this.columnLabels.length; 0 <= _ref1 ? _k <= _ref1 : _k >= _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
labelText = this.columnLabels[this.columnLabels.length - i - 1];
_results2.push(drawLabel(labelText, i));
_results1.push(drawLabel(labelText, i));
}
return _results2;
return _results1;
}
};
Line.prototype.drawSeries = function() {
var c, circle, coords, i, path, _ref, _ref2, _results;
for (i = _ref = this.seriesCoords.length - 1; _ref <= 0 ? i <= 0 : i >= 0; _ref <= 0 ? i++ : i--) {
var c, circle, coords, i, path, _i, _j, _ref, _ref1, _results;
for (i = _i = _ref = this.seriesCoords.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
coords = this.seriesCoords[i];
if (coords.length > 1) {
path = this.createPath(coords, this.options.marginTop, this.left, this.options.marginTop + this.height, this.left + this.width);
@ -281,43 +293,43 @@
}
}
this.seriesPoints = (function() {
var _ref2, _results;
var _j, _ref1, _results;
_results = [];
for (i = 0, _ref2 = this.seriesCoords.length - 1; 0 <= _ref2 ? i <= _ref2 : i >= _ref2; 0 <= _ref2 ? i++ : i--) {
for (i = _j = 0, _ref1 = this.seriesCoords.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
_results.push([]);
}
return _results;
}).call(this);
_results = [];
for (i = _ref2 = this.seriesCoords.length - 1; _ref2 <= 0 ? i <= 0 : i >= 0; _ref2 <= 0 ? i++ : i--) {
for (i = _j = _ref1 = this.seriesCoords.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) {
_results.push((function() {
var _i, _len, _ref3, _results2;
_ref3 = this.seriesCoords[i];
_results2 = [];
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
c = _ref3[_i];
var _k, _len, _ref2, _results1;
_ref2 = this.seriesCoords[i];
_results1 = [];
for (_k = 0, _len = _ref2.length; _k < _len; _k++) {
c = _ref2[_k];
if (c === null) {
circle = null;
} else {
circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.options.lineColors[i]).attr('stroke-width', 1).attr('stroke', '#ffffff');
}
_results2.push(this.seriesPoints[i].push(circle));
_results1.push(this.seriesPoints[i].push(circle));
}
return _results2;
return _results1;
}).call(this));
}
return _results;
};
Line.prototype.createPath = function(all_coords, top, left, bottom, right) {
var c, coords, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _ref;
var c, coords, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _i, _ref;
path = "";
coords = $.map(all_coords, function(c) {
return c;
});
if (this.options.smooth) {
grads = this.gradients(coords);
for (i = 0, _ref = coords.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
for (i = _i = 0, _ref = coords.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
c = coords[i];
if (i === 0) {
path += "M" + c.x + "," + c.y;
@ -354,7 +366,7 @@
};
Line.prototype.drawHover = function() {
var i, yLabel, _ref, _results;
var i, yLabel, _i, _ref, _results;
this.hoverHeight = this.options.hoverFontSize * 1.5 * (this.series.length + 1);
this.hover = this.r.rect(-10, -this.hoverHeight / 2 - this.options.hoverPaddingY, 20, this.hoverHeight + this.options.hoverPaddingY * 2, 10).attr('fill', this.options.hoverFillColor).attr('stroke', this.options.hoverBorderColor).attr('stroke-width', this.options.hoverBorderWidth).attr('opacity', this.options.hoverOpacity);
this.xLabel = this.r.text(0, (this.options.hoverFontSize * 0.75) - this.hoverHeight / 2, '').attr('fill', this.options.hoverLabelColor).attr('font-weight', 'bold').attr('font-size', this.options.hoverFontSize);
@ -363,7 +375,7 @@
this.hoverSet.push(this.xLabel);
this.yLabels = [];
_results = [];
for (i = 0, _ref = this.series.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - this.hoverHeight / 2, '').attr('fill', this.options.lineColors[i]).attr('font-size', this.options.hoverFontSize);
this.yLabels.push(yLabel);
_results.push(this.hoverSet.push(yLabel));
@ -372,11 +384,11 @@
};
Line.prototype.updateHover = function(index) {
var i, maxLabelWidth, xloc, yloc, _ref,
var i, maxLabelWidth, xloc, yloc, _i, _ref,
_this = this;
this.hoverSet.show();
this.xLabel.attr('text', this.columnLabels[index]);
for (i = 0, _ref = this.series.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
this.yLabels[i].attr('text', "" + this.seriesLabels[i] + ": " + (this.yLabelFormat(this.series[i][index])));
}
maxLabelWidth = Math.max.apply(null, $.map(this.yLabels, function(l) {
@ -405,16 +417,16 @@
};
Line.prototype.hilight = function(index) {
var i, _ref, _ref2;
var i, _i, _j, _ref, _ref1;
if (this.prevHilight !== null && this.prevHilight !== index) {
for (i = 0, _ref = this.seriesPoints.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
for (i = _i = 0, _ref = this.seriesPoints.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
if (this.seriesPoints[i][this.prevHilight]) {
this.seriesPoints[i][this.prevHilight].animate(this.pointShrink);
}
}
}
if (index !== null && this.prevHilight !== index) {
for (i = 0, _ref2 = this.seriesPoints.length - 1; 0 <= _ref2 ? i <= _ref2 : i >= _ref2; 0 <= _ref2 ? i++ : i--) {
for (i = _j = 0, _ref1 = this.seriesPoints.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
if (this.seriesPoints[i][index]) {
this.seriesPoints[i][index].animate(this.pointGrow);
}
@ -422,14 +434,16 @@
this.updateHover(index);
}
this.prevHilight = index;
if (index === null) return this.hideHover();
if (index === null) {
return this.hideHover();
}
};
Line.prototype.updateHilight = function(x) {
var hoverIndex, _ref, _results;
var hoverIndex, _i, _ref, _results;
x -= this.el.offset().left;
_results = [];
for (hoverIndex = _ref = this.hoverMargins.length; _ref <= 0 ? hoverIndex <= 0 : hoverIndex >= 0; _ref <= 0 ? hoverIndex++ : hoverIndex--) {
for (hoverIndex = _i = _ref = this.hoverMargins.length; _ref <= 0 ? _i <= 0 : _i >= 0; hoverIndex = _ref <= 0 ? ++_i : --_i) {
if (hoverIndex === 0 || this.hoverMargins[hoverIndex - 1] > x) {
this.hilight(hoverIndex);
break;
@ -442,7 +456,9 @@
Line.prototype.measureText = function(text, fontSize) {
var ret, tt;
if (fontSize == null) fontSize = 12;
if (fontSize == null) {
fontSize = 12;
}
tt = this.r.text(100, 100, text).attr('font-size', fontSize);
ret = tt.getBBox();
tt.remove();
@ -459,7 +475,9 @@
Morris.parseDate = function(date) {
var isecs, m, msecs, n, o, offsetmins, p, q, r, ret, secs;
if (typeof date === 'number') return date;
if (typeof date === 'number') {
return date;
}
m = date.match(/^(\d+) Q(\d)$/);
n = date.match(/^(\d+)-(\d+)$/);
o = date.match(/^(\d+)-(\d+)-(\d+)$/);
@ -474,7 +492,9 @@
return new Date(parseInt(o[1], 10), parseInt(o[2], 10) - 1, parseInt(o[3], 10)).getTime();
} else if (p) {
ret = new Date(parseInt(p[1], 10), 0, 1);
if (ret.getDay() !== 4) ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7);
if (ret.getDay() !== 4) {
ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7);
}
return ret.getTime() + parseInt(p[2], 10) * 604800000;
} else if (q) {
if (!q[6]) {
@ -483,7 +503,9 @@
offsetmins = 0;
if (q[6] !== 'Z') {
offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10);
if (q[7] === '+') offsetmins = 0 - offsetmins;
if (q[7] === '+') {
offsetmins = 0 - offsetmins;
}
}
return Date.UTC(parseInt(q[1], 10), parseInt(q[2], 10) - 1, parseInt(q[3], 10), parseInt(q[4], 10), parseInt(q[5], 10) + offsetmins);
}
@ -497,7 +519,9 @@
offsetmins = 0;
if (r[8] !== 'Z') {
offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10);
if (r[9] === '+') offsetmins = 0 - offsetmins;
if (r[9] === '+') {
offsetmins = 0 - offsetmins;
}
}
return Date.UTC(parseInt(r[1], 10), parseInt(r[2], 10) - 1, parseInt(r[3], 10), parseInt(r[4], 10), parseInt(r[5], 10) + offsetmins, isecs, msecs);
}
@ -516,7 +540,9 @@
intnum = Math.floor(absnum).toFixed(0);
ret += intnum.replace(/(?=(?:\d{3})+$)(?!^)/g, ',');
strabsnum = absnum.toString();
if (strabsnum.length > intnum.length) ret += strabsnum.slice(intnum.length);
if (strabsnum.length > intnum.length) {
ret += strabsnum.slice(intnum.length);
}
return ret;
}
};
@ -541,7 +567,9 @@
}
}
}
if (spec === void 0) spec = Morris.LABEL_SPECS["second"];
if (spec === void 0) {
spec = Morris.LABEL_SPECS["second"];
}
if (xLabelFormat) {
spec = $.extend({}, spec, {
fmt: xLabelFormat
@ -550,7 +578,9 @@
d = spec.start(d0);
ret = [];
while ((t = d.getTime()) <= dmax) {
if (t >= dmin) ret.push([spec.fmt(d), t]);
if (t >= dmin) {
ret.push([spec.fmt(d), t]);
}
spec.incr(d);
}
return ret;

3
morris.min.js vendored

File diff suppressed because one or more lines are too long