From 10826cbfc0356ba2e272becd4693c4f6a20befa5 Mon Sep 17 00:00:00 2001 From: Christopher Erin Date: Tue, 13 Nov 2012 17:42:01 -0600 Subject: [PATCH 1/6] discontinuous line in cases where null data values should be represented by no line rather than a line connecting only the coords with data. --- lib/morris.line.coffee | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index 12c4a90..d41c4a4 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -54,6 +54,7 @@ class Morris.Line extends Morris.Grid hideHover: false xLabels: 'auto' xLabelFormat: null + continuousLine: false # Do any size-related calculations # @@ -84,7 +85,8 @@ 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 ) + coords = coords.filter((c)-> c.y != null ) if @options.continuousLine if coords.length > 1 @createPath coords, smooth else @@ -161,10 +163,16 @@ class Morris.Line extends Morris.Grid path = "" if smooth grads = @gradients coords + nextPathType = "M" for i in [0..coords.length-1] c = coords[i] - if i is 0 + if c.y == null + nextPathType = "M" + continue + + if nextPathType == "M" path += "M#{c.x},#{c.y}" + nextPathType = "C" else g = grads[i] lc = coords[i - 1] @@ -183,13 +191,23 @@ class Morris.Line extends Morris.Grid # # @private gradients: (coords) -> + coordA = null + coordB = null for c, i in coords if i is 0 - (coords[1].y - c.y) / (coords[1].x - c.x) + coordA = coords[1] + coordB = c else if i is (coords.length - 1) - (c.y - coords[i - 1].y) / (c.x - coords[i - 1].x) + coordA = c + coordB = coords[i - 1] else - (coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x) + coordA = coords[i + 1] + coordB = coords[i - 1] + + if coordA.y != null and coordB.y != null and coordA.x != null and coordB.x != null + (coordA.y - coordB.y) / (coordA.x - coordB.x) + else + null # draw the hover tooltip # From 370edd2e2e58a7eefeee0bc527ee34abee4445fa Mon Sep 17 00:00:00 2001 From: Olly Smith Date: Thu, 29 Nov 2012 08:18:14 +0000 Subject: [PATCH 2/6] Unit test stubs. --- spec/lib/line/line_spec.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/lib/line/line_spec.coffee b/spec/lib/line/line_spec.coffee index e540da7..2c8e6e6 100644 --- a/spec/lib/line/line_spec.coffee +++ b/spec/lib/line/line_spec.coffee @@ -67,3 +67,15 @@ 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 '#generatePaths', -> + it 'should generate smooth lines when options.smooth is true' + it 'should generate jagged lines when options.smooth is false' + it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array' + it 'should filter undefined values from series' + it 'should filter null values from series only when options.continuousLine is true' + + describe '#createPath', -> + it 'should generate a smooth line' + it 'should generate a jagged line' + it 'should break the line at null values' From ee6cbd8eacd5267de942d086ed74ad969a9fdecf Mon Sep 17 00:00:00 2001 From: Christopher Erin Date: Thu, 29 Nov 2012 12:27:21 -0600 Subject: [PATCH 3/6] replace array.filter with list comprehension --- lib/morris.line.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index d41c4a4..f9a8a25 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -86,7 +86,8 @@ class Morris.Line extends Morris.Grid @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 ) - coords = coords.filter((c)-> c.y != null ) if @options.continuousLine + coords = (c for c in coords when c.y != null) if @options.continuousLine + if coords.length > 1 @createPath coords, smooth else From 6d14aa831d7c555877b7dcc52fb0912c5025c39d Mon Sep 17 00:00:00 2001 From: Christopher Erin Date: Thu, 29 Nov 2012 16:28:27 -0600 Subject: [PATCH 4/6] filled out tests for createPath and generatePaths. refactored code to allow for jagged/broken lines and to pass tests. --- lib/morris.line.coffee | 29 ++++++------ spec/lib/line/line_spec.coffee | 82 ++++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index f9a8a25..ee2d2c3 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -162,19 +162,20 @@ class Morris.Line extends Morris.Grid # @private createPath: (coords, smooth) -> path = "" - if smooth - grads = @gradients coords - nextPathType = "M" - for i in [0..coords.length-1] - c = coords[i] - if c.y == null - nextPathType = "M" - continue + grads = @gradients coords if smooth - if nextPathType == "M" - path += "M#{c.x},#{c.y}" - nextPathType = "C" - else + nextPathType = "M" + for i in [0..coords.length-1] + c = coords[i] + if c.y == null + nextPathType = "M" + continue + + if nextPathType == "M" + path += "M#{c.x},#{c.y}" + nextPathType = "CorL" + else + if smooth g = grads[i] lc = coords[i - 1] lg = grads[i - 1] @@ -184,8 +185,8 @@ class Morris.Line extends Morris.Grid 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") + else + path += "L#{c.x},#{c.y}" return path # calculate a gradient at each point for a series of points diff --git a/spec/lib/line/line_spec.coffee b/spec/lib/line/line_spec.coffee index 2c8e6e6..307701d 100644 --- a/spec/lib/line/line_spec.coffee +++ b/spec/lib/line/line_spec.coffee @@ -69,13 +69,79 @@ describe 'Morris.Line', -> chart.data.map((x) -> x.label).should == ['2012/1/1', '2012/1/2'] describe '#generatePaths', -> - it 'should generate smooth lines when options.smooth is true' - it 'should generate jagged lines when options.smooth is false' - it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array' - it 'should filter undefined values from series' - it 'should filter null values from series only when options.continuousLine is true' + TestDefaults = {} + beforeEach -> + TestDefaults = {element: 'graph', xkey: 'x', ykeys: ['y'], labels: ['dontcare']} + + it 'should generate smooth lines when options.smooth is true', -> + testData = [{x: 1, y: 1}, {x: 3, y: 1 }] + chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: true}) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + + it 'should generate jagged, continuous lines when options.smooth is false and options.continuousLine is true', -> + testData = [{x: 1, y: 1}, {x: 2, y: null }, {x: 3, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData, smooth: false, continuousLine: true}) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'L'] + + it 'should generate jagged, discontinuous lines when options.smooth is false and options.continuousLine is false', -> + testData = [{x: 1, y: 1}, {x: 2, y: null }, {x: 3, y: 1}, {x: 4, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData, smooth: false, continuousLine: false}) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'L'] + + it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array', -> + testData = [{x: 1, a: 1, b: 1}, {x: 3, a: 1, b: 1}] + chart = Morris.Line(TestDefaults extends {data: testData, smooth: ['a'], ykeys: ['a', 'b']}) + pathA = chart.generatePaths()[0] + pathA.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + + pathB = chart.generatePaths()[1] + pathB.match(/[A-Z]/g).should.deep.equal ['M', 'L'] + + #skipping because undefined values are converted to nulls in the setData method morris.grid line#98 + it.skip 'should filter undefined values from series', -> + testData = [{x: 1, y: 1}, {x: 2, y: undefined}, {x: 3, y: 1}] + options = + data: testData + continuousLine: false #doesn't matter for undefined values + + chart = Morris.Line(TestDefaults extends options) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + + it 'should filter null values from series only when options.continuousLine is true', -> + testData = [{x: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: true}) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + + it 'should not filter null values from series when options.continuousLine is false', -> + testData = [{x: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}, {x: 4, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: false}) + path = chart.generatePaths()[0] + path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'C'] describe '#createPath', -> - it 'should generate a smooth line' - it 'should generate a jagged line' - it 'should break the line at null values' + TestDefaults = {} + beforeEach -> + TestDefaults = {element: 'graph', xkey: 'x', ykeys: ['y'], labels: ['dontcare']} + + it 'should generate a smooth line', -> + testData = [{x: 1, y: 1}, {x: 3, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData}) + path = chart.createPath(testData, true) + path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + + it 'should generate a jagged line', -> + testData = [{x: 1, y: 1}, {x: 3, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData}) + path = chart.createPath(testData, false) + path.match(/[A-Z]/g).should.deep.equal ['M', 'L'] + + it 'should break the line at null values', -> + testData = [{x: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}, {x: 4, y: 1}] + chart = Morris.Line(TestDefaults extends {data: testData}) + path = chart.createPath(testData, true) + path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'C'] From b0e99f7ca95265bddb62ee712a2ab87bd0702e36 Mon Sep 17 00:00:00 2001 From: Christopher Erin Date: Thu, 29 Nov 2012 16:34:04 -0600 Subject: [PATCH 5/6] default continuousLine to true for backwards compat --- lib/morris.line.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index ee2d2c3..3d4446e 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -54,7 +54,7 @@ class Morris.Line extends Morris.Grid hideHover: false xLabels: 'auto' xLabelFormat: null - continuousLine: false + continuousLine: true # Do any size-related calculations # From d41bea2e2398d7812111c7ceadd3400c924da805 Mon Sep 17 00:00:00 2001 From: Olly Smith Date: Mon, 3 Dec 2012 08:39:13 +0000 Subject: [PATCH 6/6] Refactor. - Test paths as rendered in SVG. - More exact unit tests for createPath. - Catch some more edge case bugs in createPath. - Refactor createPath to handle null values better. --- lib/morris.grid.coffee | 2 +- lib/morris.line.coffee | 82 +++++++++++------------ morris.js | 110 +++++++++++++++++++------------ morris.min.js | 2 +- spec/lib/line/line_spec.coffee | 116 ++++++++++++++++----------------- 5 files changed, 166 insertions(+), 146 deletions(-) diff --git a/lib/morris.grid.coffee b/lib/morris.grid.coffee index adfce69..a9bda3d 100644 --- a/lib/morris.grid.coffee +++ b/lib/morris.grid.coffee @@ -95,7 +95,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 diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index 3d4446e..7a3ef4e 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -71,7 +71,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 hover margins # @@ -85,11 +85,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 ) - coords = (c for c in coords when c.y != null) if @options.continuousLine + 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 @@ -160,54 +160,48 @@ class Morris.Line extends Morris.Grid # create a path for a data series # # @private - createPath: (coords, smooth) -> + @createPath: (coords, smooth, bottom) -> path = "" - grads = @gradients coords if smooth + grads = Morris.Line.gradients(coords) if smooth - nextPathType = "M" - for i in [0..coords.length-1] - c = coords[i] - if c.y == null - nextPathType = "M" - continue - - if nextPathType == "M" - path += "M#{c.x},#{c.y}" - nextPathType = "CorL" - else - if smooth - 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}" + 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 - path += "L#{c.x},#{c.y}" + 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) -> - coordA = null - coordB = null - for c, i in coords - if i is 0 - coordA = coords[1] - coordB = c - else if i is (coords.length - 1) - coordA = c - coordB = coords[i - 1] - else - coordA = coords[i + 1] - coordB = coords[i - 1] - - if coordA.y != null and coordB.y != null and coordA.x != null and coordB.x != null - (coordA.y - coordB.y) / (coordA.x - coordB.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 null diff --git a/morris.js b/morris.js index 54e432d..8f97891 100644 --- a/morris.js +++ b/morris.js @@ -155,7 +155,7 @@ if (typeof yval === 'string') { yval = parseFloat(yval); } - if (typeof yval !== 'number') { + if ((yval != null) && typeof yval !== 'number') { yval = null; } if (yval != null) { @@ -484,7 +484,8 @@ smooth: true, hideHover: false, xLabels: 'auto', - xLabelFormat: null + xLabelFormat: null, + continuousLine: true }; Line.prototype.calc = function() { @@ -509,7 +510,7 @@ if (y != null) { _results1.push(this.transY(y)); } else { - _results1.push(null); + _results1.push(y); } } return _results1; @@ -533,7 +534,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 = []; @@ -545,7 +546,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] @@ -554,8 +555,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); } @@ -651,52 +665,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; diff --git a/morris.min.js b/morris.min.js index 2c8a0d1..47d49ed 100644 --- a/morris.min.js +++ b/morris.min.js @@ -1 +1 @@ -(function(){var e,t,n,r,i=[].slice,s={}.hasOwnProperty,o=function(e,t){function r(){this.constructor=e}for(var n in t)s.call(t,n)&&(e[n]=t[n]);return r.prototype=t.prototype,e.prototype=new r,e.__super__=t.prototype,e},u=function(e,t){return function(){return e.apply(t,arguments)}},a=[].indexOf||function(e){for(var t=0,n=this.length;tn.length&&(r+=i.slice(n.length)),r):"-"},t.pad2=function(e){return(e<10?"0":"")+e},t.Grid=function(n){function r(t){typeof t.element=="string"?this.el=e(document.getElementById(t.element)):this.el=e(t.element);if(this.el==null||this.el.length===0)throw new Error("Graph container element not found");this.options=e.extend({},this.gridDefaults,this.defaults||{},t);if(this.options.data===void 0||this.options.data.length===0)return;typeof this.options.units=="string"&&(this.options.postUnits=t.units),this.r=new Raphael(this.el[0]),this.elementWidth=null,this.elementHeight=null,this.dirty=!1,this.init&&this.init(),this.setData(this.options.data)}return o(r,n),r.prototype.gridDefaults={dateFormat:null,gridLineColor:"#aaa",gridStrokeWidth:.5,gridTextColor:"#888",gridTextSize:12,numLines:5,padding:25,parseTime:!0,postUnits:"",preUnits:"",ymax:"auto",ymin:"auto 0",goals:[],goalStrokeWidth:1,goalLineColors:["#666633","#999966","#cc6666","#663333"],events:[],eventStrokeWidth:1,eventLineColors:["#005a04","#ccffbb","#3a5f0b","#005502"]},r.prototype.setData=function(e,n){var r,i,s,o,u,a,f,l,c,h,p,d;n==null&&(n=!0),h=this.cumulative?0:null,p=this.cumulative?0:null,this.options.goals.length>0&&(u=Math.min.apply(null,this.options.goals),o=Math.max.apply(null,this.options.goals),p=p!=null?Math.min(p,u):u,h=h!=null?Math.max(h,o):o),this.data=function(){var n,r,o;o=[];for(s=n=0,r=e.length;nt.x)-(t.x>e.x)})),this.xmin=this.data[0].x,this.xmax=this.data[this.data.length-1].x,this.events=[],this.options.parseTime&&this.options.events.length>0&&(this.events=function(){var e,n,i,s;i=this.options.events,s=[];for(e=0,n=i.length;e5?(this.ymax=parseInt(this.options.ymax.slice(5),10),h!=null&&(this.ymax=Math.max(h,this.ymax))):this.ymax=h!=null?h:0:this.ymax=parseInt(this.options.ymax,10):this.ymax=this.options.ymax,typeof this.options.ymin=="string"?this.options.ymin.slice(0,4)==="auto"?this.options.ymin.length>5?(this.ymin=parseInt(this.options.ymin.slice(5),10),p!=null&&(this.ymin=Math.min(p,this.ymin))):this.ymin=p!==null?p:0:this.ymin=parseInt(this.options.ymin,10):this.ymin=this.options.ymin,this.ymin===this.ymax&&(p&&(this.ymin-=1),this.ymax+=1),this.yInterval=(this.ymax-this.ymin)/(this.options.numLines-1),this.yInterval>0&&this.yInterval<1?this.precision=-Math.floor(Math.log(this.yInterval)/Math.log(10)):this.precision=0,this.dirty=!0;if(n)return this.redraw()},r.prototype._calc=function(){var e,t,n;n=this.el.width(),e=this.el.height();if(this.elementWidth!==n||this.elementHeight!==e||this.dirty){this.elementWidth=n,this.elementHeight=e,this.dirty=!1,t=Math.max(this.measureText(this.yAxisFormat(this.ymin),this.options.gridTextSize).width,this.measureText(this.yAxisFormat(this.ymax),this.options.gridTextSize).width),this.left=t+this.options.padding,this.right=this.elementWidth-this.options.padding,this.top=this.options.padding,this.bottom=this.elementHeight-this.options.padding-1.5*this.options.gridTextSize,this.width=this.right-this.left,this.height=this.bottom-this.top,this.dx=this.width/(this.xmax-this.xmin),this.dy=this.height/(this.ymax-this.ymin);if(this.calc)return this.calc()}},r.prototype.transY=function(e){return this.bottom-(e-this.ymin)*this.dy},r.prototype.transX=function(e){return this.data.length===1?(this.left+this.right)/2:this.left+(e-this.xmin)*this.dx},r.prototype.redraw=function(){this.r.clear(),this._calc(),this.drawGrid(),this.drawGoals(),this.drawEvents();if(this.draw)return this.draw()},r.prototype.drawGoals=function(){var e,t,n,r,i,s;i=this.options.goals,s=[];for(t=n=0,r=i.length;n=t;n=s+=o)r=parseFloat(n.toFixed(this.precision)),i=this.transY(r),this.r.text(this.left-this.options.padding/2,i,this.yAxisFormat(r)).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor).attr("text-anchor","end"),u.push(this.r.path("M"+this.left+","+i+"H"+(this.left+this.width)).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth));return u},r.prototype.measureText=function(e,t){var n,r;return t==null&&(t=12),r=this.r.text(100,100,e).attr("font-size",t),n=r.getBBox(),r.remove(),n},r.prototype.yAxisFormat=function(e){return this.yLabelFormat(e)},r.prototype.yLabelFormat=function(e){return""+this.options.preUnits+t.commas(e)+this.options.postUnits},r}(t.EventEmitter),t.parseDate=function(e){var t,n,r,i,s,o,u,a,f,l,c;return typeof e=="number"?e:(n=e.match(/^(\d+) Q(\d)$/),i=e.match(/^(\d+)-(\d+)$/),s=e.match(/^(\d+)-(\d+)-(\d+)$/),u=e.match(/^(\d+) W(\d+)$/),a=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/),f=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/),n?(new Date(parseInt(n[1],10),parseInt(n[2],10)*3-1,1)).getTime():i?(new Date(parseInt(i[1],10),parseInt(i[2],10)-1,1)).getTime():s?(new Date(parseInt(s[1],10),parseInt(s[2],10)-1,parseInt(s[3],10))).getTime():u?(l=new Date(parseInt(u[1],10),0,1),l.getDay()!==4&&l.setMonth(0,1+(4-l.getDay()+7)%7),l.getTime()+parseInt(u[2],10)*6048e5):a?a[6]?(o=0,a[6]!=="Z"&&(o=parseInt(a[8],10)*60+parseInt(a[9],10),a[7]==="+"&&(o=0-o)),Date.UTC(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10)+o)):(new Date(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10))).getTime():f?(c=parseFloat(f[6]),t=Math.floor(c),r=Math.round((c-t)*1e3),f[8]?(o=0,f[8]!=="Z"&&(o=parseInt(f[10],10)*60+parseInt(f[11],10),f[9]==="+"&&(o=0-o)),Date.UTC(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10)+o,t,r)):(new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10),t,r)).getTime()):(new Date(parseInt(e,10),0,1)).getTime())},t.Line=function(e){function n(e){this.updateHilight=u(this.updateHilight,this),this.hilight=u(this.hilight,this),this.updateHover=u(this.updateHover,this);if(!(this instanceof t.Line))return new t.Line(e);n.__super__.constructor.call(this,e)}return o(n,e),n.prototype.init=function(){var e,t=this;return this.pointGrow=Raphael.animation({r:this.options.pointSize+3},25,"linear"),this.pointShrink=Raphael.animation({r:this.options.pointSize},25,"linear"),this.prevHilight=null,this.el.mousemove(function(e){return t.updateHilight(e.pageX)}),this.options.hideHover&&this.el.mouseout(function(e){return t.hilight(null)}),e=function(e){var n;return n=e.originalEvent.touches[0]||e.originalEvent.changedTouches[0],t.updateHilight(n.pageX),n},this.el.bind("touchstart",e),this.el.bind("touchmove",e),this.el.bind("touchend",e)},n.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],pointWidths:[1],pointStrokeColors:["#ffffff"],pointFillColors:[],hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,smooth:!0,hideHover:!1,xLabels:"auto",xLabelFormat:null},n.prototype.calc=function(){return this.calcPoints(),this.generatePaths(),this.calcHoverMargins()},n.prototype.calcPoints=function(){var e,t,n,r,i,s;i=this.data,s=[];for(n=0,r=i.length;ns;t=0<=s?++i:--i)r=this.options.smooth===!0||(o=this.options.ykeys[t],a.call(this.options.smooth,o)>=0),e=function(){var e,r,i,s;i=this.data,s=[];for(e=0,r=i.length;e1?u.push(this.createPath(e,r)):u.push(null);return u}.call(this)},n.prototype.draw=function(){return this.drawXAxis(),this.drawSeries(),this.drawHover(),this.hilight(this.options.hideHover?null:this.data.length-1)},n.prototype.drawXAxis=function(){var e,n,r,i,s,o,u,a,f,l,c=this;u=this.bottom+this.options.gridTextSize*1.25,o=50,i=null,e=function(e,t){var n,r;return n=c.r.text(c.transX(t),u,e).attr("font-size",c.options.gridTextSize).attr("fill",c.options.gridTextColor),r=n.getBBox(),(i==null||i>=r.x+r.width)&&r.x>=0&&r.x+r.width=0;t=o<=0?++i:--i)n=this.paths[t],n!==null&&this.r.path(n).attr("stroke",this.colorForSeries(t)).attr("stroke-width",this.options.lineWidth);this.seriesPoints=function(){var e,n,r;r=[];for(t=e=0,n=this.options.ykeys.length;0<=n?en;t=0<=n?++e:--e)r.push([]);return r}.call(this),a=[];for(t=s=u=this.options.ykeys.length-1;u<=0?s<=0:s>=0;t=u<=0?++s:--s)a.push(function(){var n,i,s,o;s=this.data,o=[];for(n=0,i=s.length;n=v;s=0<=v?++d:--d)n=e[s],s===0?f+="M"+n.x+","+n.y:(r=i[s],u=e[s-1],a=i[s-1],o=(n.x-u.x)/4,l=u.x+o,h=Math.min(this.bottom,u.y+o*a),c=n.x-o,p=Math.min(this.bottom,n.y-o*r),f+="C"+l+","+h+","+c+","+p+","+n.x+","+n.y)}else f="M"+function(){var t,r,i;i=[];for(t=0,r=e.length;ti;e=0<=i?++r:--r)t=this.cumulative?this.options.ykeys.length-e-1:e,n=this.r.text(0,this.options.hoverFontSize*1.5*(t+1.5)-this.hoverHeight/2,"").attr("fill",this.colorForSeries(e)).attr("font-size",this.options.hoverFontSize),this.yLabels.push(n),s.push(this.hoverSet.push(n));return s},n.prototype.updateHover=function(e){var t,n,r,i,s,o,u,a,f,l;this.hoverSet.show(),i=this.data[e],this.xLabel.attr("text",i.label),l=i.y;for(t=a=0,f=l.length;athis.hoverHeight+this.options.hoverPaddingY*2+this.options.hoverMargin+this.top?u=u-this.hoverHeight/2-this.options.hoverPaddingY-this.options.hoverMargin:u=u+this.hoverHeight/2+this.options.hoverPaddingY+this.options.hoverMargin,u=Math.max(this.top+this.hoverHeight/2+this.options.hoverPaddingY,u),u=Math.min(this.bottom-this.hoverHeight/2-this.options.hoverPaddingY,u),s=Math.min(this.right-r/2-this.options.hoverPaddingX,this.data[e]._x),s=Math.max(this.left+r/2+this.options.hoverPaddingX,s),this.hoverSet.attr("transform","t"+s+","+u)},n.prototype.hideHover=function(){return this.hoverSet.hide()},n.prototype.hilight=function(e){var t,n,r,i,s;if(this.prevHilight!==null&&this.prevHilight!==e)for(t=n=0,i=this.seriesPoints.length-1;0<=i?n<=i:n>=i;t=0<=i?++n:--n)this.seriesPoints[t][this.prevHilight]&&this.seriesPoints[t][this.prevHilight].animate(this.pointShrink);if(e!==null&&this.prevHilight!==e){for(t=r=0,s=this.seriesPoints.length-1;0<=s?r<=s:r>=s;t=0<=s?++r:--r)this.seriesPoints[t][e]&&this.seriesPoints[t][e].animate(this.pointGrow);this.updateHover(e)}this.prevHilight=e;if(e==null)return this.hideHover()},n.prototype.updateHilight=function(e){var t,n,r;e-=this.el.offset().left;for(t=n=0,r=this.hoverMargins.length;0<=r?nr;t=0<=r?++n:--n)if(this.hoverMargins[t]>e)break;return this.hilight(t)},n.prototype.colorForSeries=function(e){return this.options.lineColors[e%this.options.lineColors.length]},n.prototype.strokeWidthForSeries=function(e){return this.options.pointWidths[e%this.options.pointWidths.length]},n.prototype.strokeForSeries=function(e){return this.options.pointStrokeColors[e%this.options.pointStrokeColors.length]},n.prototype.pointFillColorForSeries=function(e){return this.options.pointFillColors[e%this.options.pointFillColors.length]},n}(t.Grid),t.labelSeries=function(n,r,i,s,o){var u,a,f,l,c,h,p,d,v,m,g;f=200*(r-n)/i,a=new Date(n),p=t.LABEL_SPECS[s];if(p===void 0){g=t.AUTO_LABEL_ORDER;for(v=0,m=g.length;v=h.span){p=h;break}}}p===void 0&&(p=t.LABEL_SPECS.second),o&&(p=e.extend({},p,{fmt:o})),u=p.start(a),c=[];while((d=u.getTime())<=r)d>=n&&c.push([p.fmt(u),d]),p.incr(u);return c},n=function(e){return{span:e*60*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())},incr:function(t){return t.setMinutes(t.getMinutes()+e)}}},r=function(e){return{span:e*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())+":"+t.pad2(e.getSeconds())},incr:function(t){return t.setSeconds(t.getSeconds()+e)}}},t.LABEL_SPECS={decade:{span:1728e8,start:function(e){return new Date(e.getFullYear()-e.getFullYear()%10,0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+10)}},year:{span:1728e7,start:function(e){return new Date(e.getFullYear(),0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+1)}},month:{span:24192e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),1)},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)},incr:function(e){return e.setMonth(e.getMonth()+1)}},day:{span:864e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate())},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)+"-"+t.pad2(e.getDate())},incr:function(e){return e.setDate(e.getDate()+1)}},hour:n(60),"30min":n(30),"15min":n(15),"10min":n(10),"5min":n(5),minute:n(1),"30sec":r(30),"15sec":r(15),"10sec":r(10),"5sec":r(5),second:r(1)},t.AUTO_LABEL_ORDER=["decade","year","month","day","hour","30min","15min","10min","5min","minute","30sec","15sec","10sec","5sec","second"],t.Area=function(e){function n(e){if(!(this instanceof t.Area))return new t.Area(e);this.cumulative=!0,n.__super__.constructor.call(this,e)}return o(n,e),n.prototype.calcPoints=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(r=0,i=s.length;r=0;e=i<=0?++r:--r)t=this.paths[e],t!==null&&(t+="L"+this.transX(this.xmax)+","+this.bottom+"L"+this.transX(this.xmin)+","+this.bottom+"Z",this.r.path(t).attr("fill",this.fillForSeries(e)).attr("stroke-width",0));return n.__super__.drawSeries.call(this)},n.prototype.fillForSeries=function(e){var t;return t=Raphael.rgb2hsl(this.colorForSeries(e)),Raphael.hsl(t.h,Math.min(255,t.s*.75),Math.min(255,t.l*1.25))},n}(t.Line),t.Bar=function(n){function r(n){this.updateHilight=u(this.updateHilight,this),this.hilight=u(this.hilight,this),this.updateHover=u(this.updateHover,this);if(!(this instanceof t.Bar))return new t.Bar(n);r.__super__.constructor.call(this,e.extend({},n,{parseTime:!1}))}return o(r,n),r.prototype.init=function(){var e,t=this;return this.prevHilight=null,this.el.mousemove(function(e){return t.updateHilight(e.pageX)}),this.options.hideHover&&this.el.mouseout(function(e){return t.hilight(null)}),e=function(e){var n;return n=e.originalEvent.touches[0]||e.originalEvent.changedTouches[0],t.updateHilight(n.pageX),n},this.el.bind("touchstart",e),this.el.bind("touchmove",e),this.el.bind("touchend",e)},r.prototype.defaults={barSizeRatio:.75,barGap:3,barColors:["#0b62a4","#7a92a3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,hideHover:!1},r.prototype.calc=function(){return this.calcBars(),this.calcHoverMargins()},r.prototype.calcBars=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(e=r=0,i=s.length;rn;e=1<=n?++t:--t)r.push(this.left+e*this.width/this.data.length);return r}.call(this)},r.prototype.draw=function(){return this.drawXAxis(),this.drawSeries(),this.drawHover(),this.hilight(this.options.hideHover?null:this.data.length-1)},r.prototype.drawXAxis=function(){var e,t,n,r,i,s,o,u,a,f;o=this.bottom+this.options.gridTextSize*1.25,s=50,r=null,f=[];for(e=u=0,a=this.data.length;0<=a?ua;e=0<=a?++u:--u)i=this.data[this.data.length-1-e],t=this.r.text(i._x,o,i.label).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor),n=t.getBBox(),(r==null||r>=n.x+n.width)&&n.x>=0&&n.x+n.width=0?this.transY(0):null,this.bars=function(){var o,h,p,d;p=this.data,d=[];for(r=o=0,h=p.length;or;e=0<=r?++n:--n)t=this.r.text(0,this.options.hoverFontSize*1.5*(e+1.5)-this.hoverHeight/2,"").attr("font-size",this.options.hoverFontSize),this.yLabels.push(t),i.push(this.hoverSet.push(t));return i},r.prototype.updateHover=function(e){var t,n,r,i,s,o,u,a,f,l;this.hoverSet.show(),i=this.data[e],this.xLabel.attr("text",i.label),l=i.y;for(t=a=0,f=l.length;ar;t=0<=r?++n:--n)if(this.hoverMargins[t]>e)break;return this.hilight(t)},r.prototype.colorFor=function(e,t,n){var r,i;return typeof this.options.barColors=="function"?(r={x:e.x,y:e.y[t],label:e.label},i={index:t,key:this.options.ykeys[t],label:this.options.labels[t]},this.options.barColors.call(this,r,i,n)):this.options.barColors[t%this.options.barColors.length]},r}(t.Grid),t.Donut=function(){function n(n){this.select=u(this.select,this);if(!(this instanceof t.Donut))return new t.Donut(n);typeof n.element=="string"?this.el=e(document.getElementById(n.element)):this.el=e(n.element),this.options=e.extend({},this.defaults,n);if(this.el===null||this.el.length===0)throw new Error("Graph placeholder not found.");if(n.data===void 0||n.data.length===0)return;this.data=n.data,this.redraw()}return n.prototype.defaults={colors:["#0B62A4","#3980B5","#679DC6","#95BBD7","#B0CCE1","#095791","#095085","#083E67","#052C48","#042135"],formatter:t.commas},n.prototype.redraw=function(){var e,n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b,w,E,S,x;this.el.empty(),this.r=new Raphael(this.el[0]),n=this.el.width()/2,r=this.el.height()/2,h=(Math.min(n,r)-10)/3,c=0,w=this.data;for(d=0,g=w.length;dMath.PI?1:0,this.path=this.calcSegment(this.inner+3,this.inner+this.outer-5),this.selectedPath=this.calcSegment(this.inner+3,this.inner+this.outer),this.hilight=this.calcArc(this.inner)}return o(t,e),t.prototype.calcArcPoints=function(e){return[this.cx+e*this.sin_p0,this.cy+e*this.cos_p0,this.cx+e*this.sin_p1,this.cy+e*this.cos_p1]},t.prototype.calcSegment=function(e,t){var n,r,i,s,o,u,a,f,l,c;return l=this.calcArcPoints(e),n=l[0],i=l[1],r=l[2],s=l[3],c=this.calcArcPoints(t),o=c[0],a=c[1],u=c[2],f=c[3],"M"+n+","+i+("A"+e+","+e+",0,"+this.long+",0,"+r+","+s)+("L"+u+","+f)+("A"+t+","+t+",0,"+this.long+",1,"+o+","+a)+"Z"},t.prototype.calcArc=function(e){var t,n,r,i,s;return s=this.calcArcPoints(e),t=s[0],r=s[1],n=s[2],i=s[3],"M"+t+","+r+("A"+e+","+e+",0,"+this.long+",0,"+n+","+i)},t.prototype.render=function(e){var t=this;return this.arc=e.path(this.hilight).attr({stroke:this.color,"stroke-width":2,opacity:0}),this.seg=e.path(this.path).attr({fill:this.color,stroke:"white","stroke-width":3}).hover(function(){return t.fire("hover",t)})},t.prototype.select=function(){if(!this.selected)return this.seg.animate({path:this.selectedPath},150,"<>"),this.arc.animate({opacity:1},150,"<>"),this.selected=!0},t.prototype.deselect=function(){if(this.selected)return this.seg.animate({path:this.path},150,"<>"),this.arc.animate({opacity:0},150,"<>"),this.selected=!1},t}(t.EventEmitter)}).call(this); \ No newline at end of file +(function(){var e,t,n,r,i=[].slice,s={}.hasOwnProperty,o=function(e,t){function r(){this.constructor=e}for(var n in t)s.call(t,n)&&(e[n]=t[n]);return r.prototype=t.prototype,e.prototype=new r,e.__super__=t.prototype,e},u=function(e,t){return function(){return e.apply(t,arguments)}},a=[].indexOf||function(e){for(var t=0,n=this.length;tn.length&&(r+=i.slice(n.length)),r):"-"},t.pad2=function(e){return(e<10?"0":"")+e},t.Grid=function(n){function r(t){typeof t.element=="string"?this.el=e(document.getElementById(t.element)):this.el=e(t.element);if(this.el==null||this.el.length===0)throw new Error("Graph container element not found");this.options=e.extend({},this.gridDefaults,this.defaults||{},t);if(this.options.data===void 0||this.options.data.length===0)return;typeof this.options.units=="string"&&(this.options.postUnits=t.units),this.r=new Raphael(this.el[0]),this.elementWidth=null,this.elementHeight=null,this.dirty=!1,this.init&&this.init(),this.setData(this.options.data)}return o(r,n),r.prototype.gridDefaults={dateFormat:null,gridLineColor:"#aaa",gridStrokeWidth:.5,gridTextColor:"#888",gridTextSize:12,numLines:5,padding:25,parseTime:!0,postUnits:"",preUnits:"",ymax:"auto",ymin:"auto 0",goals:[],goalStrokeWidth:1,goalLineColors:["#666633","#999966","#cc6666","#663333"],events:[],eventStrokeWidth:1,eventLineColors:["#005a04","#ccffbb","#3a5f0b","#005502"]},r.prototype.setData=function(e,n){var r,i,s,o,u,a,f,l,c,h,p,d;n==null&&(n=!0),h=this.cumulative?0:null,p=this.cumulative?0:null,this.options.goals.length>0&&(u=Math.min.apply(null,this.options.goals),o=Math.max.apply(null,this.options.goals),p=p!=null?Math.min(p,u):u,h=h!=null?Math.max(h,o):o),this.data=function(){var n,r,o;o=[];for(s=n=0,r=e.length;nt.x)-(t.x>e.x)})),this.xmin=this.data[0].x,this.xmax=this.data[this.data.length-1].x,this.events=[],this.options.parseTime&&this.options.events.length>0&&(this.events=function(){var e,n,i,s;i=this.options.events,s=[];for(e=0,n=i.length;e5?(this.ymax=parseInt(this.options.ymax.slice(5),10),h!=null&&(this.ymax=Math.max(h,this.ymax))):this.ymax=h!=null?h:0:this.ymax=parseInt(this.options.ymax,10):this.ymax=this.options.ymax,typeof this.options.ymin=="string"?this.options.ymin.slice(0,4)==="auto"?this.options.ymin.length>5?(this.ymin=parseInt(this.options.ymin.slice(5),10),p!=null&&(this.ymin=Math.min(p,this.ymin))):this.ymin=p!==null?p:0:this.ymin=parseInt(this.options.ymin,10):this.ymin=this.options.ymin,this.ymin===this.ymax&&(p&&(this.ymin-=1),this.ymax+=1),this.yInterval=(this.ymax-this.ymin)/(this.options.numLines-1),this.yInterval>0&&this.yInterval<1?this.precision=-Math.floor(Math.log(this.yInterval)/Math.log(10)):this.precision=0,this.dirty=!0;if(n)return this.redraw()},r.prototype._calc=function(){var e,t,n;n=this.el.width(),e=this.el.height();if(this.elementWidth!==n||this.elementHeight!==e||this.dirty){this.elementWidth=n,this.elementHeight=e,this.dirty=!1,t=Math.max(this.measureText(this.yAxisFormat(this.ymin),this.options.gridTextSize).width,this.measureText(this.yAxisFormat(this.ymax),this.options.gridTextSize).width),this.left=t+this.options.padding,this.right=this.elementWidth-this.options.padding,this.top=this.options.padding,this.bottom=this.elementHeight-this.options.padding-1.5*this.options.gridTextSize,this.width=this.right-this.left,this.height=this.bottom-this.top,this.dx=this.width/(this.xmax-this.xmin),this.dy=this.height/(this.ymax-this.ymin);if(this.calc)return this.calc()}},r.prototype.transY=function(e){return this.bottom-(e-this.ymin)*this.dy},r.prototype.transX=function(e){return this.data.length===1?(this.left+this.right)/2:this.left+(e-this.xmin)*this.dx},r.prototype.redraw=function(){this.r.clear(),this._calc(),this.drawGrid(),this.drawGoals(),this.drawEvents();if(this.draw)return this.draw()},r.prototype.drawGoals=function(){var e,t,n,r,i,s;i=this.options.goals,s=[];for(t=n=0,r=i.length;n=t;n=s+=o)r=parseFloat(n.toFixed(this.precision)),i=this.transY(r),this.r.text(this.left-this.options.padding/2,i,this.yAxisFormat(r)).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor).attr("text-anchor","end"),u.push(this.r.path("M"+this.left+","+i+"H"+(this.left+this.width)).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth));return u},r.prototype.measureText=function(e,t){var n,r;return t==null&&(t=12),r=this.r.text(100,100,e).attr("font-size",t),n=r.getBBox(),r.remove(),n},r.prototype.yAxisFormat=function(e){return this.yLabelFormat(e)},r.prototype.yLabelFormat=function(e){return""+this.options.preUnits+t.commas(e)+this.options.postUnits},r}(t.EventEmitter),t.parseDate=function(e){var t,n,r,i,s,o,u,a,f,l,c;return typeof e=="number"?e:(n=e.match(/^(\d+) Q(\d)$/),i=e.match(/^(\d+)-(\d+)$/),s=e.match(/^(\d+)-(\d+)-(\d+)$/),u=e.match(/^(\d+) W(\d+)$/),a=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/),f=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/),n?(new Date(parseInt(n[1],10),parseInt(n[2],10)*3-1,1)).getTime():i?(new Date(parseInt(i[1],10),parseInt(i[2],10)-1,1)).getTime():s?(new Date(parseInt(s[1],10),parseInt(s[2],10)-1,parseInt(s[3],10))).getTime():u?(l=new Date(parseInt(u[1],10),0,1),l.getDay()!==4&&l.setMonth(0,1+(4-l.getDay()+7)%7),l.getTime()+parseInt(u[2],10)*6048e5):a?a[6]?(o=0,a[6]!=="Z"&&(o=parseInt(a[8],10)*60+parseInt(a[9],10),a[7]==="+"&&(o=0-o)),Date.UTC(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10)+o)):(new Date(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10))).getTime():f?(c=parseFloat(f[6]),t=Math.floor(c),r=Math.round((c-t)*1e3),f[8]?(o=0,f[8]!=="Z"&&(o=parseInt(f[10],10)*60+parseInt(f[11],10),f[9]==="+"&&(o=0-o)),Date.UTC(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10)+o,t,r)):(new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10),t,r)).getTime()):(new Date(parseInt(e,10),0,1)).getTime())},t.Line=function(e){function n(e){this.updateHilight=u(this.updateHilight,this),this.hilight=u(this.hilight,this),this.updateHover=u(this.updateHover,this);if(!(this instanceof t.Line))return new t.Line(e);n.__super__.constructor.call(this,e)}return o(n,e),n.prototype.init=function(){var e,t=this;return this.pointGrow=Raphael.animation({r:this.options.pointSize+3},25,"linear"),this.pointShrink=Raphael.animation({r:this.options.pointSize},25,"linear"),this.prevHilight=null,this.el.mousemove(function(e){return t.updateHilight(e.pageX)}),this.options.hideHover&&this.el.mouseout(function(e){return t.hilight(null)}),e=function(e){var n;return n=e.originalEvent.touches[0]||e.originalEvent.changedTouches[0],t.updateHilight(n.pageX),n},this.el.bind("touchstart",e),this.el.bind("touchmove",e),this.el.bind("touchend",e)},n.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],pointWidths:[1],pointStrokeColors:["#ffffff"],pointFillColors:[],hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,smooth:!0,hideHover:!1,xLabels:"auto",xLabelFormat:null,continuousLine:!0},n.prototype.calc=function(){return this.calcPoints(),this.generatePaths(),this.calcHoverMargins()},n.prototype.calcPoints=function(){var e,t,n,r,i,s;i=this.data,s=[];for(n=0,r=i.length;nu;r=0<=u?++o:--o)s=this.options.smooth===!0||(f=this.options.ykeys[r],a.call(this.options.smooth,f)>=0),n=function(){var e,t,n,s;n=this.data,s=[];for(e=0,t=n.length;e1?l.push(t.Line.createPath(n,s,this.bottom)):l.push(null);return l}.call(this)},n.prototype.draw=function(){return this.drawXAxis(),this.drawSeries(),this.drawHover(),this.hilight(this.options.hideHover?null:this.data.length-1)},n.prototype.drawXAxis=function(){var e,n,r,i,s,o,u,a,f,l,c=this;u=this.bottom+this.options.gridTextSize*1.25,o=50,i=null,e=function(e,t){var n,r;return n=c.r.text(c.transX(t),u,e).attr("font-size",c.options.gridTextSize).attr("fill",c.options.gridTextColor),r=n.getBBox(),(i==null||i>=r.x+r.width)&&r.x>=0&&r.x+r.width=0;t=o<=0?++i:--i)n=this.paths[t],n!==null&&this.r.path(n).attr("stroke",this.colorForSeries(t)).attr("stroke-width",this.options.lineWidth);this.seriesPoints=function(){var e,n,r;r=[];for(t=e=0,n=this.options.ykeys.length;0<=n?en;t=0<=n?++e:--e)r.push([]);return r}.call(this),a=[];for(t=s=u=this.options.ykeys.length-1;u<=0?s<=0:s>=0;t=u<=0?++s:--s)a.push(function(){var n,i,s,o;s=this.data,o=[];for(n=0,i=s.length;ni;e=0<=i?++r:--r)t=this.cumulative?this.options.ykeys.length-e-1:e,n=this.r.text(0,this.options.hoverFontSize*1.5*(t+1.5)-this.hoverHeight/2,"").attr("fill",this.colorForSeries(e)).attr("font-size",this.options.hoverFontSize),this.yLabels.push(n),s.push(this.hoverSet.push(n));return s},n.prototype.updateHover=function(e){var t,n,r,i,s,o,u,a,f,l;this.hoverSet.show(),i=this.data[e],this.xLabel.attr("text",i.label),l=i.y;for(t=a=0,f=l.length;athis.hoverHeight+this.options.hoverPaddingY*2+this.options.hoverMargin+this.top?u=u-this.hoverHeight/2-this.options.hoverPaddingY-this.options.hoverMargin:u=u+this.hoverHeight/2+this.options.hoverPaddingY+this.options.hoverMargin,u=Math.max(this.top+this.hoverHeight/2+this.options.hoverPaddingY,u),u=Math.min(this.bottom-this.hoverHeight/2-this.options.hoverPaddingY,u),s=Math.min(this.right-r/2-this.options.hoverPaddingX,this.data[e]._x),s=Math.max(this.left+r/2+this.options.hoverPaddingX,s),this.hoverSet.attr("transform","t"+s+","+u)},n.prototype.hideHover=function(){return this.hoverSet.hide()},n.prototype.hilight=function(e){var t,n,r,i,s;if(this.prevHilight!==null&&this.prevHilight!==e)for(t=n=0,i=this.seriesPoints.length-1;0<=i?n<=i:n>=i;t=0<=i?++n:--n)this.seriesPoints[t][this.prevHilight]&&this.seriesPoints[t][this.prevHilight].animate(this.pointShrink);if(e!==null&&this.prevHilight!==e){for(t=r=0,s=this.seriesPoints.length-1;0<=s?r<=s:r>=s;t=0<=s?++r:--r)this.seriesPoints[t][e]&&this.seriesPoints[t][e].animate(this.pointGrow);this.updateHover(e)}this.prevHilight=e;if(e==null)return this.hideHover()},n.prototype.updateHilight=function(e){var t,n,r;e-=this.el.offset().left;for(t=n=0,r=this.hoverMargins.length;0<=r?nr;t=0<=r?++n:--n)if(this.hoverMargins[t]>e)break;return this.hilight(t)},n.prototype.colorForSeries=function(e){return this.options.lineColors[e%this.options.lineColors.length]},n.prototype.strokeWidthForSeries=function(e){return this.options.pointWidths[e%this.options.pointWidths.length]},n.prototype.strokeForSeries=function(e){return this.options.pointStrokeColors[e%this.options.pointStrokeColors.length]},n.prototype.pointFillColorForSeries=function(e){return this.options.pointFillColors[e%this.options.pointFillColors.length]},n}(t.Grid),t.labelSeries=function(n,r,i,s,o){var u,a,f,l,c,h,p,d,v,m,g;f=200*(r-n)/i,a=new Date(n),p=t.LABEL_SPECS[s];if(p===void 0){g=t.AUTO_LABEL_ORDER;for(v=0,m=g.length;v=h.span){p=h;break}}}p===void 0&&(p=t.LABEL_SPECS.second),o&&(p=e.extend({},p,{fmt:o})),u=p.start(a),c=[];while((d=u.getTime())<=r)d>=n&&c.push([p.fmt(u),d]),p.incr(u);return c},n=function(e){return{span:e*60*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())},incr:function(t){return t.setMinutes(t.getMinutes()+e)}}},r=function(e){return{span:e*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())+":"+t.pad2(e.getSeconds())},incr:function(t){return t.setSeconds(t.getSeconds()+e)}}},t.LABEL_SPECS={decade:{span:1728e8,start:function(e){return new Date(e.getFullYear()-e.getFullYear()%10,0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+10)}},year:{span:1728e7,start:function(e){return new Date(e.getFullYear(),0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+1)}},month:{span:24192e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),1)},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)},incr:function(e){return e.setMonth(e.getMonth()+1)}},day:{span:864e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate())},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)+"-"+t.pad2(e.getDate())},incr:function(e){return e.setDate(e.getDate()+1)}},hour:n(60),"30min":n(30),"15min":n(15),"10min":n(10),"5min":n(5),minute:n(1),"30sec":r(30),"15sec":r(15),"10sec":r(10),"5sec":r(5),second:r(1)},t.AUTO_LABEL_ORDER=["decade","year","month","day","hour","30min","15min","10min","5min","minute","30sec","15sec","10sec","5sec","second"],t.Area=function(e){function n(e){if(!(this instanceof t.Area))return new t.Area(e);this.cumulative=!0,n.__super__.constructor.call(this,e)}return o(n,e),n.prototype.calcPoints=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(r=0,i=s.length;r=0;e=i<=0?++r:--r)t=this.paths[e],t!==null&&(t+="L"+this.transX(this.xmax)+","+this.bottom+"L"+this.transX(this.xmin)+","+this.bottom+"Z",this.r.path(t).attr("fill",this.fillForSeries(e)).attr("stroke-width",0));return n.__super__.drawSeries.call(this)},n.prototype.fillForSeries=function(e){var t;return t=Raphael.rgb2hsl(this.colorForSeries(e)),Raphael.hsl(t.h,Math.min(255,t.s*.75),Math.min(255,t.l*1.25))},n}(t.Line),t.Bar=function(n){function r(n){this.updateHilight=u(this.updateHilight,this),this.hilight=u(this.hilight,this),this.updateHover=u(this.updateHover,this);if(!(this instanceof t.Bar))return new t.Bar(n);r.__super__.constructor.call(this,e.extend({},n,{parseTime:!1}))}return o(r,n),r.prototype.init=function(){var e,t=this;return this.prevHilight=null,this.el.mousemove(function(e){return t.updateHilight(e.pageX)}),this.options.hideHover&&this.el.mouseout(function(e){return t.hilight(null)}),e=function(e){var n;return n=e.originalEvent.touches[0]||e.originalEvent.changedTouches[0],t.updateHilight(n.pageX),n},this.el.bind("touchstart",e),this.el.bind("touchmove",e),this.el.bind("touchend",e)},r.prototype.defaults={barSizeRatio:.75,barGap:3,barColors:["#0b62a4","#7a92a3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,hideHover:!1},r.prototype.calc=function(){return this.calcBars(),this.calcHoverMargins()},r.prototype.calcBars=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(e=r=0,i=s.length;rn;e=1<=n?++t:--t)r.push(this.left+e*this.width/this.data.length);return r}.call(this)},r.prototype.draw=function(){return this.drawXAxis(),this.drawSeries(),this.drawHover(),this.hilight(this.options.hideHover?null:this.data.length-1)},r.prototype.drawXAxis=function(){var e,t,n,r,i,s,o,u,a,f;o=this.bottom+this.options.gridTextSize*1.25,s=50,r=null,f=[];for(e=u=0,a=this.data.length;0<=a?ua;e=0<=a?++u:--u)i=this.data[this.data.length-1-e],t=this.r.text(i._x,o,i.label).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor),n=t.getBBox(),(r==null||r>=n.x+n.width)&&n.x>=0&&n.x+n.width=0?this.transY(0):null,this.bars=function(){var o,h,p,d;p=this.data,d=[];for(r=o=0,h=p.length;or;e=0<=r?++n:--n)t=this.r.text(0,this.options.hoverFontSize*1.5*(e+1.5)-this.hoverHeight/2,"").attr("font-size",this.options.hoverFontSize),this.yLabels.push(t),i.push(this.hoverSet.push(t));return i},r.prototype.updateHover=function(e){var t,n,r,i,s,o,u,a,f,l;this.hoverSet.show(),i=this.data[e],this.xLabel.attr("text",i.label),l=i.y;for(t=a=0,f=l.length;ar;t=0<=r?++n:--n)if(this.hoverMargins[t]>e)break;return this.hilight(t)},r.prototype.colorFor=function(e,t,n){var r,i;return typeof this.options.barColors=="function"?(r={x:e.x,y:e.y[t],label:e.label},i={index:t,key:this.options.ykeys[t],label:this.options.labels[t]},this.options.barColors.call(this,r,i,n)):this.options.barColors[t%this.options.barColors.length]},r}(t.Grid),t.Donut=function(){function n(n){this.select=u(this.select,this);if(!(this instanceof t.Donut))return new t.Donut(n);typeof n.element=="string"?this.el=e(document.getElementById(n.element)):this.el=e(n.element),this.options=e.extend({},this.defaults,n);if(this.el===null||this.el.length===0)throw new Error("Graph placeholder not found.");if(n.data===void 0||n.data.length===0)return;this.data=n.data,this.redraw()}return n.prototype.defaults={colors:["#0B62A4","#3980B5","#679DC6","#95BBD7","#B0CCE1","#095791","#095085","#083E67","#052C48","#042135"],formatter:t.commas},n.prototype.redraw=function(){var e,n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b,w,E,S,x;this.el.empty(),this.r=new Raphael(this.el[0]),n=this.el.width()/2,r=this.el.height()/2,h=(Math.min(n,r)-10)/3,c=0,w=this.data;for(d=0,g=w.length;dMath.PI?1:0,this.path=this.calcSegment(this.inner+3,this.inner+this.outer-5),this.selectedPath=this.calcSegment(this.inner+3,this.inner+this.outer),this.hilight=this.calcArc(this.inner)}return o(t,e),t.prototype.calcArcPoints=function(e){return[this.cx+e*this.sin_p0,this.cy+e*this.cos_p0,this.cx+e*this.sin_p1,this.cy+e*this.cos_p1]},t.prototype.calcSegment=function(e,t){var n,r,i,s,o,u,a,f,l,c;return l=this.calcArcPoints(e),n=l[0],i=l[1],r=l[2],s=l[3],c=this.calcArcPoints(t),o=c[0],a=c[1],u=c[2],f=c[3],"M"+n+","+i+("A"+e+","+e+",0,"+this.long+",0,"+r+","+s)+("L"+u+","+f)+("A"+t+","+t+",0,"+this.long+",1,"+o+","+a)+"Z"},t.prototype.calcArc=function(e){var t,n,r,i,s;return s=this.calcArcPoints(e),t=s[0],r=s[1],n=s[2],i=s[3],"M"+t+","+r+("A"+e+","+e+",0,"+this.long+",0,"+n+","+i)},t.prototype.render=function(e){var t=this;return this.arc=e.path(this.hilight).attr({stroke:this.color,"stroke-width":2,opacity:0}),this.seg=e.path(this.path).attr({fill:this.color,stroke:"white","stroke-width":3}).hover(function(){return t.fire("hover",t)})},t.prototype.select=function(){if(!this.selected)return this.seg.animate({path:this.selectedPath},150,"<>"),this.arc.animate({opacity:1},150,"<>"),this.selected=!0},t.prototype.deselect=function(){if(this.selected)return this.seg.animate({path:this.path},150,"<>"),this.arc.animate({opacity:0},150,"<>"),this.selected=!1},t}(t.EventEmitter)}).call(this); \ No newline at end of file diff --git a/spec/lib/line/line_spec.coffee b/spec/lib/line/line_spec.coffee index 307701d..3556e98 100644 --- a/spec/lib/line/line_spec.coffee +++ b/spec/lib/line/line_spec.coffee @@ -68,80 +68,76 @@ describe 'Morris.Line', -> "#{x.getYear()}/#{x.getMonth()+1}/#{x.getDay()}" chart.data.map((x) -> x.label).should == ['2012/1/1', '2012/1/2'] - describe '#generatePaths', -> - TestDefaults = {} + describe 'rendering lines', -> beforeEach -> - TestDefaults = {element: 'graph', xkey: 'x', ykeys: ['y'], labels: ['dontcare']} + @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', -> - testData = [{x: 1, y: 1}, {x: 3, y: 1 }] - chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: true}) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + Morris.Line @defaults + shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/ - it 'should generate jagged, continuous lines when options.smooth is false and options.continuousLine is true', -> - testData = [{x: 1, y: 1}, {x: 2, y: null }, {x: 3, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData, smooth: false, continuousLine: true}) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'L'] - - it 'should generate jagged, discontinuous lines when options.smooth is false and options.continuousLine is false', -> - testData = [{x: 1, y: 1}, {x: 2, y: null }, {x: 3, y: 1}, {x: 4, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData, smooth: false, continuousLine: false}) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'L'] + 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', -> - testData = [{x: 1, a: 1, b: 1}, {x: 3, a: 1, b: 1}] - chart = Morris.Line(TestDefaults extends {data: testData, smooth: ['a'], ykeys: ['a', 'b']}) - pathA = chart.generatePaths()[0] - pathA.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + 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' - pathB = chart.generatePaths()[1] - pathB.match(/[A-Z]/g).should.deep.equal ['M', 'L'] + it 'should ignore undefined values', -> + @defaults.data[2].y = undefined + Morris.Line @defaults + shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){3}/ - #skipping because undefined values are converted to nulls in the setData method morris.grid line#98 - it.skip 'should filter undefined values from series', -> - testData = [{x: 1, y: 1}, {x: 2, y: undefined}, {x: 3, y: 1}] - options = - data: testData - continuousLine: false #doesn't matter for undefined values + 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}/ - chart = Morris.Line(TestDefaults extends options) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] - - it 'should filter null values from series only when options.continuousLine is true', -> - testData = [{x: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: true}) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] - - it 'should not filter null values from series when options.continuousLine is false', -> - testData = [{x: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}, {x: 4, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData, continuousLine: false}) - path = chart.generatePaths()[0] - path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'C'] + 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', -> - TestDefaults = {} - beforeEach -> - TestDefaults = {element: 'graph', xkey: 'x', ykeys: ['y'], labels: ['dontcare']} it 'should generate a smooth line', -> - testData = [{x: 1, y: 1}, {x: 3, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData}) - path = chart.createPath(testData, true) - path.match(/[A-Z]/g).should.deep.equal ['M', 'C'] + 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: 1, y: 1}, {x: 3, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData}) - path = chart.createPath(testData, false) - path.match(/[A-Z]/g).should.deep.equal ['M', 'L'] + 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: 1, y: 1}, {x: 2, y: null}, {x: 3, y: 1}, {x: 4, y: 1}] - chart = Morris.Line(TestDefaults extends {data: testData}) - path = chart.createPath(testData, true) - path.match(/[A-Z]/g).should.deep.equal ['M', 'M', 'C'] + 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'