From 354e15abd4ca9d73768dfe07c567737d835fcaa2 Mon Sep 17 00:00:00 2001 From: Dan Egnor Date: Thu, 13 May 2010 18:37:04 -0700 Subject: [PATCH] Fix issue 93: get the order of stacked graphs right. Also fix a problem where stacked, filled graphs would pile up their colors, which doesn't seem right for the normal semantics of stacked graphs. Also expand on the tests for stacked and filled graphs. --- dygraph-canvas.js | 43 +++++++++++++++++++------------------- dygraph.js | 58 +++++++++++++++++++++++++--------------------------- tests/fillGraph.html | 4 ++-- tests/stacked.html | 49 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 93 insertions(+), 61 deletions(-) diff --git a/dygraph-canvas.js b/dygraph-canvas.js index b83e54a..e661d7e 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -454,6 +454,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { var fillAlpha = this.options.fillAlpha; var errorBars = this.layout.options.errorBars; var fillGraph = this.layout.options.fillGraph; + var stackedGraph = this.layout.options.stackedGraph; var setNames = []; for (var name in this.layout.datasets) { @@ -491,11 +492,8 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { // setup graphics context ctx.save(); - ctx.strokeStyle = color; - ctx.lineWidth = this.options.strokeWidth; var prevX = NaN; var prevYs = [-1, -1]; - var count = 0; var yscale = this.layout.yscale; // should be same color as the lines but only 15% opaque. var rgb = new RGBColor(color); @@ -505,7 +503,6 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { ctx.beginPath(); for (var j = 0; j < this.layout.points.length; j++) { var point = this.layout.points[j]; - count++; if (point.name == setName) { if (!isOK(point.y)) { prevX = NaN; @@ -523,28 +520,29 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { ctx.lineTo(prevX, prevYs[1]); ctx.closePath(); } - prevYs[0] = newYs[0]; - prevYs[1] = newYs[1]; + prevYs = newYs; prevX = point.canvasx; } } ctx.fill(); } } else if (fillGraph) { - // TODO(danvk): merge this code with the logic above; they're very similar. - for (var i = 0; i < setCount; i++) { + var axisY = 1.0 + this.layout.minyval * this.layout.yscale; + if (axisY < 0.0) axisY = 0.0; + else if (axisY > 1.0) axisY = 1.0; + axisY = this.area.h * axisY + this.area.y; + + var baseline = [] // for stacked graphs: baseline for filling + + // process sets in reverse order (needed for stacked graphs) + for (var i = setCount - 1; i >= 0; i--) { var setName = setNames[i]; - var setNameLast; - if (i>0) setNameLast = setNames[i-1]; var color = this.colors[setName]; // setup graphics context ctx.save(); - ctx.strokeStyle = color; - ctx.lineWidth = this.options.strokeWidth; var prevX = NaN; var prevYs = [-1, -1]; - var count = 0; var yscale = this.layout.yscale; // should be same color as the lines but only 15% opaque. var rgb = new RGBColor(color); @@ -554,18 +552,20 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { ctx.beginPath(); for (var j = 0; j < this.layout.points.length; j++) { var point = this.layout.points[j]; - count++; if (point.name == setName) { if (!isOK(point.y)) { prevX = NaN; continue; } - var pX = 1.0 + this.layout.minyval * this.layout.yscale; - if (pX < 0.0) pX = 0.0; - else if (pX > 1.0) pX = 1.0; - var newYs = [ point.y, pX ]; - newYs[0] = this.area.h * newYs[0] + this.area.y; - newYs[1] = this.area.h * newYs[1] + this.area.y; + var newYs; + if (stackedGraph) { + lastY = baseline[point.canvasx]; + if (lastY === undefined) lastY = axisY; + baseline[point.canvasx] = point.canvasy; + newYs = [ point.canvasy, lastY ]; + } else { + newYs = [ point.canvasy, axisY ]; + } if (!isNaN(prevX)) { ctx.moveTo(prevX, prevYs[0]); ctx.lineTo(point.canvasx, newYs[0]); @@ -573,8 +573,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { ctx.lineTo(prevX, prevYs[1]); ctx.closePath(); } - prevYs[0] = newYs[0]; - prevYs[1] = newYs[1]; + prevYs = newYs; prevX = point.canvasx; } } diff --git a/dygraph.js b/dygraph.js index eff107c..51578bb 100644 --- a/dygraph.js +++ b/dygraph.js @@ -908,9 +908,7 @@ Dygraph.prototype.mouseMove_ = function(event) { // Extract the points we've selected this.selPoints_ = []; - var cumulative_sum = 0; // used only if we have a stackedGraph. var l = points.length; - var isStacked = this.attr_("stackedGraph"); if (!this.attr_("stackedGraph")) { for (var i = 0; i < l; i++) { if (points[i].xval == lastx) { @@ -918,11 +916,11 @@ Dygraph.prototype.mouseMove_ = function(event) { } } } else { - // Stacked points need to be examined in reverse order. + // Need to 'unstack' points starting from the bottom + var cumulative_sum = 0; for (var i = l - 1; i >= 0; i--) { if (points[i].xval == lastx) { - // Clone the point, since we need to 'unstack' it below. - var p = {}; + var p = {}; // Clone the point since we modify it for (var k in points[i]) { p[k] = points[i][k]; } @@ -931,6 +929,7 @@ Dygraph.prototype.mouseMove_ = function(event) { this.selPoints_.push(p); } } + this.selPoints_.reverse(); } if (this.attr_("highlightCallback")) { @@ -1519,12 +1518,14 @@ Dygraph.prototype.drawGraph_ = function(data) { var connectSeparatedPoints = this.attr_('connectSeparatedPoints'); - // For stacked series. - var cumulative_y = []; - var stacked_datasets = []; + // Loop over the fields (series). Go from the last to the first, + // because if they're stacked that's how we accumulate the values. - // Loop over all fields in the dataset - for (var i = 1; i < data[0].length; i++) { + var cumulative_y = []; // For stacked series. + var datasets = []; + + // Loop over all fields and create datasets + for (var i = data[0].length - 1; i >= 1; i--) { if (!this.visibility()[i - 1]) continue; var series = []; @@ -1575,38 +1576,35 @@ Dygraph.prototype.drawGraph_ = function(data) { if (maxY === null || thisMaxY > maxY) maxY = thisMaxY; if (bars) { - var vals = []; - for (var j=0; j maxY) - maxY = cumulative_y[series[j][0]]; + if (!maxY || cumulative_y[x] > maxY) + maxY = cumulative_y[x]; } - stacked_datasets.push([this.attr_("labels")[i], vals]); - //this.layout_.addDataset(this.attr_("labels")[i], vals); - } else { - this.layout_.addDataset(this.attr_("labels")[i], series); } + + datasets[i] = series; } - if (stacked_datasets.length > 0) { - for (var i = (stacked_datasets.length - 1); i >= 0; i--) { - this.layout_.addDataset(stacked_datasets[i][0], stacked_datasets[i][1]); - } + for (var i = 1; i < datasets.length; i++) { + this.layout_.addDataset(this.attr_("labels")[i], datasets[i]); } // Use some heuristics to come up with a good maxY value, unless it's been diff --git a/tests/fillGraph.html b/tests/fillGraph.html index b233a39..828c07f 100644 --- a/tests/fillGraph.html +++ b/tests/fillGraph.html @@ -33,7 +33,7 @@ ); new Dygraph( - document.getElementById("div_g3"), + document.getElementById("div_g2"), function() { var ret = "X,Y1,Y2\n"; for (var i = 0; i < 100; i++) { @@ -46,7 +46,7 @@ ); new Dygraph( - document.getElementById("div_g2"), + document.getElementById("div_g3"), function() { var ret = "X,Y1,Y2\n"; for (var i = 0; i < 100; i++) { diff --git a/tests/stacked.html b/tests/stacked.html index ac2f86f..8973347 100644 --- a/tests/stacked.html +++ b/tests/stacked.html @@ -11,9 +11,15 @@

Simple graph:

-
+

Stacked graph:

-
+
+

Simple graph with missing data:

+
+

Stacked graph with missing data:

+
+

Stacked graph with many series:

+
-- 2.7.4