From: Adil Date: Wed, 12 Dec 2012 18:24:30 +0000 (-0500) Subject: Merge remote-tracking branch 'upstream/master' into rgbcolor_change X-Git-Tag: v1.0.0~142^2~3 X-Git-Url: https://adrianiainlam.tk/git/?a=commitdiff_plain;h=21ebe38bb1eeae3a7fd73335a411bfd81c66d985;hp=e9a32469f3fb0fa4438993d5c46e046b07488bec;p=dygraphs.git Merge remote-tracking branch 'upstream/master' into rgbcolor_change --- diff --git a/Makefile b/Makefile index b5e812d..007468e 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,22 @@ # # Dean Wampler March 22, 2010 -all: test generate-combined +all: test generate-combined generate-documentation clean: + @echo cleaning... @cp .dygraph-combined-clean.js dygraph-combined.js + rm docs/options.html generate-combined: @echo Generating dygraph-combined.js @./generate-combined.sh +generate-documentation: + @echo Generating docs/options.html + @./generate-documentation.py > docs/options.html + @chmod a+r docs/options.html + gwt: generate-gwt generate-gwt: diff --git a/auto_tests/misc/local.html b/auto_tests/misc/local.html index 0b4dd62..b7d3a0d 100644 --- a/auto_tests/misc/local.html +++ b/auto_tests/misc/local.html @@ -31,6 +31,7 @@ + diff --git a/auto_tests/tests/multiple_axes-old.js b/auto_tests/tests/multiple_axes-old.js new file mode 100644 index 0000000..a0dc8ac --- /dev/null +++ b/auto_tests/tests/multiple_axes-old.js @@ -0,0 +1,311 @@ +/** + * @fileoverview Tests involving multiple y-axes. + * + * @author danvdk@gmail.com (Dan Vanderkam) + */ + +var MultipleAxesOldTestCase = TestCase("multiple-axes-old-tests"); + +MultipleAxesOldTestCase.prototype.setUp = function() { + document.body.innerHTML = "
"; +}; + +MultipleAxesOldTestCase.getData = function() { + var data = []; + for (var i = 1; i <= 100; i++) { + var m = "01", d = i; + if (d > 31) { m = "02"; d -= 31; } + if (m == "02" && d > 28) { m = "03"; d -= 28; } + if (m == "03" && d > 31) { m = "04"; d -= 31; } + if (d < 10) d = "0" + d; + // two series, one with range 1-100, one with range 1-2M + data.push([new Date("2010/" + m + "/" + d), + i, + 100 - i, + 1e6 * (1 + i * (100 - i) / (50 * 50)), + 1e6 * (2 - i * (100 - i) / (50 * 50))]); + } + return data; +}; + +MultipleAxesOldTestCase.prototype.testOldBasicMultipleAxes = function() { + var data = MultipleAxesTestCase.getData(); + + var g = new Dygraph( + document.getElementById("graph"), + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + width: 640, + height: 350, + 'Y3': { + axis: { + // set axis-related properties here + labelsKMB: true + } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + } + } + ); + + assertEquals(["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"], getYLabelsForAxis("1")); + assertEquals(["900K", "1.01M", "1.12M", "1.23M", "1.34M", "1.45M", "1.55M", "1.66M", "1.77M", "1.88M", "1.99M"], getYLabelsForAxis("2")); +}; + +MultipleAxesOldTestCase.prototype.testOldNewStylePerAxisOptions = function() { + var data = MultipleAxesTestCase.getData(); + + var g = new Dygraph( + document.getElementById("graph"), + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + width: 640, + height: 350, + 'Y3': { + axis: { } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + }, + axes: { + y2: { + labelsKMB: true + } + } + } + ); + + assertEquals(["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"], getYLabelsForAxis("1")); + assertEquals(["900K", "1.01M", "1.12M", "1.23M", "1.34M", "1.45M", "1.55M", "1.66M", "1.77M", "1.88M", "1.99M"], getYLabelsForAxis("2")); +}; + +MultipleAxesOldTestCase.prototype.testOldMultiAxisLayout = function() { + var data = MultipleAxesTestCase.getData(); + + var el = document.getElementById("graph"); + + var g = new Dygraph( + el, + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + width: 640, + height: 350, + 'Y3': { + axis: { } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + }, + axes: { + y2: { + labelsKMB: true + } + } + } + ); + + // Test that all elements are inside the bounds of the graph, set above + var innerDiv = el.firstChild; + for (var child = innerDiv.firstChild; child != null; child = child.nextSibling) { + assertTrue(child.offsetLeft >= 0); + assertTrue((child.offsetLeft + child.offsetWidth) <= 640); + assertTrue(child.offsetTop >= 0); + // TODO(flooey@google.com): Text sometimes linebreaks, + // causing the labels to appear outside the allocated area. + // assertTrue((child.offsetTop + child.offsetHeight) <= 350); + } +}; + +MultipleAxesOldTestCase.prototype.testOldTwoAxisVisibility = function() { + var data = []; + data.push([0,0,0]); + data.push([1,2,2000]); + data.push([2,4,1000]); + + var g = new Dygraph( + document.getElementById("graph"), + data, + { + labels: [ 'X', 'bar', 'zot' ], + 'zot': { + axis: { + labelsKMB: true + } + } + } + ); + + assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); + assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); + + g.setVisibility(0, false); + + assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); + assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); + + g.setVisibility(0, true); + g.setVisibility(1, false); + + assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); + assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); +}; + +// verifies that all four chart labels (title, x-, y-, y2-axis label) can be +// used simultaneously. +MultipleAxesOldTestCase.prototype.testOldMultiChartLabels = function() { + var data = MultipleAxesTestCase.getData(); + + var el = document.getElementById("graph"); + el.style.border = '1px solid black'; + el.style.marginLeft = '200px'; + el.style.marginTop = '200px'; + + var g = new Dygraph( + el, + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + width: 640, + height: 350, + 'Y3': { + axis: { } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + }, + xlabel: 'x-axis', + ylabel: 'y-axis', + y2label: 'y2-axis', + title: 'Chart title' + } + ); + + assertEquals(["Chart title", "x-axis", "y-axis", "y2-axis"], + getClassTexts("dygraph-label")); + assertEquals(["Chart title"], getClassTexts("dygraph-title")); + assertEquals(["x-axis"], getClassTexts("dygraph-xlabel")); + assertEquals(["y-axis"], getClassTexts("dygraph-ylabel")); + assertEquals(["y2-axis"], getClassTexts("dygraph-y2label")); + + // TODO(danvk): check relative positioning here: title on top, y left of y2. +}; + +// Check that a chart w/o a secondary y-axis will not get a y2label, even if one +// is specified. +MultipleAxesOldTestCase.prototype.testOldNoY2LabelWithoutSecondaryAxis = function() { + var g = new Dygraph( + document.getElementById("graph"), + MultipleAxesTestCase.getData(), + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + width: 640, + height: 350, + xlabel: 'x-axis', + ylabel: 'y-axis', + y2label: 'y2-axis', + title: 'Chart title' + } + ); + + assertEquals(["Chart title", "x-axis", "y-axis"], + getClassTexts("dygraph-label")); + assertEquals(["Chart title"], getClassTexts("dygraph-title")); + assertEquals(["x-axis"], getClassTexts("dygraph-xlabel")); + assertEquals(["y-axis"], getClassTexts("dygraph-ylabel")); + assertEquals([], getClassTexts("dygraph-y2label")); +}; + +MultipleAxesOldTestCase.prototype.testOldValueRangePerAxisOptions = function() { + var data = MultipleAxesTestCase.getData(); + + g = new Dygraph( + document.getElementById("graph"), + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + 'Y3': { + axis: { + } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + }, + axes: { + y: { + valueRange: [40, 70] + }, + y2: { + // set axis-related properties here + labelsKMB: true + } + }, + ylabel: 'Primary y-axis', + y2label: 'Secondary y-axis', + yAxisLabelWidth: 60 + } + ); + assertEquals(["40", "45", "50", "55", "60", "65"], getYLabelsForAxis("1")); + assertEquals(["900K","1.1M","1.3M","1.5M","1.7M","1.9M"], getYLabelsForAxis("2")); + + g.updateOptions( + { + axes: { + y: { + valueRange: [40, 80] + }, + y2: { + valueRange: [1e6, 1.2e6] + } + } + } + ); + assertEquals(["40", "45", "50", "55", "60", "65", "70", "75"], getYLabelsForAxis("1")); + assertEquals(["1M", "1.02M", "1.05M", "1.08M", "1.1M", "1.13M", "1.15M", "1.18M"], getYLabelsForAxis("2")); +}; + +MultipleAxesOldTestCase.prototype.testOldDrawPointCallback = function() { + var data = MultipleAxesTestCase.getData(); + + var results = { y : {}, y2 : {}}; + var firstCallback = function(g, seriesName, ctx, canvasx, canvasy, color, radius) { + results.y[seriesName] = 1; + Dygraph.Circles.DEFAULT(g, seriesName, ctx, canvasx, canvasy, color, radius); + + }; + var secondCallback = function(g, seriesName, ctx, canvasx, canvasy, color, radius) { + results.y2[seriesName] = 1; + Dygraph.Circles.TRIANGLE(g, seriesName, ctx, canvasx, canvasy, color, radius); + }; + + g = new Dygraph( + document.getElementById("graph"), + data, + { + labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], + drawPoints : true, + pointSize : 3, + 'Y3': { + axis: { + } + }, + 'Y4': { + axis: 'Y3' // use the same y-axis as series Y3 + }, + axes: { + y2: { + drawPointCallback: secondCallback + } + }, + drawPointCallback: firstCallback + } + ); + + assertEquals(1, results.y["Y1"]); + assertEquals(1, results.y["Y2"]); + assertEquals(1, results.y2["Y3"]); + assertEquals(1, results.y2["Y4"]); +}; diff --git a/auto_tests/tests/multiple_axes.js b/auto_tests/tests/multiple_axes.js index d08ff50..39ec8b1 100644 --- a/auto_tests/tests/multiple_axes.js +++ b/auto_tests/tests/multiple_axes.js @@ -63,40 +63,17 @@ MultipleAxesTestCase.prototype.testBasicMultipleAxes = function() { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], width: 640, height: 350, - 'Y3': { - axis: { - // set axis-related properties here - labelsKMB: true + series : { + 'Y3': { + axis: 'y2' + }, + 'Y4': { + axis: 'y2' } }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 - } - } - ); - - assertEquals(["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"], getYLabelsForAxis("1")); - assertEquals(["900K", "1.01M", "1.12M", "1.23M", "1.34M", "1.45M", "1.55M", "1.66M", "1.77M", "1.88M", "1.99M"], getYLabelsForAxis("2")); -}; - -MultipleAxesTestCase.prototype.testNewStylePerAxisOptions = function() { - var data = MultipleAxesTestCase.getData(); - - var g = new Dygraph( - document.getElementById("graph"), - data, - { - labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], - width: 640, - height: 350, - 'Y3': { - axis: { } - }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 - }, - axes: { - y2: { + axes : { + y2 : { + // set axis-related properties here labelsKMB: true } } @@ -107,44 +84,6 @@ MultipleAxesTestCase.prototype.testNewStylePerAxisOptions = function() { assertEquals(["900K", "1.01M", "1.12M", "1.23M", "1.34M", "1.45M", "1.55M", "1.66M", "1.77M", "1.88M", "1.99M"], getYLabelsForAxis("2")); }; -MultipleAxesTestCase.prototype.testMultiAxisLayout = function() { - var data = MultipleAxesTestCase.getData(); - - var el = document.getElementById("graph"); - - var g = new Dygraph( - el, - data, - { - labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], - width: 640, - height: 350, - 'Y3': { - axis: { } - }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 - }, - axes: { - y2: { - labelsKMB: true - } - } - } - ); - - // Test that all elements are inside the bounds of the graph, set above - var innerDiv = el.firstChild; - for (var child = innerDiv.firstChild; child != null; child = child.nextSibling) { - assertTrue(child.offsetLeft >= 0); - assertTrue((child.offsetLeft + child.offsetWidth) <= 640); - assertTrue(child.offsetTop >= 0); - // TODO(flooey@google.com): Text sometimes linebreaks, - // causing the labels to appear outside the allocated area. - // assertTrue((child.offsetTop + child.offsetHeight) <= 350); - } -}; - MultipleAxesTestCase.prototype.testTwoAxisVisibility = function() { var data = []; data.push([0,0,0]); @@ -156,8 +95,13 @@ MultipleAxesTestCase.prototype.testTwoAxisVisibility = function() { data, { labels: [ 'X', 'bar', 'zot' ], - 'zot': { - axis: { + series : { + zot : { + axis : 'y2' + } + }, + axes : { + y2: { labelsKMB: true } } @@ -196,11 +140,13 @@ MultipleAxesTestCase.prototype.testMultiChartLabels = function() { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], width: 640, height: 350, - 'Y3': { - axis: { } - }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 + series : { + 'Y3': { + axis: 'y2' + }, + 'Y4': { + axis: 'y2' + } }, xlabel: 'x-axis', ylabel: 'y-axis', @@ -252,13 +198,14 @@ MultipleAxesTestCase.prototype.testValueRangePerAxisOptions = function() { data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], - 'Y3': { - axis: { + series : { + 'Y3': { + axis: 'y2' + }, + 'Y4': { + axis: 'y2' } }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 - }, axes: { y: { valueRange: [40, 70] @@ -313,13 +260,14 @@ MultipleAxesTestCase.prototype.testDrawPointCallback = function() { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], drawPoints : true, pointSize : 3, - 'Y3': { - axis: { + series : { + 'Y3': { + axis: 'y2' + }, + 'Y4': { + axis: 'y2' } }, - 'Y4': { - axis: 'Y3' // use the same y-axis as series Y3 - }, axes: { y2: { drawPointCallback: secondCallback diff --git a/auto_tests/tests/per_series.js b/auto_tests/tests/per_series.js index 4831ded..ab52add 100644 --- a/auto_tests/tests/per_series.js +++ b/auto_tests/tests/per_series.js @@ -1,5 +1,5 @@ /** - * @fileoverview FILL THIS IN + * @fileoverview Tests for per-series options. * * @author danvk@google.com (Dan Vanderkam) */ @@ -12,7 +12,6 @@ perSeriesTestCase.prototype.setUp = function() { perSeriesTestCase.prototype.tearDown = function() { }; - perSeriesTestCase.prototype.testPerSeriesFill = function() { var opts = { width: 480, @@ -48,3 +47,120 @@ perSeriesTestCase.prototype.testPerSeriesFill = function() { assertEquals([255,0,0,38], sampler.colorAtCoordinate(6.5, 0.5)); }; +perSeriesTestCase.prototype.testOldStyleSeries = function() { + var opts = { + pointSize : 5, + Y: { pointSize : 4 }, + }; + var graph = document.getElementById("graph"); + var data = "X,Y,Z\n1,0,0\n"; + g = new Dygraph(graph, data, opts); + + assertEquals(5, g.getOption("pointSize")); + assertEquals(4, g.getOption("pointSize", "Y")); + assertEquals(5, g.getOption("pointSize", "Z")); +}; + +perSeriesTestCase.prototype.testNewStyleSeries = function() { + var opts = { + pointSize : 5, + series : { + Y: { pointSize : 4 } + }, + }; + var graph = document.getElementById("graph"); + var data = "X,Y,Z\n1,0,0\n"; + g = new Dygraph(graph, data, opts); + + assertEquals(5, g.getOption("pointSize")); + assertEquals(4, g.getOption("pointSize", "Y")); + assertEquals(5, g.getOption("pointSize", "Z")); +}; + +perSeriesTestCase.prototype.testNewStyleSeriesTrumpsOldStyle = function() { + var opts = { + pointSize : 5, + Z : { pointSize : 6 }, + series : { + Y: { pointSize : 4 } + }, + }; + var graph = document.getElementById("graph"); + var data = "X,Y,Z\n1,0,0\n"; + g = new Dygraph(graph, data, opts); + + assertEquals(5, g.getOption("pointSize")); + assertEquals(4, g.getOption("pointSize", "Y")); + assertEquals(5, g.getOption("pointSize", "Z")); + + // Erase the series object, and Z will become visible again. + g.updateOptions({ series : undefined }); + assertEquals(5, g.getOption("pointSize")); + assertEquals(6, g.getOption("pointSize", "Z")); + assertEquals(5, g.getOption("pointSize", "Y")); +}; + +// TODO(konigsberg): move to multiple_axes.js +perSeriesTestCase.prototype.testAxisInNewSeries = function() { + var opts = { + series : { + D : { axis : 'y2' }, + C : { axis : 1 }, + B : { axis : 0 }, + E : { axis : 'y' } + } + }; + var graph = document.getElementById("graph"); + var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; + g = new Dygraph(graph, data, opts); + + assertEquals(["A", "B", "E"], g.attributes_.seriesForAxis(0)); + assertEquals(["C", "D"], g.attributes_.seriesForAxis(1)); +}; + +// TODO(konigsberg): move to multiple_axes.js +perSeriesTestCase.prototype.testAxisInNewSeries_withAxes = function() { + var opts = { + series : { + D : { axis : 'y2' }, + C : { axis : 1 }, + B : { axis : 0 }, + E : { axis : 'y' } + }, + axes : { + y : { pointSize : 7 }, + y2 : { pointSize : 6 } + } + }; + var graph = document.getElementById("graph"); + var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; + g = new Dygraph(graph, data, opts); + + assertEquals(["A", "B", "E"], g.attributes_.seriesForAxis(0)); + assertEquals(["C", "D"], g.attributes_.seriesForAxis(1)); + + assertEquals(1.5, g.getOption("pointSize")); + assertEquals(7, g.getOption("pointSize", "A")); + assertEquals(7, g.getOption("pointSize", "B")); + assertEquals(6, g.getOption("pointSize", "C")); + assertEquals(6, g.getOption("pointSize", "D")); + assertEquals(7, g.getOption("pointSize", "E")); +}; + +// TODO(konigsberg): move to multiple_axes.js +perSeriesTestCase.prototype.testOldAxisSpecInNewSeriesThrows = function() { + var opts = { + series : { + D : { axis : {} }, + }, + }; + var graph = document.getElementById("graph"); + var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; + try { + new Dygraph(graph, data, opts); + } catch(e) { + assertEquals( + "Using objects for axis specification is not supported inside the 'series' option.", + e); + } +} diff --git a/dygraph-options-reference.js b/dygraph-options-reference.js index 5aa7f05..9110879 100644 --- a/dygraph-options-reference.js +++ b/dygraph-options-reference.js @@ -759,6 +759,12 @@ Dygraph.OPTIONS_REFERENCE = // "labels": ["Data Line display"], "type": "array or function", "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." + }, + "series": { + "default": "null", + "labels": ["Series"], + "type": "Object", + "description": "Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series. When this option is missing, it falls back on the old-style of per-series options comingled with global options." } } ; // @@ -787,6 +793,7 @@ Dygraph.OPTIONS_REFERENCE = // 'Legend', 'Overall display', 'Rolling Averages', + 'Series', 'Value display/formatting', 'Zooming', 'Debugging', diff --git a/dygraph-options.js b/dygraph-options.js index 9a4f6bf..815a893 100644 --- a/dygraph-options.js +++ b/dygraph-options.js @@ -9,11 +9,11 @@ /* * Interesting member variables: * dygraph_ - the graph. - * global - global attributes (common among all graphs, AIUI) + * global_ - global attributes (common among all graphs, AIUI) * user - attributes set by the user - * axes - map of options specific to the axis. - * series - { seriesName -> { idx, yAxis, options } - * labels - used as mapping from index to series name. + * axes_ - array of axis index to { series : [ series names ] , options : { axis-specific options. } + * series_ - { seriesName -> { idx, yAxis, options }} + * labels_ - used as mapping from index to series name. */ /** @@ -46,6 +46,43 @@ var DygraphOptions = function(dygraph) { this.reparseSeries(); }; +/* + * Not optimal, but does the trick when you're only using two axes. + * If we move to more axes, this can just become a function. + */ +DygraphOptions.AXIS_STRING_MAPPINGS_ = { + 'y' : 0, + 'Y' : 0, + 'y1' : 0, + 'Y1' : 0, + 'y2' : 1, + 'Y2' : 1 +}; + +DygraphOptions.axisToIndex_ = function(axis) { + if (typeof(axis) == "string") { + if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) { + return DygraphOptions.AXIS_STRING_MAPPINGS_[axis]; + } + throw "Unknown axis : " + axis; + } + if (typeof(axis) == "number") { + if (axis === 0 || axis === 1) { + return axis; + } + throw "Dygraphs only supports two y-axes, indexed from 0-1."; + } + if (typeof(axis) == "object") { + throw "Using objects for axis specification " + + "is not supported inside the 'series' option."; + } + if (axis) { + throw "Unknown axis : " + axis; + } + // No axis specification means axis 0. + return 0; +}; + /** * Reparses options that are all related to series. This typically occurs when * options are either updated, or source data has been made avaialble. @@ -55,55 +92,98 @@ var DygraphOptions = function(dygraph) { DygraphOptions.prototype.reparseSeries = function() { this.labels = this.get("labels").slice(1); - this.axes_ = [ {} ]; // Always one axis at least. + this.axes_ = [ { series : [], options : {}} ]; // Always one axis at least. this.series_ = {}; - var axisId = 0; // 0-offset; there's always one. - // Go through once, add all the series, and for those with {} axis options, add a new axis. - for (var idx = 0; idx < this.labels.length; idx++) { - var seriesName = this.labels[idx]; + // Traditionally, per-series options were specified right up there with the options. For instance + // { + // labels: [ "X", "foo", "bar" ], + // pointSize: 3, + // foo : {}, // options for foo + // bar : {} // options for bar + // } + // + // Moving forward, series really should be specified in the series element, separating them. + // like so: + // + // { + // labels: [ "X", "foo", "bar" ], + // pointSize: 3, + // series : { + // foo : {}, // options for foo + // bar : {} // options for bar + // } + // } + // + // So, if series is found, it's expected to contain per-series data, otherwise we fall + // back. + var oldStyleSeries = !this.user_["series"]; + + if (oldStyleSeries) { + var axisId = 0; // 0-offset; there's always one. + // Go through once, add all the series, and for those with {} axis options, add a new axis. + for (var idx = 0; idx < this.labels.length; idx++) { + var seriesName = this.labels[idx]; + + var optionsForSeries = this.user_[seriesName] || {}; + + var yAxis = 0; + var axis = optionsForSeries["axis"]; + if (typeof(axis) == 'object') { + yAxis = ++axisId; + this.axes_[yAxis] = { series : [ seriesName ], options : axis }; + } - var optionsForSeries = this.user_[seriesName] || {}; - var yAxis = 0; + // Associate series without axis options with axis 0. + if (!axis) { // undefined + this.axes_[0].series.push(seriesName); + } - var axis = optionsForSeries["axis"]; - if (typeof(axis) == 'object') { - yAxis = ++axisId; - this.axes_[yAxis] = axis; + this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries }; } - this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries }; - } - - // Go through one more time and assign series to an axis defined by another - // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } } - for (var idx = 0; idx < this.labels.length; idx++) { - var seriesName = this.labels[idx]; - var optionsForSeries = this.series_[seriesName]["options"]; - var axis = optionsForSeries["axis"]; - - if (typeof(axis) == 'string') { - if (!this.series_.hasOwnProperty(axis)) { - this.dygraph_.error("Series " + seriesName + " wants to share a y-axis with " + - "series " + axis + ", which does not define its own axis."); - return null; + + // Go through one more time and assign series to an axis defined by another + // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } } + for (var idx = 0; idx < this.labels.length; idx++) { + var seriesName = this.labels[idx]; + var optionsForSeries = this.series_[seriesName]["options"]; + var axis = optionsForSeries["axis"]; + + if (typeof(axis) == 'string') { + if (!this.series_.hasOwnProperty(axis)) { + this.dygraph_.error("Series " + seriesName + " wants to share a y-axis with " + + "series " + axis + ", which does not define its own axis."); + return null; + } + var yAxis = this.series_[axis].yAxis; + this.series_[seriesName].yAxis = yAxis; + this.axes_[yAxis].series.push(seriesName); } - this.series_[seriesName].yAxis = this.series_[axis].yAxis; } - } + } else { + for (var idx = 0; idx < this.labels.length; idx++) { + var seriesName = this.labels[idx]; + var optionsForSeries = this.user_.series[seriesName] || {}; + var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]); - // This doesn't support reading from the 'x' axis, only 'y' and 'y2. - // Read from the global "axes" option. - if (this.user_.hasOwnProperty("axes")) { - var axis_opts = this.user_.axes; + this.series_[seriesName] = { + idx: idx, + yAxis: yAxis, + options : optionsForSeries }; - if (axis_opts.hasOwnProperty("y")) { - Dygraph.update(this.axes_[0], axis_opts.y); + if (!this.axes_[yAxis]) { + this.axes_[yAxis] = { series : [ seriesName ], options : {} }; + } else { + this.axes_[yAxis].series.push(seriesName); + } } + } - if (axis_opts.hasOwnProperty("y2")) { - this.axes_[1] = this.axes_[1] || {}; - Dygraph.update(this.axes_[1], axis_opts.y2); - } + // This doesn't support reading from the 'x' axis, only 'y' and 'y2. + var axis_opts = this.user_["axes"] || {}; + Dygraph.update(this.axes_[0].options, axis_opts["y"] || {}); + if (this.axes_.length > 1) { + Dygraph.update(this.axes_[1].options, axis_opts["y2"] || {}); } }; @@ -139,7 +219,7 @@ DygraphOptions.prototype.getForAxis = function(name, axis) { axisIdx = (axis == "y2") ? 1 : 0; } - var axisOptions = this.axes_[axisIdx]; + var axisOptions = this.axes_[axisIdx].options; if (axisOptions.hasOwnProperty(name)) { return axisOptions[name]; } @@ -177,3 +257,47 @@ DygraphOptions.prototype.getForSeries = function(name, series) { return this.getForAxis(name, seriesObj["yAxis"]); }; +/** + * Returns the number of y-axes on the chart. + * @return {Number} the number of axes. + */ +DygraphOptions.prototype.numAxes = function() { + return this.axes_.length; +}; + +/** + * Return the y-axis for a given series, specified by name. + */ +DygraphOptions.prototype.axisForSeries = function(seriesName) { + return this.series_[seriesName].yAxis; +}; + +/** + * Returns the options for the specified axis. + */ +DygraphOptions.prototype.axisOptions = function(yAxis) { + return this.axes_[yAxis].options; +}; + +/** + * Return the series associated with an axis. + */ +DygraphOptions.prototype.seriesForAxis = function(yAxis) { + return this.axes_[yAxis].series; +}; + +/** + * Return the list of all series, in their columnar order. + */ +DygraphOptions.prototype.seriesNames = function() { + return this.labels_; +}; + +/* Are we using this? */ +/** + * Return the index of the specified series. + * @param {string} series the series name. + */ +DygraphOptions.prototype.indexOfSeries = function(series) { + return this.series_[series].idx; +}; diff --git a/dygraph.js b/dygraph.js index 97da32a..72a459f 100644 --- a/dygraph.js +++ b/dygraph.js @@ -577,7 +577,6 @@ Dygraph.prototype.attr_ = function(name, seriesName) { Dygraph.OPTIONS_REFERENCE[name] = true; } // - return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name); }; @@ -1141,7 +1140,7 @@ Dygraph.prototype.getPropertiesForSeries = function(series_name) { column: idx, visible: this.visibility()[idx - 1], color: this.colorsMap_[series_name], - axis: 1 + this.seriesToAxisMap_[series_name] + axis: 1 + this.attributes_.axisForSeries(series_name) }; }; @@ -2424,9 +2423,8 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) { * currently being displayed. This includes things like the number of axes and * the style of the axes. It does not include the range of each axis and its * tick marks. - * This fills in this.axes_ and this.seriesToAxisMap_. + * This fills in this.axes_. * axes_ = [ { options } ] - * seriesToAxisMap_ = { seriesName: 0, seriesName2: 1, ... } * indices are into the axes_ array. */ Dygraph.prototype.computeYAxes_ = function() { @@ -2440,70 +2438,16 @@ Dygraph.prototype.computeYAxes_ = function() { } } - this.axes_ = [{ yAxisId : 0, g : this }]; // always have at least one y-axis. - this.seriesToAxisMap_ = {}; - - // Get a list of series names. - var labels = this.attr_("labels"); - var series = {}; - for (i = 1; i < labels.length; i++) series[labels[i]] = (i - 1); - - // all options which could be applied per-axis: - var axisOptions = [ - 'includeZero', - 'valueRange', - 'labelsKMB', - 'labelsKMG2', - 'pixelsPerYLabel', - 'yAxisLabelWidth', - 'axisLabelFontSize', - 'axisTickSize', - 'logscale' - ]; - - // Copy global axis options over to the first axis. - for (i = 0; i < axisOptions.length; i++) { - var k = axisOptions[i]; - v = this.attr_(k); - if (v) this.axes_[0][k] = v; - } - + // this.axes_ doesn't match this.attributes_.axes_.options. It's used for + // data computation as well as options storage. // Go through once and add all the axes. - for (seriesName in series) { - if (!series.hasOwnProperty(seriesName)) continue; - axis = this.attr_("axis", seriesName); - if (axis === null) { - this.seriesToAxisMap_[seriesName] = 0; - continue; - } - if (typeof(axis) == 'object') { - // Add a new axis, making a copy of its per-axis options. - opts = {}; - Dygraph.update(opts, this.axes_[0]); - Dygraph.update(opts, { valueRange: null }); // shouldn't inherit this. - var yAxisId = this.axes_.length; - opts.yAxisId = yAxisId; - opts.g = this; - Dygraph.update(opts, axis); - this.axes_.push(opts); - this.seriesToAxisMap_[seriesName] = yAxisId; - } - } - - // Go through one more time and assign series to an axis defined by another - // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } } - for (seriesName in series) { - if (!series.hasOwnProperty(seriesName)) continue; - axis = this.attr_("axis", seriesName); - if (typeof(axis) == 'string') { - if (!this.seriesToAxisMap_.hasOwnProperty(axis)) { - this.error("Series " + seriesName + " wants to share a y-axis with " + - "series " + axis + ", which does not define its own axis."); - return null; - } - var idx = this.seriesToAxisMap_[axis]; - this.seriesToAxisMap_[seriesName] = idx; - } + this.axes_ = []; + + for (axis = 0; axis < this.attributes_.numAxes(); axis++) { + // Add a new axis, making a copy of its per-axis options. + opts = { g : this }; + Dygraph.update(opts, this.attributes_.axisOptions(axis)); + this.axes_[axis] = opts; } if (valueWindows !== undefined) { @@ -2526,7 +2470,6 @@ Dygraph.prototype.computeYAxes_ = function() { } } } - }; /** @@ -2534,13 +2477,7 @@ Dygraph.prototype.computeYAxes_ = function() { * @return {Number} the number of axes. */ Dygraph.prototype.numAxes = function() { - var last_axis = 0; - for (var series in this.seriesToAxisMap_) { - if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue; - var idx = this.seriesToAxisMap_[series]; - if (idx > last_axis) last_axis = idx; - } - return 1 + last_axis; + return this.attributes_.numAxes(); }; /** @@ -2552,7 +2489,7 @@ Dygraph.prototype.numAxes = function() { */ Dygraph.prototype.axisPropertiesForSeries = function(series) { // TODO(danvk): handle errors. - return this.axes_[this.seriesToAxisMap_[series]]; + return this.axes_[this.attributes_.axisForSeries(series)]; }; /** @@ -2562,25 +2499,20 @@ Dygraph.prototype.axisPropertiesForSeries = function(series) { * This fills in the valueRange and ticks fields in each entry of this.axes_. */ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { - // Build a map from axis number -> [list of series names] - var seriesForAxis = [], series; - for (series in this.seriesToAxisMap_) { - if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue; - var idx = this.seriesToAxisMap_[series]; - while (seriesForAxis.length <= idx) seriesForAxis.push([]); - seriesForAxis[idx].push(series); - } + var series; + var numAxes = this.attributes_.numAxes(); // Compute extreme values, a span and tick marks for each axis. - for (var i = 0; i < this.axes_.length; i++) { + for (var i = 0; i < numAxes; i++) { var axis = this.axes_[i]; - if (!seriesForAxis[i]) { + series = this.attributes_.seriesForAxis(i); + + if (series.length == 0) { // If no series are defined or visible then use a reasonable default axis.extremeRange = [0, 1]; } else { // Calculate the extremes of extremes. - series = seriesForAxis[i]; var minY = Infinity; // extremes[series[0]][0]; var maxY = -Infinity; // extremes[series[0]][1]; var extremeMinY, extremeMaxY; @@ -3364,6 +3296,8 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { Dygraph.updateDeep(this.user_attrs_, attrs); + this.attributes_.reparseSeries(); + if (file) { this.file_ = file; if (!block_redraw) this.start_(); diff --git a/generate-combined.sh b/generate-combined.sh index 855313e..40c8019 100755 --- a/generate-combined.sh +++ b/generate-combined.sh @@ -6,6 +6,11 @@ # This list needs to be kept in sync w/ the one in dygraph-dev.js # and the one in jsTestDriver.conf. cat \ +strftime/strftime-min.js \ +rgbcolor/rgbcolor.js \ +stacktrace.js \ +dashed-canvas.js \ +dygraph-options.js \ dygraph-layout.js \ dygraph-canvas.js \ dygraph.js \ @@ -14,9 +19,6 @@ dygraph-gviz.js \ dygraph-interaction-model.js \ dygraph-range-selector.js \ dygraph-tickers.js \ -rgbcolor/rgbcolor.js \ -strftime/strftime-min.js \ -dashed-canvas.js \ plugins/base.js \ plugins/annotations.js \ plugins/axes.js \ diff --git a/tests/custom-circles.html b/tests/custom-circles.html index f2ef094..0e6d62f 100644 --- a/tests/custom-circles.html +++ b/tests/custom-circles.html @@ -31,13 +31,15 @@ drawPoints : true, pointSize : 5, highlightCircleSize: 8, - A : { - drawPointCallback : Dygraph.Circles.TRIANGLE, - drawHighlightPointCallback : Dygraph.Circles.TRIANGLE - }, - B : { - drawPointCallback : Dygraph.Circles.HEXAGON, - drawHighlightPointCallback : Dygraph.Circles.HEXAGON + series : { + A : { + drawPointCallback : Dygraph.Circles.TRIANGLE, + drawHighlightPointCallback : Dygraph.Circles.TRIANGLE + }, + B : { + drawPointCallback : Dygraph.Circles.HEXAGON, + drawHighlightPointCallback : Dygraph.Circles.HEXAGON + } } }); diff --git a/tests/isolated-points.html b/tests/isolated-points.html index f507fda..1c1da06 100644 --- a/tests/isolated-points.html +++ b/tests/isolated-points.html @@ -29,8 +29,10 @@ ], { labels: ["X", "S1", "S2" ], - S1: { - drawGapEdgePoints: true + series: { + S1: { + drawGapEdgePoints: true + } }, pointSize: 4, showRoller: true diff --git a/tests/per-series.html b/tests/per-series.html index 98501ac..84730b1 100644 --- a/tests/per-series.html +++ b/tests/per-series.html @@ -18,6 +18,8 @@

Chart with per-series properties with legend.

+

First graph, using old-style series specification.

+