Merge pull request #475 from danvk/checked-options
authorDan Vanderkam <danvdk@gmail.com>
Sat, 22 Nov 2014 02:13:21 +0000 (21:13 -0500)
committerDan Vanderkam <danvdk@gmail.com>
Sat, 22 Nov 2014 02:13:21 +0000 (21:13 -0500)
Log warnings when using non-existent options

18 files changed:
Makefile
auto_tests/tests/dygraph-options-tests.js
auto_tests/tests/per_series.js
auto_tests/tests/range_selector.js
auto_tests/tests/simple_drawing.js
auto_tests/tests/update_options.js
auto_tests/tests/utils_test.js
dygraph-options-reference.js
dygraph-options.js
dygraph-utils.js
dygraph.js
tests/custom-circles.html
tests/fillGraph.html
tests/per-series.html
tests/plotters.html
tests/steps.html
tests/two-axes.html
tests/value-axis-formatters.html

index 2047c7f..6829ebf 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,7 @@ move-combined: generate-combined
 clean-combined-test: clean
        @echo restoring combined
        git checkout dygraph-dev.js
+       rm dygraph-combined.js.map
 
 lint:
        @./lint.sh
index 792dcdb..7222a21 100644 (file)
@@ -35,7 +35,7 @@ DygraphOptionsTestCase.prototype.testGetSeriesNames = function() {
  * Ensures that even if logscale is set globally, it doesn't impact the
  * x axis.
  */
-DygraphOptionsTestCase.prototype.getLogscaleForX = function() {
+DygraphOptionsTestCase.prototype.testGetLogscaleForX = function() {
   var opts = {
     width: 480,
     height: 320
@@ -47,10 +47,72 @@ DygraphOptionsTestCase.prototype.getLogscaleForX = function() {
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, opts);
 
-  assertFalse(g.getOptionForAxis('logscale', 'x'));
-  assertFalse(g.getOptionForAxis('logscale', 'y'));
+  assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+  assertFalse(!!g.getOptionForAxis('logscale', 'y'));
 
   g.updateOptions({ logscale : true });
-  assertFalse(g.getOptionForAxis('logscale', 'x'));
-  assertTrue(g.getOptionForAxis('logscale', 'y'));
+  assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+  assertTrue(!!g.getOptionForAxis('logscale', 'y'));
+};
+
+// Helper to gather all warnings emitted by Dygraph constructor.
+// Removes everything after the first open parenthesis in each warning.
+// Returns them in a (possibly empty) list.
+var getWarnings = function(div, data, opts) {
+  var warnings = [];
+  var oldWarn = console.warn;
+  console.warn = function(message) {
+    warnings.push(message.replace(/ \(.*/, ''));
+  };
+  try {
+    new Dygraph(graph, data, opts);
+  } catch (e) {
+  }
+  console.warn = oldWarn;
+  return warnings;
+};
+
+DygraphOptionsTestCase.prototype.testLogWarningForNonexistentOption = function() {
+  if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+    return;  // this test won't pass in non-debug mode.
+  }
+
+  var graph = document.getElementById("graph");
+  var data = "X,Y,Y2,Y3\n" +
+      "1,-1,2,3";
+
+  var expectWarning = function(opts, badOptionName) {
+    DygraphOptions.resetWarnings_();
+    var warnings = getWarnings(graph, data, opts);
+    assertEquals(['Unknown option ' + badOptionName], warnings);
+  };
+  var expectNoWarning = function(opts) {
+    DygraphOptions.resetWarnings_();
+    var warnings = getWarnings(graph, data, opts);
+    assertEquals([], warnings);
+  };
+
+  expectNoWarning({});
+  expectWarning({nonExistentOption: true}, 'nonExistentOption');
+  expectWarning({series: {Y: {nonExistentOption: true}}}, 'nonExistentOption');
+  // expectWarning({Y: {nonExistentOption: true}});
+  expectWarning({axes: {y: {anotherNonExistentOption: true}}}, 'anotherNonExistentOption');
+  expectWarning({highlightSeriesOpts: {anotherNonExistentOption: true}}, 'anotherNonExistentOption');
+  expectNoWarning({highlightSeriesOpts: {strokeWidth: 20}});
+  expectNoWarning({strokeWidth: 20});
+};
+
+DygraphOptionsTestCase.prototype.testOnlyLogsEachWarningOnce = function() {
+  if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+    return;  // this test won't pass in non-debug mode.
+  }
+
+  var graph = document.getElementById("graph");
+  var data = "X,Y,Y2,Y3\n" +
+      "1,-1,2,3";
+
+  var warnings1 = getWarnings(graph, data, {nonExistent: true});
+  var warnings2 = getWarnings(graph, data, {nonExistent: true});
+  assertEquals(['Unknown option nonExistent'], warnings1);
+  assertEquals([], warnings2);
 };
index 7289e0c..8a7e081 100644 (file)
@@ -20,7 +20,9 @@ perSeriesTestCase.prototype.testPerSeriesFill = function() {
     drawYGrid: false,
     drawXAxis: false,
     drawYAxis: false,
-    Y: { fillGraph: true },
+    series: {
+      Y: { fillGraph: true },
+    },
     colors: [ '#FF0000', '#0000FF' ],
     fillAlpha: 0.15
   };
index aed2b3e..c2b829e 100644 (file)
@@ -115,7 +115,6 @@ RangeSelectorTestCase.prototype.testRangeSelectorOptions = function() {
     showRangeSelector: true,
     rangeSelectorHeight: 30,
     rangeSelectorPlotFillColor: 'lightyellow',
-    rangeSelectorPlotStyleColor: 'yellow',
     labels: ['X', 'Y']
   };
   var data = [
index 27e9a1a..dde41b8 100644 (file)
@@ -96,12 +96,20 @@ SimpleDrawingTestCase.prototype.testDrawWithAxis = function() {
  */
 SimpleDrawingTestCase.prototype.testDrawSimpleDash = function() {
   var opts = {
-      drawXGrid: false,
-      drawYGrid: false,
-      drawXAxis: false,
-      drawYAxis: false,
+    axes: {
+      x: {
+        drawGrid: false,
+        drawAxis: false
+      },
+      y: {
+        drawGrid: false,
+        drawAxis: false
+      }
+    },
+    series: {
       'Y1': {strokePattern: [25, 7, 7, 7]},
-      colors: ['#ff0000']
+    },
+    colors: ['#ff0000']
   };
 
   var graph = document.getElementById("graph");
index ac28e99..faf8f25 100644 (file)
@@ -67,7 +67,7 @@ UpdateOptionsTestCase.prototype.testStrokeSingleSeries = function() {
   var optionsForY1 = { };
 
   optionsForY1['strokeWidth'] = 3;
-  updatedOptions['Y1'] = optionsForY1;
+  updatedOptions['series'] = {'Y1': optionsForY1};
 
   // These options will allow us to jump to renderGraph_()
   // drawGraph_() will be skipped.
@@ -80,17 +80,16 @@ UpdateOptionsTestCase.prototype.testStrokeSingleSeries = function() {
 UpdateOptionsTestCase.prototype.testSingleSeriesRequiresNewPoints = function() {
   var graphDiv = document.getElementById("graph");
   var graph = new Dygraph(graphDiv, this.data, this.opts);
-  var updatedOptions = { };
-  var optionsForY1 = { };
-  var optionsForY2 = { };
-
-  // This will not require new points.
-  optionsForY1['strokeWidth'] = 2;
-  updatedOptions['Y1'] = optionsForY1;
-
-  // This will require new points.
-  optionsForY2['stepPlot'] = true;
-  updatedOptions['Y2'] = optionsForY2;
+  var updatedOptions = {
+    series: {
+      Y1: {
+        strokeWidth: 2
+      },
+      Y2: {
+        stepPlot: true
+      }
+    }
+  };
 
   // These options will not allow us to jump to renderGraph_()
   // drawGraph_() must be called
index 0aee0d5..5bd7f8d 100644 (file)
@@ -177,6 +177,12 @@ UtilsTestCase.prototype.testToRGB = function() {
   assertEquals({r: 255, g: 0, b: 0}, Dygraph.toRGB_('red'));
 };
 
+UtilsTestCase.prototype.testIsPixelChangingOptionList = function() {
+  var isPx = Dygraph.isPixelChangingOptionList;
+  assertTrue(isPx([], { axes: { y: { digitsAfterDecimal: 3 }}}));
+  assertFalse(isPx([], { axes: { y: { axisLineColor: 'blue' }}}));
+};
+
 /*
 UtilsTestCase.prototype.testDateSet = function() {
   var base = new Date(1383455100000);
index 8520c10..b88e720 100644 (file)
@@ -339,8 +339,8 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
   "axis": {
     "default": "(none)",
     "labels": ["Axis display"],
-    "type": "string or object",
-    "description": "Set to either an object ({}) filled with options for this axis or to the name of an existing data series with its own axis to re-use that axis. See tests for usage."
+    "type": "string",
+    "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series."
   },
   "pixelsPerXLabel": {
     "default": "",
@@ -425,6 +425,13 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "red, blue",
     "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately."
   },
+  "gridLinePattern": {
+    "default": "null",
+    "labels": ["Grid"],
+    "type": "array<integer>",
+    "example": "[10, 2, 5, 2]",
+    "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines."
+  },
   "visibility": {
     "default": "[true, true, ...]",
     "labels": ["Data Line display"],
@@ -817,6 +824,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "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."
   },
+  "axes": {
+    "default": "null",
+    "labels": ["Configuration"],
+    "type": "Object",
+    "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on <a href='http://dygraphs.com/per-axis.html'>per-series and per-axis options</a>."
+  },
   "series": {
     "default": "null",
     "labels": ["Series"],
index b0fad61..413b9ea 100644 (file)
 
 var DygraphOptions = (function() {
 
+// For "production" code, this gets set to false by uglifyjs.
+// Need to define it outside of "use strict", hence the nested IIFEs.
+if (typeof(DEBUG) === 'undefined') DEBUG=true;
+
+return (function() {
+
 /*jshint sub:true */
 /*global Dygraph:false */
 "use strict";
@@ -217,6 +223,8 @@ DygraphOptions.prototype.reparseSeries = function() {
     Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {});
   }
   Dygraph.update(this.xAxis_.options, axis_opts["x"] || {});
+
+  if (DEBUG) this.validateOptions_();
 };
 
 /**
@@ -372,6 +380,77 @@ DygraphOptions.prototype.seriesNames = function() {
   return this.labels_;
 };
 
+if (DEBUG) {
+
+/**
+ * Validate all options.
+ * This requires Dygraph.OPTIONS_REFERENCE, which is only available in debug builds.
+ * @private
+ */
+DygraphOptions.prototype.validateOptions_ = function() {
+  if (typeof Dygraph.OPTIONS_REFERENCE === 'undefined') {
+    throw 'Called validateOptions_ in prod build.';
+  }
+
+  var that = this;
+  var validateOption = function(optionName) {
+    if (!Dygraph.OPTIONS_REFERENCE[optionName]) {
+      that.warnInvalidOption_(optionName);
+    }
+  };
+
+  var optionsDicts = [this.xAxis_.options,
+                      this.yAxes_[0].options,
+                      this.yAxes_[1] && this.yAxes_[1].options,
+                      this.global_,
+                      this.user_,
+                      this.highlightSeries_];
+  var names = this.seriesNames();
+  for (var i = 0; i < names.length; i++) {
+    var name = names[i];
+    if (this.series_.hasOwnProperty(name)) {
+      optionsDicts.push(this.series_[name].options);
+    }
+  }
+  for (var i = 0; i < optionsDicts.length; i++) {
+    var dict = optionsDicts[i];
+    if (!dict) continue;
+    for (var optionName in dict) {
+      if (dict.hasOwnProperty(optionName)) {
+        validateOption(optionName);
+      }
+    }
+  }
+};
+
+var WARNINGS = {};  // Only show any particular warning once.
+
+/**
+ * Logs a warning about invalid options.
+ * TODO: make this throw for testing
+ * @private
+ */
+DygraphOptions.prototype.warnInvalidOption_ = function(optionName) {
+  if (!WARNINGS[optionName]) {
+    WARNINGS[optionName] = true;
+    var isSeries = (this.labels_.indexOf(optionName) >= 0);
+    if (isSeries) {
+      console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs');
+    } else {
+      console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html');
+      throw "invalid option " + optionName;
+    }
+  }
+};
+
+// Reset list of previously-shown warnings. Used for testing.
+DygraphOptions.resetWarnings_ = function() {
+  WARNINGS = {};
+};
+
+}
+
 return DygraphOptions;
 
 })();
+})();
index 73777b9..f4bb2ff 100644 (file)
@@ -11,7 +11,8 @@
  * search) and generic DOM-manipulation functions.
  */
 
-/*jshint globalstrict: true */
+(function() {
+
 /*global Dygraph:false, G_vmlCanvasManager:false, Node:false */
 "use strict";
 
@@ -894,72 +895,64 @@ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis,
   })();
 };
 
+// A whitelist of options that do not change pixel positions.
+var pixelSafeOptions = {
+  'annotationClickHandler': true,
+  'annotationDblClickHandler': true,
+  'annotationMouseOutHandler': true,
+  'annotationMouseOverHandler': true,
+  'axisLabelColor': true,
+  'axisLineColor': true,
+  'axisLineWidth': true,
+  'clickCallback': true,
+  'drawCallback': true,
+  'drawHighlightPointCallback': true,
+  'drawPoints': true,
+  'drawPointCallback': true,
+  'drawXGrid': true,
+  'drawYGrid': true,
+  'fillAlpha': true,
+  'gridLineColor': true,
+  'gridLineWidth': true,
+  'hideOverlayOnMouseOut': true,
+  'highlightCallback': true,
+  'highlightCircleSize': true,
+  'interactionModel': true,
+  'isZoomedIgnoreProgrammaticZoom': true,
+  'labelsDiv': true,
+  'labelsDivStyles': true,
+  'labelsDivWidth': true,
+  'labelsKMB': true,
+  'labelsKMG2': true,
+  'labelsSeparateLines': true,
+  'labelsShowZeroValues': true,
+  'legend': true,
+  'panEdgeFraction': true,
+  'pixelsPerYLabel': true,
+  'pointClickCallback': true,
+  'pointSize': true,
+  'rangeSelectorPlotFillColor': true,
+  'rangeSelectorPlotStrokeColor': true,
+  'showLabelsOnHighlight': true,
+  'showRoller': true,
+  'strokeWidth': true,
+  'underlayCallback': true,
+  'unhighlightCallback': true,
+  'zoomCallback': true
+};
+
 /**
  * This function will scan the option list and determine if they
  * require us to recalculate the pixel positions of each point.
+ * TODO: move this into dygraph-options.js
  * @param {!Array.<string>} labels a list of options to check.
  * @param {!Object} attrs
  * @return {boolean} true if the graph needs new points else false.
  * @private
  */
 Dygraph.isPixelChangingOptionList = function(labels, attrs) {
-  // A whitelist of options that do not change pixel positions.
-  var pixelSafeOptions = {
-    'annotationClickHandler': true,
-    'annotationDblClickHandler': true,
-    'annotationMouseOutHandler': true,
-    'annotationMouseOverHandler': true,
-    'axisLabelColor': true,
-    'axisLineColor': true,
-    'axisLineWidth': true,
-    'clickCallback': true,
-    'digitsAfterDecimal': true,
-    'drawCallback': true,
-    'drawHighlightPointCallback': true,
-    'drawPoints': true,
-    'drawPointCallback': true,
-    'drawXGrid': true,
-    'drawYGrid': true,
-    'fillAlpha': true,
-    'gridLineColor': true,
-    'gridLineWidth': true,
-    'hideOverlayOnMouseOut': true,
-    'highlightCallback': true,
-    'highlightCircleSize': true,
-    'interactionModel': true,
-    'isZoomedIgnoreProgrammaticZoom': true,
-    'labelsDiv': true,
-    'labelsDivStyles': true,
-    'labelsDivWidth': true,
-    'labelsKMB': true,
-    'labelsKMG2': true,
-    'labelsSeparateLines': true,
-    'labelsShowZeroValues': true,
-    'legend': true,
-    'maxNumberWidth': true,
-    'panEdgeFraction': true,
-    'pixelsPerYLabel': true,
-    'pointClickCallback': true,
-    'pointSize': true,
-    'rangeSelectorPlotFillColor': true,
-    'rangeSelectorPlotStrokeColor': true,
-    'showLabelsOnHighlight': true,
-    'showRoller': true,
-    'sigFigs': true,
-    'strokeWidth': true,
-    'underlayCallback': true,
-    'unhighlightCallback': true,
-    'xAxisLabelFormatter': true,
-    'xTicker': true,
-    'xValueFormatter': true,
-    'yAxisLabelFormatter': true,
-    'yValueFormatter': true,
-    'zoomCallback': true
-  };
-
   // Assume that we do not require new points.
   // This will change to true if we actually do need new points.
-  var requiresNewPoints = false;
 
   // Create a dictionary of series names for faster lookup.
   // If there are no labels, then the dictionary stays empty.
@@ -970,34 +963,44 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
     }
   }
 
+  // Scan through a flat (i.e. non-nested) object of options.
+  // Returns true/false depending on whether new points are needed.
+  var scanFlatOptions = function(options) {
+    for (var property in options) {
+      if (options.hasOwnProperty(property) &&
+          !pixelSafeOptions[property]) {
+        return true;
+      }
+    }
+    return false;
+  };
+
   // Iterate through the list of updated options.
   for (var property in attrs) {
-    // Break early if we already know we need new points from a previous option.
-    if (requiresNewPoints) {
-      break;
-    }
-    if (attrs.hasOwnProperty(property)) {
-      // Find out of this field is actually a series specific options list.
-      if (seriesNamesDictionary[property]) {
-        // This property value is a list of options for this series.
-        // If any of these sub properties are not pixel safe, set the flag.
-        for (var subProperty in attrs[property]) {
-          // Break early if we already know we need new points from a previous option.
-          if (requiresNewPoints) {
-            break;
-          }
-          if (attrs[property].hasOwnProperty(subProperty) && !pixelSafeOptions[subProperty]) {
-            requiresNewPoints = true;
-          }
+    if (!attrs.hasOwnProperty(property)) continue;
+
+    // Find out of this field is actually a series specific options list.
+    if (property == 'highlightSeriesOpts' ||
+        (seriesNamesDictionary[property] && !attrs.series)) {
+      // This property value is a list of options for this series.
+      if (scanFlatOptions(attrs[property])) return true;
+    } else if (property == 'series' || property == 'axes') {
+      // This is twice-nested options list.
+      var perSeries = attrs[property];
+      for (var series in perSeries) {
+        if (perSeries.hasOwnProperty(series) &&
+            scanFlatOptions(perSeries[series])) {
+          return true;
         }
-      // If this was not a series specific option list, check if its a pixel changing property.
-      } else if (!pixelSafeOptions[property]) {
-        requiresNewPoints = true;
       }
+    } else {
+      // If this was not a series specific option list, check if it's a pixel
+      // changing property.
+      if (!pixelSafeOptions[property]) return true;
     }
   }
 
-  return requiresNewPoints;
+  return false;
 };
 
 Dygraph.Circles = {
@@ -1207,3 +1210,5 @@ Dygraph.parseFloat_ = function(x, opt_line_no, opt_line) {
 
   return null;
 };
+
+})();
index 7dbf29f..49ecc3e 100644 (file)
@@ -46,6 +46,7 @@
 // For "production" code, this gets set to false by uglifyjs.
 if (typeof(DEBUG) === 'undefined') DEBUG=true;
 
+var Dygraph = (function() {
 /*jshint globalstrict: true */
 /*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */
 "use strict";
@@ -334,7 +335,6 @@ Dygraph.DEFAULT_ATTRS = {
   axisLineWidth: 0.3,
   gridLineWidth: 0.3,
   axisLabelColor: "black",
-  axisLabelFont: "Arial",  // TODO(danvk): is this implemented?
   axisLabelWidth: 50,
   drawYGrid: true,
   drawXGrid: true,
@@ -3794,3 +3794,7 @@ Dygraph.addAnnotationRule = function() {
 
   console.warn("Unable to add default annotation CSS rule; display may be off.");
 };
+
+return Dygraph;
+
+})();
index 0f0ec3c..127ec04 100644 (file)
@@ -98,7 +98,8 @@
         var shapes = [];
         var addShape = function(name, pointFn, highlightPointFn) {
           shapes.push(name);
-          opts[name] = {
+          if (!opts['series']) opts['series'] = {};
+          opts.series[name] = {
             drawPointCallback: pointFn,
             drawHighlightPointCallback: highlightPointFn
           };
index 0188dc4..2b048df 100644 (file)
@@ -59,7 +59,9 @@
           return ret;
         },
         {
-          Y1: { fillGraph: true }
+          series: {
+            Y1: { fillGraph: true }
+          }
         }
       );
     </script>
index 84730b1..4f436d6 100644 (file)
               {
                 legend: 'always',
                 strokeWidth: 2,
-                'parabola': {
-                  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: Dygraph.DOT_DASH_LINE,
-                  strokeWidth: 2,
-                  highlightCircleSize: 3
+                series: {
+                  'parabola': {
+                    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: Dygraph.DOT_DASH_LINE,
+                    strokeWidth: 2,
+                    highlightCircleSize: 3
+                  }
                 }
               }
           );
               data,
               {
                 strokeWidth: 2,
-                '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
+                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
+                  }
                 }
               }
           );
index 104ab78..5bbbf15 100644 (file)
@@ -246,11 +246,13 @@ var candleData = "Date,Open,Close,High,Low\n" +
             {
               labels: ['Date', 'A', 'B'],
               includeZero: true,
-              "A": {
-                strokeWidth: 2
-              },
-              "B": {
-                plotter: barChartPlotter
+              series: {
+                "A": {
+                  strokeWidth: 2
+                },
+                "B": {
+                  plotter: barChartPlotter
+                }
               }
             });
 
@@ -314,12 +316,14 @@ var candleData = "Date,Open,Close,High,Low\n" +
             NoisyData(),
             {
               errorBars: true,
-              'A': {
-                plotter: Dygraph.Plotters.errorPlotter
-              },
-              'B': {
-                plotter: Dygraph.Plotters.linePlotter,
-                strokePattern: Dygraph.DASHED_LINE
+              series: {
+                'A': {
+                  plotter: Dygraph.Plotters.errorPlotter
+                },
+                'B': {
+                  plotter: Dygraph.Plotters.linePlotter,
+                  strokePattern: Dygraph.DASHED_LINE
+                }
               }
             });
 
index 8c58811..dd9ff5c 100644 (file)
         labels: ["Date","GapSeries1","GapSeries2"],
         showRoller: true,
         stepPlot: true,
-        GapSeries2: { axis: {} }
+        series: {
+          GapSeries2: {
+            axis: 'y2'
+          }
+        }
       }
     );
     </script>
index e3e29ff..24f717a 100644 (file)
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            'Y3': {
-              axis: {
-              }
-            },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
+            series: {
+              'Y3': {
+                axis: 'y2'
+              },
+              'Y4': {
+                axis: 'y2'
+              },
             },
             axes: {
               y2: {
index 193c48e..184101e 100644 (file)
             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' }
             },
             xAxisLabelWidth: 100,
             yAxisLabelWidth: 100,