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
@ -87,12 +88,18 @@ class Morris.Line
# Compute the vertical range of the graph if desired
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
ymax = Math.max.apply null, Array.prototype.concat.apply([], @series)
if @options.ymax.length > 5
@options.ymax = Math.max parseInt(@options.ymax[5..], 10), ymax
else
@options.ymax = ymax
# use Array.concat to flatten arrays and find the max y value
ymax = Math.max.apply null, Array.prototype.concat.apply([], @series)
if @options.ymax.length > 5
@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