Support for negative numbers.

This commit is contained in:
Olly Smith 2012-03-08 20:03:45 +00:00
parent c4e48a275c
commit 4fb43d5d37
4 changed files with 108 additions and 50 deletions

34
examples/negative.html Normal file
View File

@ -0,0 +1,34 @@
<!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>Negative values</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var neg_data = [
{"period": "2011-08-12", "a": 100},
{"period": "2011-03-03", "a": 75},
{"period": "2010-08-08", "a": 50},
{"period": "2010-05-10", "a": 25},
{"period": "2010-03-14", "a": 0},
{"period": "2010-01-10", "a": -25},
{"period": "2009-12-10", "a": -50},
{"period": "2009-10-07", "a": -75},
{"period": "2009-09-25", "a": -100}
];
Morris.Line({
element: 'graph',
data: neg_data,
xkey: 'period',
ykeys: ['a'],
labels: ['Series A']
});
</pre>
</body>

View File

@ -37,6 +37,7 @@ class Morris.Line
'#9440ed' '#9440ed'
] ]
ymax: 'auto' ymax: 'auto'
ymin: 'auto 0'
marginTop: 25 marginTop: 25
marginRight: 25 marginRight: 25
marginBottom: 30 marginBottom: 30
@ -87,12 +88,18 @@ class Morris.Line
# Compute the vertical range of the graph if desired # Compute the vertical range of the graph if desired
if typeof @options.ymax is 'string' and @options.ymax[0..3] is 'auto' if typeof @options.ymax is 'string' and @options.ymax[0..3] is 'auto'
# use Array.concat to flatten arrays and find the max y value # use Array.concat to flatten arrays and find the max y value
ymax = Math.max.apply null, Array.prototype.concat.apply([], @series) ymax = Math.max.apply null, Array.prototype.concat.apply([], @series)
if @options.ymax.length > 5 if @options.ymax.length > 5
@options.ymax = Math.max parseInt(@options.ymax[5..], 10), ymax @options.ymax = Math.max parseInt(@options.ymax[5..], 10), ymax
else else
@options.ymax = ymax @options.ymax = ymax
if typeof @options.ymin is 'string' and @options.ymin[0..3] is 'auto'
ymin = Math.min.apply null, Array.prototype.concat.apply([], @series)
if @options.ymin.length > 5
@options.ymin = Math.min parseInt(@options.ymin[5..], 10), ymin
else
@options.ymin = ymin
# Clear and redraw the graph # Clear and redraw the graph
# #
@ -104,11 +111,14 @@ class Morris.Line
@r = new Raphael(@el[0]) @r = new Raphael(@el[0])
# calculate grid dimensions # calculate grid dimensions
left = @measureText(@options.ymax, @options.gridTextSize).width + @options.marginLeft maxYLabelWidth = Math.max(
@measureText(@options.ymin, @options.gridTextSize).width,
@measureText(@options.ymax, @options.gridTextSize).width)
left = maxYLabelWidth + @options.marginLeft
width = @el.width() - left - @options.marginRight width = @el.width() - left - @options.marginRight
height = @el.height() - @options.marginTop - @options.marginBottom height = @el.height() - @options.marginTop - @options.marginBottom
dx = width / (@xmax - @xmin) dx = width / (@xmax - @xmin)
dy = height / @options.ymax dy = height / (@options.ymax - @options.ymin)
# quick translation helpers # quick translation helpers
transX = (x) => transX = (x) =>
@ -117,13 +127,15 @@ class Morris.Line
else else
left + (x - @xmin) * dx left + (x - @xmin) * dx
transY = (y) => transY = (y) =>
return @options.marginTop + height - y * dy return @options.marginTop + height - (y - @options.ymin) * dy
# draw y axis labels, horizontal lines # draw y axis labels, horizontal lines
lineInterval = height / (@options.numLines - 1) yInterval = (@options.ymax - @options.ymin) / (@options.numLines - 1)
for i in [0..@options.numLines-1] firstY = Math.ceil(@options.ymin / yInterval) * yInterval
y = @options.marginTop + i * lineInterval lastY = Math.floor(@options.ymax / yInterval) * yInterval
v = Math.round((@options.numLines - 1 - i) * @options.ymax / (@options.numLines - 1)) for lineY in [firstY..lastY] by yInterval
v = Math.floor(lineY)
y = transY(v)
@r.text(left - @options.marginLeft/2, y, v) @r.text(left - @options.marginLeft/2, y, v)
.attr('font-size', @options.gridTextSize) .attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor) .attr('fill', @options.gridTextColor)
@ -334,7 +346,7 @@ class Morris.Line
# eg: commas(1234567) -> '1,234,567' # eg: commas(1234567) -> '1,234,567'
# #
commas: (num) -> commas: (num) ->
Math.max(0, num).toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',') num.toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',')
window.Morris = Morris window.Morris = Morris
# vim: set et ts=2 sw=2 sts=2 # vim: set et ts=2 sw=2 sts=2

View File

@ -26,6 +26,7 @@
pointSize: 4, pointSize: 4,
lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'], lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
ymax: 'auto', ymax: 'auto',
ymin: 'auto 0',
marginTop: 25, marginTop: 25,
marginRight: 25, marginRight: 25,
marginBottom: 30, marginBottom: 30,
@ -50,7 +51,7 @@
}; };
Line.prototype.precalc = function() { Line.prototype.precalc = function() {
var ykey, ymax, _i, _j, _len, _ref, _ref2, _results, var ykey, ymax, ymin, _i, _j, _len, _ref, _ref2, _results,
_this = this; _this = this;
this.options.data.sort(function(a, b) { this.options.data.sort(function(a, b) {
return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]); return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]);
@ -87,23 +88,32 @@
if (typeof this.options.ymax === 'string' && this.options.ymax.slice(0, 4) === 'auto') { if (typeof this.options.ymax === 'string' && this.options.ymax.slice(0, 4) === 'auto') {
ymax = Math.max.apply(null, Array.prototype.concat.apply([], this.series)); ymax = Math.max.apply(null, Array.prototype.concat.apply([], this.series));
if (this.options.ymax.length > 5) { if (this.options.ymax.length > 5) {
return this.options.ymax = Math.max(parseInt(this.options.ymax.slice(5), 10), ymax); this.options.ymax = Math.max(parseInt(this.options.ymax.slice(5), 10), ymax);
} else { } else {
return this.options.ymax = ymax; this.options.ymax = ymax;
}
}
if (typeof this.options.ymin === 'string' && this.options.ymin.slice(0, 4) === 'auto') {
ymin = Math.min.apply(null, Array.prototype.concat.apply([], this.series));
if (this.options.ymin.length > 5) {
return this.options.ymin = Math.min(parseInt(this.options.ymin.slice(5), 10), ymin);
} else {
return this.options.ymin = ymin;
} }
} }
}; };
Line.prototype.redraw = function() { Line.prototype.redraw = function() {
var c, circle, columns, coords, dx, dy, height, hideHover, hilight, hover, hoverHeight, hoverMargins, hoverSet, i, label, labelBox, labelText, left, lineInterval, path, pointGrow, pointShrink, prevHilight, prevLabelMargin, s, seriesCoords, seriesPoints, touchHandler, transX, transY, updateHilight, updateHover, v, width, x, xLabel, xLabelMargin, y, yLabel, yLabels, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, var c, circle, columns, coords, dx, dy, firstY, height, hideHover, hilight, hover, hoverHeight, hoverMargins, hoverSet, i, label, labelBox, labelText, lastY, left, lineY, maxYLabelWidth, path, pointGrow, pointShrink, prevHilight, prevLabelMargin, s, seriesCoords, seriesPoints, touchHandler, transX, transY, updateHilight, updateHover, v, width, x, xLabel, xLabelMargin, y, yInterval, yLabel, yLabels, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7,
_this = this; _this = this;
this.el.empty(); this.el.empty();
this.r = new Raphael(this.el[0]); this.r = new Raphael(this.el[0]);
left = this.measureText(this.options.ymax, this.options.gridTextSize).width + this.options.marginLeft; maxYLabelWidth = Math.max(this.measureText(this.options.ymin, this.options.gridTextSize).width, this.measureText(this.options.ymax, this.options.gridTextSize).width);
left = maxYLabelWidth + this.options.marginLeft;
width = this.el.width() - left - this.options.marginRight; width = this.el.width() - left - this.options.marginRight;
height = this.el.height() - this.options.marginTop - this.options.marginBottom; height = this.el.height() - this.options.marginTop - this.options.marginBottom;
dx = width / (this.xmax - this.xmin); dx = width / (this.xmax - this.xmin);
dy = height / this.options.ymax; dy = height / (this.options.ymax - this.options.ymin);
transX = function(x) { transX = function(x) {
if (_this.xvals.length === 1) { if (_this.xvals.length === 1) {
return left + width / 2; return left + width / 2;
@ -112,18 +122,20 @@
} }
}; };
transY = function(y) { transY = function(y) {
return _this.options.marginTop + height - y * dy; return _this.options.marginTop + height - (y - _this.options.ymin) * dy;
}; };
lineInterval = height / (this.options.numLines - 1); yInterval = (this.options.ymax - this.options.ymin) / (this.options.numLines - 1);
for (i = 0, _ref = this.options.numLines - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { firstY = Math.ceil(this.options.ymin / yInterval) * yInterval;
y = this.options.marginTop + i * lineInterval; lastY = Math.floor(this.options.ymax / yInterval) * yInterval;
v = Math.round((this.options.numLines - 1 - i) * this.options.ymax / (this.options.numLines - 1)); for (lineY = firstY; firstY <= lastY ? lineY <= lastY : lineY >= lastY; lineY += yInterval) {
v = Math.floor(lineY);
y = transY(v);
this.r.text(left - this.options.marginLeft / 2, y, v).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end'); this.r.text(left - this.options.marginLeft / 2, y, v).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
this.r.path("M" + left + "," + y + 'H' + (left + width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth); this.r.path("M" + left + "," + y + 'H' + (left + width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth);
} }
prevLabelMargin = null; prevLabelMargin = null;
xLabelMargin = 50; xLabelMargin = 50;
for (i = _ref2 = Math.ceil(this.xmin), _ref3 = Math.floor(this.xmax); _ref2 <= _ref3 ? i <= _ref3 : i >= _ref3; _ref2 <= _ref3 ? i++ : i--) { for (i = _ref = Math.ceil(this.xmin), _ref2 = Math.floor(this.xmax); _ref <= _ref2 ? i <= _ref2 : i >= _ref2; _ref <= _ref2 ? i++ : i--) {
labelText = this.options.parseTime ? i : this.columnLabels[this.columnLabels.length - i - 1]; labelText = this.options.parseTime ? i : this.columnLabels[this.columnLabels.length - i - 1];
label = this.r.text(transX(i), this.options.marginTop + height + this.options.marginBottom / 2, labelText).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor); label = this.r.text(transX(i), this.options.marginTop + height + this.options.marginBottom / 2, labelText).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor);
labelBox = label.getBBox(); labelBox = label.getBBox();
@ -134,19 +146,19 @@
} }
} }
columns = (function() { columns = (function() {
var _i, _len, _ref4, _results; var _i, _len, _ref3, _results;
_ref4 = this.xvals; _ref3 = this.xvals;
_results = []; _results = [];
for (_i = 0, _len = _ref4.length; _i < _len; _i++) { for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
x = _ref4[_i]; x = _ref3[_i];
_results.push(transX(x)); _results.push(transX(x));
} }
return _results; return _results;
}).call(this); }).call(this);
seriesCoords = []; seriesCoords = [];
_ref4 = this.series; _ref3 = this.series;
for (_i = 0, _len = _ref4.length; _i < _len; _i++) { for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
s = _ref4[_i]; s = _ref3[_i];
seriesCoords.push($.map(s, function(y, i) { seriesCoords.push($.map(s, function(y, i) {
return { return {
x: columns[i], x: columns[i],
@ -154,7 +166,7 @@
}; };
})); }));
} }
for (i = _ref5 = seriesCoords.length - 1; _ref5 <= 0 ? i <= 0 : i >= 0; _ref5 <= 0 ? i++ : i--) { for (i = _ref4 = seriesCoords.length - 1; _ref4 <= 0 ? i <= 0 : i >= 0; _ref4 <= 0 ? i++ : i--) {
coords = seriesCoords[i]; coords = seriesCoords[i];
if (coords.length > 1) { if (coords.length > 1) {
path = this.createPath(coords, this.options.marginTop, left, this.options.marginTop + height, left + width); path = this.createPath(coords, this.options.marginTop, left, this.options.marginTop + height, left + width);
@ -162,17 +174,17 @@
} }
} }
seriesPoints = (function() { seriesPoints = (function() {
var _ref6, _results; var _ref5, _results;
_results = []; _results = [];
for (i = 0, _ref6 = seriesCoords.length - 1; 0 <= _ref6 ? i <= _ref6 : i >= _ref6; 0 <= _ref6 ? i++ : i--) { for (i = 0, _ref5 = seriesCoords.length - 1; 0 <= _ref5 ? i <= _ref5 : i >= _ref5; 0 <= _ref5 ? i++ : i--) {
_results.push([]); _results.push([]);
} }
return _results; return _results;
})(); })();
for (i = _ref6 = seriesCoords.length - 1; _ref6 <= 0 ? i <= 0 : i >= 0; _ref6 <= 0 ? i++ : i--) { for (i = _ref5 = seriesCoords.length - 1; _ref5 <= 0 ? i <= 0 : i >= 0; _ref5 <= 0 ? i++ : i--) {
_ref7 = seriesCoords[i]; _ref6 = seriesCoords[i];
for (_j = 0, _len2 = _ref7.length; _j < _len2; _j++) { for (_j = 0, _len2 = _ref6.length; _j < _len2; _j++) {
c = _ref7[_j]; c = _ref6[_j];
circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.options.lineColors[i]).attr('stroke-width', 1).attr('stroke', '#ffffff'); circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.options.lineColors[i]).attr('stroke-width', 1).attr('stroke', '#ffffff');
seriesPoints[i].push(circle); seriesPoints[i].push(circle);
} }
@ -184,16 +196,16 @@
hoverSet.push(hover); hoverSet.push(hover);
hoverSet.push(xLabel); hoverSet.push(xLabel);
yLabels = []; yLabels = [];
for (i = 0, _ref8 = this.series.length - 1; 0 <= _ref8 ? i <= _ref8 : i >= _ref8; 0 <= _ref8 ? i++ : i--) { for (i = 0, _ref7 = this.series.length - 1; 0 <= _ref7 ? i <= _ref7 : i >= _ref7; 0 <= _ref7 ? i++ : i--) {
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - hoverHeight / 2, '').attr('fill', this.options.lineColors[i]).attr('font-size', this.options.hoverFontSize); yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - hoverHeight / 2, '').attr('fill', this.options.lineColors[i]).attr('font-size', this.options.hoverFontSize);
yLabels.push(yLabel); yLabels.push(yLabel);
hoverSet.push(yLabel); hoverSet.push(yLabel);
} }
updateHover = function(index) { updateHover = function(index) {
var i, maxLabelWidth, xloc, yloc, _ref9; var i, maxLabelWidth, xloc, yloc, _ref8;
hoverSet.show(); hoverSet.show();
xLabel.attr('text', _this.columnLabels[index]); xLabel.attr('text', _this.columnLabels[index]);
for (i = 0, _ref9 = _this.series.length - 1; 0 <= _ref9 ? i <= _ref9 : i >= _ref9; 0 <= _ref9 ? i++ : i--) { for (i = 0, _ref8 = _this.series.length - 1; 0 <= _ref8 ? i <= _ref8 : i >= _ref8; 0 <= _ref8 ? i++ : i--) {
yLabels[i].attr('text', "" + _this.seriesLabels[i] + ": " + (_this.commas(_this.series[i][index]))); yLabels[i].attr('text', "" + _this.seriesLabels[i] + ": " + (_this.commas(_this.series[i][index])));
} }
maxLabelWidth = Math.max.apply(null, $.map(yLabels, function(l) { maxLabelWidth = Math.max.apply(null, $.map(yLabels, function(l) {
@ -230,14 +242,14 @@
r: this.options.pointSize r: this.options.pointSize
}, 25, 'linear'); }, 25, 'linear');
hilight = function(index) { hilight = function(index) {
var i, _ref10, _ref9; var i, _ref8, _ref9;
if (prevHilight !== null && prevHilight !== index) { if (prevHilight !== null && prevHilight !== index) {
for (i = 0, _ref9 = seriesPoints.length - 1; 0 <= _ref9 ? i <= _ref9 : i >= _ref9; 0 <= _ref9 ? i++ : i--) { for (i = 0, _ref8 = seriesPoints.length - 1; 0 <= _ref8 ? i <= _ref8 : i >= _ref8; 0 <= _ref8 ? i++ : i--) {
seriesPoints[i][prevHilight].animate(pointShrink); seriesPoints[i][prevHilight].animate(pointShrink);
} }
} }
if (index !== null && prevHilight !== index) { if (index !== null && prevHilight !== index) {
for (i = 0, _ref10 = seriesPoints.length - 1; 0 <= _ref10 ? i <= _ref10 : i >= _ref10; 0 <= _ref10 ? i++ : i--) { for (i = 0, _ref9 = seriesPoints.length - 1; 0 <= _ref9 ? i <= _ref9 : i >= _ref9; 0 <= _ref9 ? i++ : i--) {
seriesPoints[i][index].animate(pointGrow); seriesPoints[i][index].animate(pointGrow);
} }
updateHover(index); updateHover(index);
@ -246,10 +258,10 @@
if (index === null) return hideHover(); if (index === null) return hideHover();
}; };
updateHilight = function(x) { updateHilight = function(x) {
var hoverIndex, _ref9, _results; var hoverIndex, _ref8, _results;
x -= _this.el.offset().left; x -= _this.el.offset().left;
_results = []; _results = [];
for (hoverIndex = _ref9 = hoverMargins.length; _ref9 <= 0 ? hoverIndex <= 0 : hoverIndex >= 0; _ref9 <= 0 ? hoverIndex++ : hoverIndex--) { for (hoverIndex = _ref8 = hoverMargins.length; _ref8 <= 0 ? hoverIndex <= 0 : hoverIndex >= 0; _ref8 <= 0 ? hoverIndex++ : hoverIndex--) {
if (hoverIndex === 0 || hoverMargins[hoverIndex - 1] > x) { if (hoverIndex === 0 || hoverMargins[hoverIndex - 1] > x) {
hilight(hoverIndex); hilight(hoverIndex);
break; break;
@ -362,7 +374,7 @@
}; };
Line.prototype.commas = function(num) { Line.prototype.commas = function(num) {
return Math.max(0, num).toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ','); return num.toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',');
}; };
return Line; return Line;

2
morris.min.js vendored

File diff suppressed because one or more lines are too long