Merge remote-tracking branch 'upstream/master' into rgbcolor_change
authorAdil <adilflorida@gmail.com>
Wed, 12 Dec 2012 18:24:30 +0000 (13:24 -0500)
committerAdil <adilflorida@gmail.com>
Wed, 12 Dec 2012 18:24:30 +0000 (13:24 -0500)
13 files changed:
Makefile
auto_tests/misc/local.html
auto_tests/tests/multiple_axes-old.js [new file with mode: 0644]
auto_tests/tests/multiple_axes.js
auto_tests/tests/per_series.js
dygraph-options-reference.js
dygraph-options.js
dygraph.js
generate-combined.sh
tests/custom-circles.html
tests/isolated-points.html
tests/per-series.html
tests/two-axes-vr.html

index b5e812d..007468e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,15 +4,22 @@
 #
 # Dean Wampler <dean@deanwampler.com> 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:
index 0b4dd62..b7d3a0d 100644 (file)
@@ -31,6 +31,7 @@
   <script type="text/javascript" src="../tests/missing_points.js"></script>
   <script type="text/javascript" src="../tests/multi_csv.js"></script>
   <script type="text/javascript" src="../tests/multiple_axes.js"></script>
+  <script type="text/javascript" src="../tests/multiple_axes-old.js"></script>
   <script type="text/javascript" src="../tests/no_hours.js"></script>
   <script type="text/javascript" src="../tests/parser.js"></script>
   <script type="text/javascript" src="../tests/pathological_cases.js"></script>
diff --git a/auto_tests/tests/multiple_axes-old.js b/auto_tests/tests/multiple_axes-old.js
new file mode 100644 (file)
index 0000000..a0dc8ac
--- /dev/null
@@ -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 = "<div id='graph'></div>";
+};
+
+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"]);
+};
index d08ff50..39ec8b1 100644 (file)
@@ -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
index 4831ded..ab52add 100644 (file)
@@ -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);
+  }
+}
index 5aa7f05..9110879 100644 (file)
@@ -759,6 +759,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "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."
   }
 }
 ;  // </JSON>
@@ -787,6 +793,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
    'Legend',
    'Overall display',
    'Rolling Averages',
+   'Series',
    'Value display/formatting',
    'Zooming',
    'Debugging',
index 9a4f6bf..815a893 100644 (file)
@@ -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;
+};
index 97da32a..72a459f 100644 (file)
@@ -577,7 +577,6 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
     Dygraph.OPTIONS_REFERENCE[name] = true;
   }
 // </REMOVE_FOR_COMBINED>
-
   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_();
index 855313e..40c8019 100755 (executable)
@@ -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 \
index f2ef094..0e6d62f 100644 (file)
             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
+              }
             }
         });
 
index f507fda..1c1da06 100644 (file)
       ],
       {
         labels: ["X", "S1", "S2" ],
-        S1: {
-          drawGapEdgePoints: true
+        series: {
+          S1: {
+            drawGapEdgePoints: true
+          }
         },
         pointSize: 4,
         showRoller: true
index 98501ac..84730b1 100644 (file)
@@ -18,6 +18,8 @@
     <div id="demodiv"></div>
     <h2>Chart with per-series properties with legend.</h2>
     <div id="demodiv2"></div>
+    <h2>First graph, using old-style series specification.</h2>
+    <div id="demodiv3"></div>
     <script type="text/javascript">
       data = function() {
         var zp = function(x) { if (x < 10) return "0"+x; else return x; };
               data,
               {
                 strokeWidth: 2,
+               series : {
+                  'parabola': {
+                    strokeWidth: 0.0,
+                    drawPoints: true,
+                    pointSize: 4,
+                    highlightCircleSize: 6
+                  },
+                  'line': {
+                    strokeWidth: 1.0,
+                    drawPoints: true,
+                    pointSize: 1.5
+                  },
+                  'sine wave': {
+                    strokeWidth: 3,
+                    highlightCircleSize: 10
+                  },
+                  'sine wave2': {
+                    strokePattern: [10, 2, 5, 2],
+                    strokeWidth: 2,
+                    highlightCircleSize: 3
+                  }
+                }
+              }
+          );
+      g2 = new Dygraph(
+              document.getElementById("demodiv2"),
+              data,
+              {
+                legend: 'always',
+                strokeWidth: 2,
                 'parabola': {
-                  strokeWidth: 0.0,
+                  strokePattern: null,
                   drawPoints: true,
                   pointSize: 4,
                   highlightCircleSize: 6
                 },
                 'line': {
+                  strokePattern: Dygraph.DASHED_LINE,
                   strokeWidth: 1.0,
                   drawPoints: true,
                   pointSize: 1.5
                 },
+                'another line': {
+                  strokePattern: [25, 5]
+                },
                 'sine wave': {
+                  strokePattern: Dygraph.DOTTED_LINE,
                   strokeWidth: 3,
                   highlightCircleSize: 10
                 },
                 'sine wave2': {
-                  strokePattern: [10, 2, 5, 2],
+                  strokePattern: Dygraph.DOT_DASH_LINE,
                   strokeWidth: 2,
                   highlightCircleSize: 3
                 }
               }
           );
-      g2 = new Dygraph(
-              document.getElementById("demodiv2"),
+      g3 = new Dygraph(
+              document.getElementById("demodiv3"),
               data,
               {
-                legend: 'always',
                 strokeWidth: 2,
                 'parabola': {
-                  strokePattern: null,
+                  strokeWidth: 0.0,
                   drawPoints: true,
                   pointSize: 4,
                   highlightCircleSize: 6
                 },
                 'line': {
-                  strokePattern: Dygraph.DASHED_LINE,
                   strokeWidth: 1.0,
                   drawPoints: true,
                   pointSize: 1.5
                 },
-                'another line': {
-                  strokePattern: [25, 5]
-                },
                 'sine wave': {
-                  strokePattern: Dygraph.DOTTED_LINE,
                   strokeWidth: 3,
                   highlightCircleSize: 10
                 },
                 'sine wave2': {
-                  strokePattern: Dygraph.DOT_DASH_LINE,
+                  strokePattern: [10, 2, 5, 2],
                   strokeWidth: 2,
                   highlightCircleSize: 3
                 }
index 5814435..0646b9a 100644 (file)
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            'Y3': {
-              axis: {
+            series : {
+              'Y3': {
+                axis: {
+                }
+              },
+              'Y4': {
+                axis: 'Y3'  // use the same y-axis as series Y3
               }
             },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
-            },
             valueRange: [40, 70],
             axes: {
               y2: {
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            'Y3': {
-              axis: {
+            series : {
+              'Y3': {
+                axis: {
+                }
+              },
+              'Y4': {
+                axis: 'Y3'  // use the same y-axis as series Y3
               }
             },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
-            },
             axes: {
               y: {
                 valueRange: [40, 80]
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            'Y3': {
-              axis: {
+            series : {
+              'Y3': {
+                axis: {
+                }
+              },
+              'Y4': {
+                axis: 'Y3'  // use the same y-axis as series Y3
               }
             },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
-            },
             axes: {
               y: {
                 valueRange: [40, 80]