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'
]
ymax: 'auto'
ymin: 'auto 0'
marginTop: 25
marginRight: 25
marginBottom: 30
@ -93,6 +94,12 @@ class Morris.Line
@options.ymax = Math.max parseInt(@options.ymax[5..], 10), ymax
else
@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
#
@ -104,11 +111,14 @@ class Morris.Line
@r = new Raphael(@el[0])
# 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
height = @el.height() - @options.marginTop - @options.marginBottom
dx = width / (@xmax - @xmin)
dy = height / @options.ymax
dy = height / (@options.ymax - @options.ymin)
# quick translation helpers
transX = (x) =>
@ -117,13 +127,15 @@ class Morris.Line
else
left + (x - @xmin) * dx
transY = (y) =>
return @options.marginTop + height - y * dy
return @options.marginTop + height - (y - @options.ymin) * dy
# draw y axis labels, horizontal lines
lineInterval = height / (@options.numLines - 1)
for i in [0..@options.numLines-1]
y = @options.marginTop + i * lineInterval
v = Math.round((@options.numLines - 1 - i) * @options.ymax / (@options.numLines - 1))
yInterval = (@options.ymax - @options.ymin) / (@options.numLines - 1)
firstY = Math.ceil(@options.ymin / yInterval) * yInterval
lastY = Math.floor(@options.ymax / yInterval) * yInterval
for lineY in [firstY..lastY] by yInterval
v = Math.floor(lineY)
y = transY(v)
@r.text(left - @options.marginLeft/2, y, v)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
@ -334,7 +346,7 @@ class Morris.Line
# eg: commas(1234567) -> '1,234,567'
#
commas: (num) ->
Math.max(0, num).toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',')
num.toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',')
window.Morris = Morris
# vim: set et ts=2 sw=2 sts=2

View File

@ -26,6 +26,7 @@
pointSize: 4,
lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
ymax: 'auto',
ymin: 'auto 0',
marginTop: 25,
marginRight: 25,
marginBottom: 30,
@ -50,7 +51,7 @@
};
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.options.data.sort(function(a, b) {
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') {
ymax = Math.max.apply(null, Array.prototype.concat.apply([], this.series));
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 {
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() {
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.el.empty();
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;
height = this.el.height() - this.options.marginTop - this.options.marginBottom;
dx = width / (this.xmax - this.xmin);
dy = height / this.options.ymax;
dy = height / (this.options.ymax - this.options.ymin);
transX = function(x) {
if (_this.xvals.length === 1) {
return left + width / 2;
@ -112,18 +122,20 @@
}
};
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);
for (i = 0, _ref = this.options.numLines - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
y = this.options.marginTop + i * lineInterval;
v = Math.round((this.options.numLines - 1 - i) * this.options.ymax / (this.options.numLines - 1));
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) {
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.path("M" + left + "," + y + 'H' + (left + width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth);
}
prevLabelMargin = null;
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];
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();
@ -134,19 +146,19 @@
}
}
columns = (function() {
var _i, _len, _ref4, _results;
_ref4 = this.xvals;
var _i, _len, _ref3, _results;
_ref3 = this.xvals;
_results = [];
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
x = _ref4[_i];
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
x = _ref3[_i];
_results.push(transX(x));
}
return _results;
}).call(this);
seriesCoords = [];
_ref4 = this.series;
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
s = _ref4[_i];
_ref3 = this.series;
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
s = _ref3[_i];
seriesCoords.push($.map(s, function(y, i) {
return {
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];
if (coords.length > 1) {
path = this.createPath(coords, this.options.marginTop, left, this.options.marginTop + height, left + width);
@ -162,17 +174,17 @@
}
}
seriesPoints = (function() {
var _ref6, _results;
var _ref5, _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([]);
}
return _results;
})();
for (i = _ref6 = seriesCoords.length - 1; _ref6 <= 0 ? i <= 0 : i >= 0; _ref6 <= 0 ? i++ : i--) {
_ref7 = seriesCoords[i];
for (_j = 0, _len2 = _ref7.length; _j < _len2; _j++) {
c = _ref7[_j];
for (i = _ref5 = seriesCoords.length - 1; _ref5 <= 0 ? i <= 0 : i >= 0; _ref5 <= 0 ? i++ : i--) {
_ref6 = seriesCoords[i];
for (_j = 0, _len2 = _ref6.length; _j < _len2; _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');
seriesPoints[i].push(circle);
}
@ -184,16 +196,16 @@
hoverSet.push(hover);
hoverSet.push(xLabel);
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);
yLabels.push(yLabel);
hoverSet.push(yLabel);
}
updateHover = function(index) {
var i, maxLabelWidth, xloc, yloc, _ref9;
var i, maxLabelWidth, xloc, yloc, _ref8;
hoverSet.show();
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])));
}
maxLabelWidth = Math.max.apply(null, $.map(yLabels, function(l) {
@ -230,14 +242,14 @@
r: this.options.pointSize
}, 25, 'linear');
hilight = function(index) {
var i, _ref10, _ref9;
var i, _ref8, _ref9;
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);
}
}
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);
}
updateHover(index);
@ -246,10 +258,10 @@
if (index === null) return hideHover();
};
updateHilight = function(x) {
var hoverIndex, _ref9, _results;
var hoverIndex, _ref8, _results;
x -= _this.el.offset().left;
_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) {
hilight(hoverIndex);
break;
@ -362,7 +374,7 @@
};
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;

2
morris.min.js vendored

File diff suppressed because one or more lines are too long