Merge branch 'master' into html-hover

Conflicts:
	morris.min.js
This commit is contained in:
Olly Smith 2012-12-03 21:39:03 +00:00
commit cf9d10b9dc
5 changed files with 189 additions and 71 deletions

View File

@ -97,7 +97,7 @@ class Morris.Grid extends Morris.EventEmitter
ret.y = for ykey, idx in @options.ykeys
yval = row[ykey]
yval = parseFloat(yval) if typeof yval is 'string'
yval = null unless typeof yval is 'number'
yval = null if yval? and typeof yval isnt 'number'
if yval?
if @cumulative
total += yval

View File

@ -55,6 +55,7 @@ class Morris.Line extends Morris.Grid
hilightAutoHide: false
xLabels: 'auto'
xLabelFormat: null
continuousLine: true
# Do any size-related calculations
#
@ -72,7 +73,7 @@ class Morris.Line extends Morris.Grid
for row in @data
row._x = @transX(row.x)
row._y = for y in row.y
if y? then @transY(y) else null
if y? then @transY(y) else y
# calculate hilight margins
#
@ -89,9 +90,11 @@ class Morris.Line extends Morris.Grid
generatePaths: ->
@paths = for i in [0...@options.ykeys.length]
smooth = @options.smooth is true or @options.ykeys[i] in @options.smooth
coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt null)
coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt undefined)
coords = (c for c in coords when c.y isnt null) if @options.continuousLine
if coords.length > 1
@createPath coords, smooth
Morris.Line.createPath coords, smooth, @bottom
else
null
@ -161,39 +164,50 @@ class Morris.Line extends Morris.Grid
# create a path for a data series
#
# @private
createPath: (coords, smooth) ->
@createPath: (coords, smooth, bottom) ->
path = ""
if smooth
grads = @gradients coords
for i in [0..coords.length-1]
c = coords[i]
if i is 0
path += "M#{c.x},#{c.y}"
grads = Morris.Line.gradients(coords) if smooth
prevCoord = {y: null}
for coord, i in coords
if coord.y?
if prevCoord.y?
if smooth
g = grads[i]
lg = grads[i - 1]
ix = (coord.x - prevCoord.x) / 4
x1 = prevCoord.x + ix
y1 = Math.max(bottom, prevCoord.y + ix * lg)
x2 = coord.x - ix
y2 = Math.max(bottom, coord.y - ix * g)
path += "C#{x1},#{y1},#{x2},#{y2},#{coord.x},#{coord.y}"
else
path += "L#{coord.x},#{coord.y}"
else
g = grads[i]
lc = coords[i - 1]
lg = grads[i - 1]
ix = (c.x - lc.x) / 4
x1 = lc.x + ix
y1 = Math.min(@bottom, lc.y + ix * lg)
x2 = c.x - ix
y2 = Math.min(@bottom, c.y - ix * g)
path += "C#{x1},#{y1},#{x2},#{y2},#{c.x},#{c.y}"
else
path = "M" + ("#{c.x},#{c.y}" for c in coords).join("L")
if not smooth or grads[i]?
path += "M#{coord.x},#{coord.y}"
prevCoord = coord
return path
# calculate a gradient at each point for a series of points
#
# @private
gradients: (coords) ->
for c, i in coords
if i is 0
(coords[1].y - c.y) / (coords[1].x - c.x)
else if i is (coords.length - 1)
(c.y - coords[i - 1].y) / (c.x - coords[i - 1].x)
@gradients: (coords) ->
grad = (a, b) -> (a.y - b.y) / (a.x - b.x)
for coord, i in coords
if coord.y?
nextCoord = coords[i + 1] or {y: null}
prevCoord = coords[i - 1] or {y: null}
if prevCoord.y? and nextCoord.y?
grad(prevCoord, nextCoord)
else if prevCoord.y?
grad(prevCoord, coord)
else if nextCoord.y?
grad(coord, nextCoord)
else
null
else
(coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x)
null
# @private
hilight: (index) =>
@ -228,4 +242,4 @@ class Morris.Line extends Morris.Grid
else if type is 'point'
@options.pointFillColors[sidx % @options.pointFillColors.length] || @options.lineColors[sidx % @options.lineColors.length]
else
@options.lineColors[sidx % @options.lineColors.length]
@options.lineColors[sidx % @options.lineColors.length]

110
morris.js
View File

@ -324,7 +324,7 @@
if (typeof yval === 'string') {
yval = parseFloat(yval);
}
if (typeof yval !== 'number') {
if ((yval != null) && typeof yval !== 'number') {
yval = null;
}
if (yval != null) {
@ -819,7 +819,8 @@
hilight: true,
hilightAutoHide: false,
xLabels: 'auto',
xLabelFormat: null
xLabelFormat: null,
continuousLine: true
};
Line.prototype.calc = function() {
@ -845,7 +846,7 @@
if (y != null) {
_results1.push(this.transY(y));
} else {
_results1.push(null);
_results1.push(y);
}
}
return _results1;
@ -883,7 +884,7 @@
};
Line.prototype.generatePaths = function() {
var coords, i, r, smooth;
var c, coords, i, r, smooth;
return this.paths = (function() {
var _i, _ref, _ref1, _results;
_results = [];
@ -895,7 +896,7 @@
_results1 = [];
for (_j = 0, _len = _ref2.length; _j < _len; _j++) {
r = _ref2[_j];
if (r._y[i] !== null) {
if (r._y[i] !== void 0) {
_results1.push({
x: r._x,
y: r._y[i]
@ -904,8 +905,21 @@
}
return _results1;
}).call(this);
if (this.options.continuousLine) {
coords = (function() {
var _j, _len, _results1;
_results1 = [];
for (_j = 0, _len = coords.length; _j < _len; _j++) {
c = coords[_j];
if (c.y !== null) {
_results1.push(c);
}
}
return _results1;
})();
}
if (coords.length > 1) {
_results.push(this.createPath(coords, smooth));
_results.push(Morris.Line.createPath(coords, smooth, this.bottom));
} else {
_results.push(null);
}
@ -1002,52 +1016,68 @@
return _results;
};
Line.prototype.createPath = function(coords, smooth) {
var c, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _i, _ref;
Line.createPath = function(coords, smooth, bottom) {
var coord, g, grads, i, ix, lg, path, prevCoord, x1, x2, y1, y2, _i, _len;
path = "";
if (smooth) {
grads = this.gradients(coords);
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;
grads = Morris.Line.gradients(coords);
}
prevCoord = {
y: null
};
for (i = _i = 0, _len = coords.length; _i < _len; i = ++_i) {
coord = coords[i];
if (coord.y != null) {
if (prevCoord.y != null) {
if (smooth) {
g = grads[i];
lg = grads[i - 1];
ix = (coord.x - prevCoord.x) / 4;
x1 = prevCoord.x + ix;
y1 = Math.max(bottom, prevCoord.y + ix * lg);
x2 = coord.x - ix;
y2 = Math.max(bottom, coord.y - ix * g);
path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + coord.x + "," + coord.y;
} else {
path += "L" + coord.x + "," + coord.y;
}
} else {
g = grads[i];
lc = coords[i - 1];
lg = grads[i - 1];
ix = (c.x - lc.x) / 4;
x1 = lc.x + ix;
y1 = Math.min(this.bottom, lc.y + ix * lg);
x2 = c.x - ix;
y2 = Math.min(this.bottom, c.y - ix * g);
path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + c.x + "," + c.y;
if (!smooth || (grads[i] != null)) {
path += "M" + coord.x + "," + coord.y;
}
}
}
} else {
path = "M" + ((function() {
var _j, _len, _results;
_results = [];
for (_j = 0, _len = coords.length; _j < _len; _j++) {
c = coords[_j];
_results.push("" + c.x + "," + c.y);
}
return _results;
})()).join("L");
prevCoord = coord;
}
return path;
};
Line.prototype.gradients = function(coords) {
var c, i, _i, _len, _results;
Line.gradients = function(coords) {
var coord, grad, i, nextCoord, prevCoord, _i, _len, _results;
grad = function(a, b) {
return (a.y - b.y) / (a.x - b.x);
};
_results = [];
for (i = _i = 0, _len = coords.length; _i < _len; i = ++_i) {
c = coords[i];
if (i === 0) {
_results.push((coords[1].y - c.y) / (coords[1].x - c.x));
} else if (i === (coords.length - 1)) {
_results.push((c.y - coords[i - 1].y) / (c.x - coords[i - 1].x));
coord = coords[i];
if (coord.y != null) {
nextCoord = coords[i + 1] || {
y: null
};
prevCoord = coords[i - 1] || {
y: null
};
if ((prevCoord.y != null) && (nextCoord.y != null)) {
_results.push(grad(prevCoord, nextCoord));
} else if (prevCoord.y != null) {
_results.push(grad(prevCoord, coord));
} else if (nextCoord.y != null) {
_results.push(grad(coord, nextCoord));
} else {
_results.push(null);
}
} else {
_results.push((coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x));
_results.push(null);
}
}
return _results;

2
morris.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -66,3 +66,77 @@ describe 'Morris.Line', ->
x = new Date(d)
"#{x.getYear()}/#{x.getMonth()+1}/#{x.getDay()}"
chart.data.map((x) -> x.label).should == ['2012/1/1', '2012/1/2']
describe 'rendering lines', ->
beforeEach ->
@defaults =
element: 'graph'
data: [{x:0, y:1, z:0}, {x:1, y:0, z:1}, {x:2, y:1, z:0}, {x:3, y:0, z:1}, {x:4, y:1, z:0}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
lineColors: ['#abcdef', '#fedcba']
smooth: true
continuousLine: false
shouldHavePath = (regex, color = '#abcdef') ->
# Matches an SVG path element within the rendered chart.
#
# Sneakily uses line colors to differentiate between paths within
# the chart.
$('#graph').find("path[stroke='#{color}']").attr('d').should.match regex
it 'should generate smooth lines when options.smooth is true', ->
Morris.Line @defaults
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/
it 'should generate jagged lines when options.smooth is false', ->
Morris.Line $.extend(@defaults, smooth: false)
shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/
it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array', ->
Morris.Line $.extend(@defaults, smooth: ['y'])
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/, '#abcdef'
shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/, '#fedcba'
it 'should ignore undefined values', ->
@defaults.data[2].y = undefined
Morris.Line @defaults
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){3}/
it 'should ignore null values when options.continuousLine is true', ->
@defaults.data[2].y = null
Morris.Line $.extend(@defaults, continuousLine: true)
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){3}/
it 'should break the line at null values when options.continuousLine is false', ->
@defaults.data[2].y = null
Morris.Line @defaults
shouldHavePath /(M[\d\.]+,[\d\.]+C[\d\.]+(,[\d\.]+){5}){2}/
describe '#createPath', ->
it 'should generate a smooth line', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}]
path = Morris.Line.createPath(testData, true, 0)
path.should.equal 'M0,10C2.5,7.5,7.5,0,10,0C12.5,0,17.5,7.5,20,10'
it 'should generate a jagged line', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}]
path = Morris.Line.createPath(testData, false, 0)
path.should.equal 'M0,10L10,0L20,10'
it 'should prevent paths from descending below the bottom of the chart', ->
testData = [{x: 0, y: 20}, {x: 10, y: 10}, {x: 20, y: 30}]
path = Morris.Line.createPath(testData, true, 10)
path.should.equal 'M0,20C2.5,17.5,7.5,10,10,10C12.5,11.25,17.5,25,20,30'
it 'should break the line at null values', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: null}, {x: 30, y: 10}, {x: 40, y: 0}]
path = Morris.Line.createPath(testData, true, 0)
path.should.equal 'M0,10C2.5,7.5,7.5,2.5,10,0M30,10C32.5,7.5,37.5,2.5,40,0'
it 'should ignore leading and trailing null values', ->
testData = [{x: 0, y: null}, {x: 10, y: 10}, {x: 20, y: 0}, {x: 30, y: 10}, {x: 40, y: null}]
path = Morris.Line.createPath(testData, true, 0)
path.should.equal 'M10,10C12.5,7.5,17.5,0,20,0C22.5,0,27.5,7.5,30,10'