Merge http://github.com/danvk/dygraphs into custom-points
authorRobert Konigsberg <konigsberg@google.com>
Wed, 22 Feb 2012 14:39:38 +0000 (09:39 -0500)
committerRobert Konigsberg <konigsberg@google.com>
Wed, 22 Feb 2012 14:39:38 +0000 (09:39 -0500)
Conflicts:
dygraph-canvas.js
dygraph-utils.js

24 files changed:
auto_tests/misc/local.html
auto_tests/tests/axis_labels.js
auto_tests/tests/callback.js [new file with mode: 0644]
auto_tests/tests/interaction_model.js
auto_tests/tests/multiple_axes.js
auto_tests/tests/range_tests.js
auto_tests/tests/sanity.js
auto_tests/tests/simple_drawing.js
docs/index.html
dygraph-canvas.js
dygraph-gviz.js
dygraph-interaction-model.js
dygraph-layout.js
dygraph-options-reference.js
dygraph-range-selector.js
dygraph-utils.js
dygraph.js
gallery/interaction-api.js
push-to-web.sh
tests/annotation-gviz.html
tests/dygraph-many-points-benchmark.html
tests/interaction.js
tests/per-series.html
tests/two-axes-vr.html [new file with mode: 0644]

index d24cab8..8e4cfdd 100644 (file)
@@ -39,6 +39,7 @@
   <script type="text/javascript" src="../tests/update_options.js"></script>
   <script type="text/javascript" src="../tests/utils_test.js"></script>
   <script type="text/javascript" src="../tests/multiple_axes.js"></script>
+  <script type="text/javascript" src="../tests/callback.js"></script>
 
 
   <script type="text/javascript">
index 6fc54e5..02727a3 100644 (file)
@@ -420,3 +420,30 @@ AxisLabelsTestCase.prototype.testGlobalFormatters = function() {
   g.setSelection(9);
   assertEquals("vf9: y:vf18", getLegend());
 };
+
+AxisLabelsTestCase.prototype.testSeriesOrder = function() {
+  var opts = {
+    width: 480,
+    height: 320
+  };
+  var data = "x,00,01,10,11\n" +
+      "0,101,201,301,401\n" +
+      "1,102,202,302,402\n" +
+      "2,103,203,303,403\n" +
+      "3,104,204,304,404\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  g.setSelection(2);
+  assertEquals('2: 00:103 01:203 10:303 11:403', getLegend());
+
+  // Sanity checks for indexFromSetName
+  assertEquals(0, g.indexFromSetName("x"));
+  assertEquals(1, g.indexFromSetName("00"));
+  assertEquals(null, g.indexFromSetName("abcde"));
+
+  // Verify that we get the label list back in the right order
+  assertEquals(["x", "00", "01", "10", "11"], g.getLabels());
+};
diff --git a/auto_tests/tests/callback.js b/auto_tests/tests/callback.js
new file mode 100644 (file)
index 0000000..1697e21
--- /dev/null
@@ -0,0 +1,54 @@
+/** 
+ * @fileoverview Test cases for the callbacks.
+ *
+ * @author uemit.seren@gmail.com (Ümit Seren)
+ */
+
+var CallbackTestCase = TestCase("callback");
+
+CallbackTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+CallbackTestCase.prototype.tearDown = function() {
+};
+ var data = "X,a\,b,c\n" +
+ "10,-1,1,2\n" +
+ "11,0,3,1\n" +
+ "12,1,4,2\n" +
+ "13,0,2,3\n";
+ /**
+  * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
+  * is properly called when the  first series is hidden (setVisibility = false) 
+  * 
+  */
+ CallbackTestCase.prototype.testHighlightCallbackIsCalled = function() {
+   var h_row;
+   var h_pts;
+
+   var highlightCallback  =  function(e, x, pts, row) {
+         h_row = row;
+         h_pts = pts;
+   }; 
+
+   
+
+   var graph = document.getElementById("graph");
+   var g = new Dygraph(graph, data,
+       {
+         width: 100,
+         height : 100,
+         visibility: [false, true, true],
+         highlightCallback : highlightCallback,
+       });
+
+   DygraphOps.dispatchMouseMove(g, 13, 10);
+
+   //check correct row is returned
+   assertEquals(3, h_row);
+   //check there are only two points (because first series is hidden)
+   assertEquals(2, h_pts.length);
+ };
index 8eec47c..4646ff8 100644 (file)
@@ -331,3 +331,36 @@ InteractionModelTestCase.prototype.testIsZoomed_updateOptions_both = function()
   assertTrue(g.isZoomed("x"));
   assertTrue(g.isZoomed("y"));
 };
+
+
+InteractionModelTestCase.prototype.testCorrectAxisValueRangeAfterUnzoom = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {valueRange:[1,50],dateRange:[1,9],animatedZooms:false});
+  
+  // Zoom x axis
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 30, 10);
+  DygraphOps.dispatchMouseUp_Point(g, 30, 10);
+
+  // Zoom y axis
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 10, 30);
+  DygraphOps.dispatchMouseUp_Point(g, 10, 30);
+  currentYAxisRange = g.yAxisRange();
+  currentXAxisRange = g.xAxisRange();
+  
+  //check that the range for the axis has changed
+  assertNotEquals(1,currentXAxisRange[0]);
+  assertNotEquals(10,currentXAxisRange[1]);
+  assertNotEquals(1,currentYAxisRange[0]);
+  assertNotEquals(50,currentYAxisRange[1]);
+  
+  // unzoom by doubleclick
+  DygraphOps.dispatchDoubleClick(g, null);
+  
+  // check if range for y-axis was reset to original value 
+  // TODO check if range for x-axis is correct. 
+  // Currently not possible because dateRange is set to null and extremes are returned
+  newYAxisRange = g.yAxisRange();
+  assertEquals(1,newYAxisRange[0]);
+  assertEquals(50,newYAxisRange[1]);
+};
index bf75876..05e93c7 100644 (file)
@@ -243,3 +243,51 @@ MultipleAxesTestCase.prototype.testNoY2LabelWithoutSecondaryAxis = function() {
   assertEquals(["y-axis"], getClassTexts("dygraph-ylabel"));
   assertEquals([], getClassTexts("dygraph-y2label"));
 };
+
+MultipleAxesTestCase.prototype.testValueRangePerAxisOptions = 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"));
+};
\ No newline at end of file
index 32c511e..c92c489 100644 (file)
@@ -70,8 +70,12 @@ RangeTestCase.prototype.testRangeSetOperations = function() {
   g.updateOptions({  });
   assertEquals([12, 18], g.xAxisRange());
   assertEquals([10, 40], g.yAxisRange(0));
+  
+  g.updateOptions({valueRange : null, axes: {y:{valueRange : [15, 20]}}});
+  assertEquals([12, 18], g.xAxisRange());
+  assertEquals([15, 20], g.yAxisRange(0));
 
-  g.updateOptions({ dateWindow : null, valueRange : null });
+  g.updateOptions({ dateWindow : null, valueRange : null, axes: null });
   assertEquals([10, 20], g.xAxisRange());
   assertEquals([0, 55], g.yAxisRange(0));
 };
index 528b7a1..95a1aa2 100644 (file)
@@ -80,6 +80,8 @@ SanityTestCase.prototype.testYAxisRange_custom = function() {
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, ZERO_TO_FIFTY, { valueRange: [0,50] });
   assertEquals([0, 50], g.yAxisRange(0));
+  g.updateOptions({valueRange: null, axes: {y: {valueRange: [10, 40]}}});
+  assertEquals([10, 40], g.yAxisRange(0));
 };
 
 /**
@@ -95,7 +97,15 @@ SanityTestCase.prototype.testToDomYCoord = function() {
 
   assertEquals(50, g.toDomYCoord(0));
   assertEquals(0, g.toDomYCoord(50));
-  
+
+  for (var x = 0; x <= 50; x++) {
+    assertEqualsDelta(50 - x, g.toDomYCoord(x), 0.00001);
+  }
+  g.updateOptions({valueRange: null, axes: {y: {valueRange: [0, 50]}}});
+
+  assertEquals(50, g.toDomYCoord(0));
+  assertEquals(0, g.toDomYCoord(50));
+
   for (var x = 0; x <= 50; x++) {
     assertEqualsDelta(50 - x, g.toDomYCoord(x), 0.00001);
   }
index ca19301..0ea13a4 100644 (file)
@@ -56,3 +56,27 @@ SimpleDrawingTestCase.prototype.testDrawSimpleRangePlusOne = function() {
     lineWidth: 1
   });
 }
+
+/**
+ * Tests that it is drawing dashes, and it remember the dash history between
+ * points.
+ */
+SimpleDrawingTestCase.prototype.testDrawSimpleDash = function() {
+  var opts = {
+      drawXGrid: false,
+      drawYGrid: false,
+      drawXAxis: false,
+      drawYAxis: false,
+      'Y1': {strokePattern: [25, 7, 7, 7]},
+      colors: ['#ff0000']
+  };
+
+  var graph = document.getElementById("graph");
+  // Set the dims so we pass if default changes.
+  graph.style.width='480px';
+  graph.style.height='320px';
+  var g = new Dygraph(graph, [[1, 4], [2, 5], [3, 3], [4, 7], [5, 9]], opts);
+  htx = g.hidden_ctx_;
+
+  assertEquals(29, CanvasAssertions.numLinesDrawn(htx, "#ff0000"));
+};
index bf5b5fa..5c2444f 100644 (file)
@@ -632,10 +632,42 @@ public static native JavaScriptObject drawDygraph(
     <p>dygraphs has also found use in other organizations:</p>
 
     <ul class='padded-list'>
+  <li><a
+    href="http://iswa.ccmc.gsfc.nasa.gov:8080/IswaSystemWebApp/index.jsp?i_1=388&l_1=99&t_1=316&w_1=800&h_1=400&s_1=0!3!0!ACE.B_x!ACE.B_y!ACE.B_z!">Integrated
+    Space Weather Analysis System</a> (NASA)<br/>
+  <span class="desc">&ldquo;We use [dygraphs] in the Integrated Space Weather
+    Analysis System available from the Space Weather Laboratory at NASA Goddard
+    Space Flight Center. It works quite well for time series data from various
+    missions and simulations that we store.&rdquo;</span></li>
+
+
+  <li><a href="http://www.eutelsat.fr">Eutelsat</a><br/>
+  <span class="desc">&ldquo;Eutelsat uses dygraphs for charting spacecraft
+    telemetry for a fleet of 25 geostationary satellites. The spacecraft
+    engineers are very happy with it.  All satellite combined are producing
+    about 200 millions unique data points per day so we really appreciate the
+    excellent performance of dygraphs.&rdquo;</span></li>
+
+  <li><a href="http://www.10gen.com/mongodb-monitoring-service">10gen MongoDB
+    Monitoring Service</a><br/>
+  <span class="desc">A free monitoring service for MongoDB from 10gen (the
+    creators of MongoDB). Used by thousands of servers and users. Makes use of
+    <a href="tests/synchronize.html">synchronized charts</a> to display many
+    quantities simultaneously.</span></li>
+
     <li><a href="http://toolserver.org/~dartar/moodbar/">Wikimedia Foundation - Moodbar data dashboard</a><br/>
     <span class="desc">dygraphs is used internally at Wikimedia as a handy solution to monitor the
     results of a bunch of small experiments.</span></li>
 
+  <li><a href="http://code.google.com/p/quadrant-framework/">quadrant-framework</a> (MySQL Load Testing Framework)<br/>
+  <span class="desc">A user friendly framework for creating and visualizing
+    MySQL database load test jobs. For more information on its use of dygraphs,
+    see <a href="http://themattreid.com/wordpress/2011/05/20/quadrant-framework-rev7-update-adds-dygraphs-support/">this post</a>.</span></li>
+
+  <li><a href="http://spinwave.wordpress.com/2011/03/28/spinwave-systems-enables-energy-efficiency-case-studies/">Spinwave Systems</a> (Home energy monitoring)<br/>
+  <span class="desc">dygraphs is used to chart energy usage over time.</span></li>
+
+
     <li><a href="http://www.socib.es/jwebchart/?file=http://thredds.socib.es/thredds/dodsC/mooring/weather_station/mobims_calamillor-scb_met001/L1/dep0001_mobims-calamillor_scb-met001_L1_latest.nc">Jwebchart</a><br/>
     <span class="desc">
     jWebChart is a stand-alone and Thredds' embedded plotting system for
index caa4a37..caf4049 100644 (file)
@@ -28,6 +28,7 @@
 /*global Dygraph:false,RGBColor:false */
 "use strict";
 
+
 var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
   this.dygraph_ = dygraph;
 
@@ -311,7 +312,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
     inner_div.className = 'dygraph-axis-label' +
                           ' dygraph-axis-label-' + axis +
                           (prec_axis ? ' dygraph-axis-label-' + prec_axis : '');
-    inner_div.appendChild(document.createTextNode(txt));
+    inner_div.innerHTML=txt;
     div.appendChild(inner_div);
     return div;
   };
@@ -679,12 +680,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var pointsLength = points.length;
   var point, i, j, prevX, prevY, prevYs, color, setName, newYs, err_color, rgb, yscale, axis;
 
-  var setNames = [];
-  for (var name in this.layout.datasets) {
-    if (this.layout.datasets.hasOwnProperty(name)) {
-      setNames.push(name);
-    }
-  }
+  var setNames = this.layout.setNames;
   var setCount = setNames.length;
 
   // TODO(danvk): Move this mapping into Dygraph and get it out of here.
@@ -826,8 +822,9 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var afterLastIndexInSet = 0;
   var setLength = 0;
   for (i = 0; i < setCount; i += 1) {
+    firstIndexInSet = this.layout.setPointsOffsets[i];
     setLength = this.layout.setPointsLengths[i];
-    afterLastIndexInSet += setLength;
+    afterLastIndexInSet = firstIndexInSet + setLength;
     setName = setNames[i];
     color = this.colors[setName];
     var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
@@ -844,6 +841,10 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       drawPointCallback = Dygraph.Circles.DEFAULT;
     }
     var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
+    var strokePattern = this.dygraph_.attr_("strokePattern", setName);
+    if (!Dygraph.isArrayLike(strokePattern)) {
+      strokePattern = null;
+    }
     for (j = firstIndexInSet; j < afterLastIndexInSet; j++) {
       point = points[j];
       if (isNullOrNaN(point.canvasy)) {
@@ -852,8 +853,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
           ctx.beginPath();
           ctx.strokeStyle = color;
           ctx.lineWidth = this.attr_('strokeWidth');
-          ctx.moveTo(prevX, prevY);
-          ctx.lineTo(point.canvasx, prevY);
+          this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
           ctx.stroke();
         }
         // this will make us move to the next point, not draw a line to it.
@@ -879,13 +879,12 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
             ctx.beginPath();
             ctx.strokeStyle = color;
             ctx.lineWidth = strokeWidth;
-            ctx.moveTo(prevX, prevY);
             if (stepPlot) {
-              ctx.lineTo(point.canvasx, prevY);
+              this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
             }
+            this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
             prevX = point.canvasx;
             prevY = point.canvasy;
-            ctx.lineTo(prevX, prevY);
             ctx.stroke();
           }
         }
@@ -908,3 +907,94 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
 
   context.restore();
 };
+
+/**
+ * This does dashed lines onto a canvas for a given pattern. You must call
+ * ctx.stroke() after to actually draw it, much line ctx.lineTo(). It remembers
+ * the state of the line in regards to where we left off on drawing the pattern.
+ * You can draw a dashed line in several function calls and the pattern will be
+ * continous as long as you didn't call this function with a different pattern
+ * in between.
+ * @param ctx The canvas 2d context to draw on.
+ * @param x The start of the line's x coordinate.
+ * @param y The start of the line's y coordinate.
+ * @param x2 The end of the line's x coordinate.
+ * @param y2 The end of the line's y coordinate.
+ * @param pattern The dash pattern to draw, an array of integers where even 
+ * index is drawn and odd index is not drawn (Ex. [10, 2, 5, 2], 10 is drawn 5
+ * is drawn, 2 is the space between.). A null pattern, array of length one, or
+ * empty array will do just a solid line.
+ * @private
+ */
+DygraphCanvasRenderer.prototype._dashedLine = function(ctx, x, y, x2, y2, pattern) {
+  // Original version http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
+  // Modified by Russell Valentine to keep line history and continue the pattern
+  // where it left off.
+  var dx, dy, len, rot, patternIndex, segment;
+
+  // If we don't have a pattern or it is an empty array or of size one just
+  // do a solid line.
+  if (!pattern || pattern.length <= 1) {
+    ctx.moveTo(x, y);
+    ctx.lineTo(x2, y2);
+    return;
+  }
+
+  // If we have a different dash pattern than the last time this was called we
+  // reset our dash history and start the pattern from the begging 
+  // regardless of state of the last pattern.
+  if (!Dygraph.compareArrays(pattern, this._dashedLineToHistoryPattern)) {
+    this._dashedLineToHistoryPattern = pattern;
+    this._dashedLineToHistory = [0, 0];
+  }
+  ctx.save();
+
+  // Calculate transformation parameters
+  dx = (x2-x);
+  dy = (y2-y);
+  len = Math.sqrt(dx*dx + dy*dy);
+  rot = Math.atan2(dy, dx);
+
+  // Set transformation
+  ctx.translate(x, y);
+  ctx.moveTo(0, 0);
+  ctx.rotate(rot);
+
+  // Set last pattern index we used for this pattern.
+  patternIndex = this._dashedLineToHistory[0];
+  x = 0;
+  while (len > x) {
+    // Get the length of the pattern segment we are dealing with.
+    segment = pattern[patternIndex];
+    // If our last draw didn't complete the pattern segment all the way we 
+    // will try to finish it. Otherwise we will try to do the whole segment.
+    if (this._dashedLineToHistory[1]) {
+      x += this._dashedLineToHistory[1];
+    } else {
+      x += segment;
+    }
+    if (x > len) {
+      // We were unable to complete this pattern index all the way, keep
+      // where we are the history so our next draw continues where we left off
+      // in the pattern.
+      this._dashedLineToHistory = [patternIndex, x-len];
+      x = len;
+    } else {
+      // We completed this patternIndex, we put in the history that we are on
+      // the beginning of the next segment.
+      this._dashedLineToHistory = [(patternIndex+1)%pattern.length, 0];
+    }
+
+    // We do a line on a even pattern index and just move on a odd pattern index.
+    // The move is the empty space in the dash.
+    if(patternIndex % 2 === 0) {
+      ctx.lineTo(x, 0);
+    } else {
+      ctx.moveTo(x, 0);
+    }
+    // If we are not done, next loop process the next pattern segment, or the
+    // first segment again if we are at the end of the pattern.
+    patternIndex = (patternIndex+1) % pattern.length;
+  }
+  ctx.restore();
+};
index b87fe80..114263a 100644 (file)
@@ -67,14 +67,10 @@ Dygraph.GVizChart.prototype.getSelection = function() {
 
   if (row < 0) return selection;
 
-  var col = 1;
   var datasets = this.date_graph.layout_.datasets;
-  for (var k in datasets) {
-    if (!datasets.hasOwnProperty(k)) continue;
-    selection.push({row: row, column: col});
-    col++;
+  for (var setIdx = 0; setIdx < datasets.length; ++setIdx) {
+    selection.push({row: row, column: setIdx + 1});
   }
 
   return selection;
 };
-
index 55184ec..a3de6f7 100644 (file)
@@ -418,3 +418,21 @@ Dygraph.Interaction.nonInteractiveModel_ = {
     }
   }
 };
+
+// Default interaction model when using the range selector.
+Dygraph.Interaction.dragIsPanInteractionModel = {
+  mousedown: function(event, g, context) {
+    context.initializeMouseDown(event, g, context);
+    Dygraph.startPan(event, g, context);
+  },
+  mousemove: function(event, g, context) {
+    if (context.isPanning) {
+      Dygraph.movePan(event, g, context);
+    }
+  },
+  mouseup: function(event, g, context) {
+    if (context.isPanning) {
+      Dygraph.endPan(event, g, context);
+    }
+  }
+};
index 328da81..1563817 100644 (file)
@@ -32,6 +32,7 @@
 var DygraphLayout = function(dygraph) {
   this.dygraph_ = dygraph;
   this.datasets = [];
+  this.setNames = [];
   this.annotations = [];
   this.yAxes_ = null;
 
@@ -46,7 +47,8 @@ DygraphLayout.prototype.attr_ = function(name) {
 };
 
 DygraphLayout.prototype.addDataset = function(setname, set_xy) {
-  this.datasets[setname] = set_xy;
+  this.datasets.push(set_xy);
+  this.setNames.push(setname);
 };
 
 DygraphLayout.prototype.getPlotArea = function() {
@@ -162,9 +164,8 @@ DygraphLayout.prototype._evaluateLimits = function() {
     this.minxval = this.dateWindow_[0];
     this.maxxval = this.dateWindow_[1];
   } else {
-    for (var name in this.datasets) {
-      if (!this.datasets.hasOwnProperty(name)) continue;
-      var series = this.datasets[name];
+    for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+      var series = this.datasets[setIdx];
       if (series.length > 1) {
         var x1 = series[0][0];
         if (!this.minxval || x1 < this.minxval) this.minxval = x1;
@@ -212,13 +213,14 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
   // for every data set since the points are added in order of the sets in
   // datasets.
   this.setPointsLengths = [];
+  this.setPointsOffsets = [];
 
-  for (var setName in this.datasets) {
-    if (!this.datasets.hasOwnProperty(setName)) continue;
-
-    var dataset = this.datasets[setName];
+  for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+    var dataset = this.datasets[setIdx];
+    var setName = this.setNames[setIdx];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
 
+    this.setPointsOffsets.push(this.points.length);
     var setPointsLength = 0;
 
     for (var j = 0; j < dataset.length; j++) {
@@ -283,10 +285,10 @@ DygraphLayout.prototype.evaluateWithError = function() {
 
   // Copy over the error terms
   var i = 0;  // index in this.points
-  for (var setName in this.datasets) {
-    if (!this.datasets.hasOwnProperty(setName)) continue;
+  for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
     var j = 0;
-    var dataset = this.datasets[setName];
+    var dataset = this.datasets[setIdx];
+    var setName = this.setNames[setIdx];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
     for (j = 0; j < dataset.length; j++, i++) {
       var item = dataset[j];
@@ -340,7 +342,13 @@ DygraphLayout.prototype._evaluateAnnotations = function() {
  */
 DygraphLayout.prototype.removeAllDatasets = function() {
   delete this.datasets;
+  delete this.setNames;
+  delete this.setPointsLengths;
+  delete this.setPointsOffsets;
   this.datasets = [];
+  this.setNames = [];
+  this.setPointsLengths = [];
+  this.setPointsOffsets = [];
 };
 
 /**
index 94461a5..a5869fa 100644 (file)
@@ -275,6 +275,13 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "example": "0.5, 2.0",
     "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs."
   },
+  "strokePattern": {
+    "default": "null",
+    "labels": ["Data Line display"],
+    "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."
+  },
   "wilsonInterval": {
     "default": "true",
     "labels": ["Error Bars"],
@@ -310,7 +317,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "labels": ["Axis display"],
     "type": "Array of two numbers",
     "example": "[10, 110]",
-    "description": "Explicitly set the vertical range of the graph to [low, high]."
+    "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately."
   },
   "labelsDivWidth": {
     "default": "250",
index fe69864..9ae04d0 100644 (file)
@@ -255,7 +255,12 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
         return e.srcElement == self.iePanOverlay_;
     } else {
       // Getting clientX directly from the event is not accurate enough :(
-      var clientX = self.canvasRect_.x + (e.layerX !== undefined ? e.layerX : e.offsetX);
+      var clientX;
+      if (e.offsetX != undefined) {
+        clientX = self.canvasRect_.x + e.offsetX;
+      } else {
+        clientX = e.clientX;
+      }
       var zoomHandleStatus = self.getZoomHandleStatus_();
       return (clientX > zoomHandleStatus.leftHandlePos && clientX < zoomHandleStatus.rightHandlePos);
     }
@@ -342,24 +347,8 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  var interactionModel = {
-    mousedown: function(event, g, context) {
-      context.initializeMouseDown(event, g, context);
-      Dygraph.startPan(event, g, context);
-    },
-    mousemove: function(event, g, context) {
-      if (context.isPanning) {
-        Dygraph.movePan(event, g, context);
-      }
-    },
-    mouseup: function(event, g, context) {
-      if (context.isPanning) {
-        Dygraph.endPan(event, g, context);
-      }
-    }
-  };
-
-  this.dygraph_.attrs_.interactionModel = interactionModel;
+  this.dygraph_.attrs_.interactionModel =
+      Dygraph.Interaction.dragIsPanInteractionModel;
   this.dygraph_.attrs_.panEdgeFraction = 0.0001;
 
   var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
index 0ae3f08..9f4fcb0 100644 (file)
@@ -35,6 +35,13 @@ Dygraph.ERROR = 3;
 // https://github.com/eriwen/javascript-stacktrace
 Dygraph.LOG_STACK_TRACES = false;
 
+/** A dotted line stroke pattern. */
+Dygraph.DOTTED_LINE = [2, 2];
+/** A dashed line stroke pattern. */
+Dygraph.DASHED_LINE = [7, 3];
+/** A dot dash stroke pattern. */
+Dygraph.DOT_DASH_LINE = [7, 2, 2, 2];
+
 /**
  * @private
  * Log an error on the JS console at the given severity.
@@ -778,6 +785,29 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
   return requiresNewPoints;
 };
 
+/**
+ * Compares two arrays to see if they are equal. If either parameter is not an
+ * array it will return false. Does a shallow compare 
+ * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false.
+ * @param array1 first array
+ * @param array2 second array
+ * @return True if both parameters are arrays, and contents are equal.
+ */
+Dygraph.compareArrays = function(array1, array2) {
+  if (!Dygraph.isArrayLike(array1) || !Dygraph.isArrayLike(array2)) {
+    return false;
+  }
+  if (array1.length !== array2.length) {
+    return false;
+  }
+  for (var i = 0; i < array1.length; i++) {
+    if (array1[i] !== array2[i]) {
+      return false;
+    }
+  }
+  return true;
+};
+
 
 Dygraph.RegularConvex = function(sides, rotation) {
   this.sides = sides;
index f03defc..51d66ba 100644 (file)
@@ -397,6 +397,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   Dygraph.updateDeep(this.attrs_, Dygraph.DEFAULT_ATTRS);
 
   this.boundaryIds_ = [];
+  this.setIndexByName_ = {};
 
   // Create the containing DIV and other interactive elements
   this.createInterface_();
@@ -840,11 +841,13 @@ Dygraph.prototype.createInterface_ = function() {
   this.createStatusMessage_();
   this.createDragInterface_();
 
+  this.resizeHandler = function(e) {
+    dygraph.resize();
+  }
+
   // Update when the window is resized.
   // TODO(danvk): drop frames depending on complexity of the chart.
-  Dygraph.addEvent(window, 'resize', function(e) {
-    dygraph.resize();
-  });
+  Dygraph.addEvent(window, 'resize', this.resizeHandler);
 };
 
 /**
@@ -868,7 +871,9 @@ Dygraph.prototype.destroy = function() {
       }
     }
   };
-
+  // remove event handlers
+  Dygraph.removeEvent(window,'resize',this.resizeHandler);
+  this.resizeHandler = null;
   // These may not all be necessary, but it can't hurt...
   nullOut(this.layout_);
   nullOut(this.plotter_);
@@ -1327,7 +1332,7 @@ Dygraph.prototype.doUnzoom_ = function() {
   }
 
   for (var i = 0; i < this.axes_.length; i++) {
-    if (this.axes_[i].valueWindow !== null) {
+    if (typeof(this.axes_[i].valueWindow) !== 'undefined' && this.axes_[i].valueWindow !== null) {
       dirty = true;
       dirtyY = true;
     }
@@ -1379,7 +1384,8 @@ Dygraph.prototype.doUnzoom_ = function() {
 
       newValueRanges = [];
       for (i = 0; i < this.axes_.length; i++) {
-        newValueRanges.push(this.axes_[i].extremeRange);
+        var axis = this.axes_[i];
+        newValueRanges.push(axis.valueRange != null ? axis.valueRange : axis.extremeRange);
       }
     }
 
@@ -1526,29 +1532,113 @@ Dygraph.prototype.mouseMove_ = function(event) {
 Dygraph.prototype.idxToRow_ = function(idx) {
   if (idx < 0) return -1;
 
-  for (var i in this.layout_.datasets) {
-    if (idx < this.layout_.datasets[i].length) {
-      return this.boundaryIds_[0][0]+idx;
+  // make sure that you get the boundaryIds record which is also defined (see bug #236)
+  var boundaryIdx = -1;
+  for (var i = 0; i < this.boundaryIds_.length; i++) {
+    if (this.boundaryIds_[i] !== undefined) {
+      boundaryIdx = i;
+      break;
+    }
+  }
+  if (boundaryIdx < 0) return -1;
+  for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+    var set = this.layout_.datasets[setIdx];
+    if (idx < set.length) {
+      return this.boundaryIds_[boundaryIdx][0] + idx;
     }
-    idx -= this.layout_.datasets[i].length;
+    idx -= set.length;
   }
   return -1;
 };
 
 /**
  * @private
+ * Generates legend html dash for any stroke pattern. It will try to scale the
+ * pattern to fit in 1em width. Or if small enough repeat the partern for 1em
+ * width.
+ * @param strokePattern The pattern
+ * @param color The color of the series.
+ * @param oneEmWidth The width in pixels of 1em in the legend.
+ */
+Dygraph.prototype.generateLegendDashHTML_ = function(strokePattern, color, oneEmWidth) {
+  var dash = "";
+  var i, j, paddingLeft, marginRight;
+  var strokePixelLength = 0, segmentLoop = 0;
+  var normalizedPattern = [];
+  var loop;
+  // IE 7,8 fail at these divs, so they get boring legend, have not tested 9.
+  var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
+  if(isIE) {
+    return "&mdash;";
+  }
+  if (!strokePattern || strokePattern.length <= 1) {
+    // Solid line
+    dash = "<div style=\"display: inline-block; position: relative; " +
+    "bottom: .5ex; padding-left: 1em; height: 1px; " +
+    "border-bottom: 2px solid " + color + ";\"></div>";
+  } else {
+    // Compute the length of the pixels including the first segment twice, 
+    // since we repeat it.
+    for (i = 0; i <= strokePattern.length; i++) {
+      strokePixelLength += strokePattern[i%strokePattern.length];
+    }
+
+    // See if we can loop the pattern by itself at least twice.
+    loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0]));
+    if (loop > 1) {
+      // This pattern fits at least two times, no scaling just convert to em;
+      for (i = 0; i < strokePattern.length; i++) {
+        normalizedPattern[i] = strokePattern[i]/oneEmWidth;
+      }
+      // Since we are repeating the pattern, we don't worry about repeating the
+      // first segment in one draw.
+      segmentLoop = normalizedPattern.length;
+    } else {
+      // If the pattern doesn't fit in the legend we scale it to fit.
+      loop = 1;
+      for (i = 0; i < strokePattern.length; i++) {
+        normalizedPattern[i] = strokePattern[i]/strokePixelLength;
+      }
+      // For the scaled patterns we do redraw the first segment.
+      segmentLoop = normalizedPattern.length+1;
+    }
+    // Now make the pattern.
+    for (j = 0; j < loop; j++) {
+      for (i = 0; i < segmentLoop; i+=2) {
+        // The padding is the drawn segment.
+        paddingLeft = normalizedPattern[i%normalizedPattern.length];
+        if (i < strokePattern.length) {
+          // The margin is the space segment.
+          marginRight = normalizedPattern[(i+1)%normalizedPattern.length];
+        } else {
+          // The repeated first segment has no right margin.
+          marginRight = 0;
+        }
+        dash += "<div style=\"display: inline-block; position: relative; " +
+          "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " +
+          paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color +
+          ";\"></div>";
+      }
+    }
+  }
+  return dash;
+};
+
+/**
+ * @private
  * Generates HTML for the legend which is displayed when hovering over the
  * chart. If no selected points are specified, a default legend is returned
  * (this may just be the empty string).
  * @param { Number } [x] The x-value of the selected points.
  * @param { [Object] } [sel_points] List of selected points for the given
  * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ * @param { Number } [oneEmWidth] The pixel width for 1em in the legend.
  */
-Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
+Dygraph.prototype.generateLegendHTML_ = function(x, sel_points, oneEmWidth) {
   // If no points are selected, we display a default legend. Traditionally,
   // this has been blank. But a better default would be a conventional legend,
   // which provides essential information for a non-interactive chart.
-  var html, sepLines, i, c;
+  var html, sepLines, i, c, dash, strokePattern;
   if (typeof(x) === 'undefined') {
     if (this.attr_('legend') != 'always') return '';
 
@@ -1559,8 +1649,10 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
       if (!this.visibility()[i - 1]) continue;
       c = this.plotter_.colors[labels[i]];
       if (html !== '') html += (sepLines ? '<br/>' : ' ');
-      html += "<b><span style='color: " + c + ";'>&mdash;" + labels[i] +
-        "</span></b>";
+      strokePattern = this.attr_("strokePattern", labels[i]);
+      dash = this.generateLegendDashHTML_(strokePattern, c, oneEmWidth);
+      html += "<span style='font-weight: bold; color: " + c + ";'>" + dash + 
+        " " + labels[i] + "</span>";
     }
     return html;
   }
@@ -1603,8 +1695,14 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
  * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
  */
 Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
-  var html = this.generateLegendHTML_(x, sel_points);
   var labelsDiv = this.attr_("labelsDiv");
+  var sizeSpan = document.createElement('span');
+  // Calculates the width of 1em in pixels for the legend.
+  sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;');
+  labelsDiv.appendChild(sizeSpan);
+  var oneEmWidth=sizeSpan.offsetWidth;
+
+  var html = this.generateLegendHTML_(x, sel_points, oneEmWidth);
   if (labelsDiv !== null) {
     labelsDiv.innerHTML = html;
   } else {
@@ -1685,8 +1783,9 @@ Dygraph.prototype.setSelection = function(row) {
   }
 
   if (row !== false && row >= 0) {
-    for (var i in this.layout_.datasets) {
-      if (row < this.layout_.datasets[i].length) {
+    for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+      var set = this.layout_.datasets[setIdx];
+      if (row < set.length) {
         var point = this.layout_.points[pos+row];
 
         if (this.attr_("stackedGraph")) {
@@ -1695,7 +1794,7 @@ Dygraph.prototype.setSelection = function(row) {
 
         this.selPoints_.push(point);
       }
-      pos += this.layout_.datasets[i].length;
+      pos += set.length;
     }
   }
 
@@ -2021,9 +2120,15 @@ Dygraph.prototype.drawGraph_ = function(clearSelection) {
   var extremes = packed[1];
   this.boundaryIds_ = packed[2];
 
+  this.setIndexByName_ = {};
+  var labels = this.attr_("labels");
+  if (labels.length > 0) {
+    this.setIndexByName_[labels[0]] = 0;
+  }
   for (var i = 1; i < datasets.length; i++) {
+    this.setIndexByName_[labels[i]] = i;
     if (!this.visibility()[i - 1]) continue;
-    this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
+    this.layout_.addDataset(labels[i], datasets[i]);
   }
 
   this.computeYAxisRanges_(extremes);
@@ -2092,7 +2197,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw, clearSelection) {
 Dygraph.prototype.computeYAxes_ = function() {
   // Preserve valueWindow settings if they exist, and if the user hasn't
   // specified a new valueRange.
-  var i, valueWindows, seriesName, axis, index;
+  var i, valueWindows, seriesName, axis, index, opts, v;
   if (this.axes_ !== undefined && this.user_attrs_.hasOwnProperty("valueRange") === false) {
     valueWindows = [];
     for (index = 0; index < this.axes_.length; index++) {
@@ -2124,7 +2229,7 @@ Dygraph.prototype.computeYAxes_ = function() {
   // Copy global axis options over to the first axis.
   for (i = 0; i < axisOptions.length; i++) {
     var k = axisOptions[i];
-    var v = this.attr_(k);
+    v = this.attr_(k);
     if (v) this.axes_[0][k] = v;
   }
 
@@ -2138,7 +2243,7 @@ Dygraph.prototype.computeYAxes_ = function() {
     }
     if (typeof(axis) == 'object') {
       // Add a new axis, making a copy of its per-axis options.
-      var opts = {};
+      opts = {};
       Dygraph.update(opts, this.axes_[0]);
       Dygraph.update(opts, { valueRange: null });  // shouldn't inherit this.
       var yAxisId = this.axes_.length;
@@ -2172,6 +2277,22 @@ Dygraph.prototype.computeYAxes_ = function() {
       this.axes_[index].valueWindow = valueWindows[index];
     }
   }
+
+  // New axes options
+  for (axis = 0; axis < this.axes_.length; axis++) {
+    if (axis === 0) {
+      opts = this.optionsViewForAxis_('y' + (axis ? '2' : ''));
+      v = opts("valueRange");
+      if (v) this.axes_[axis].valueRange = v;
+    } else {  // To keep old behavior
+      var axes = this.user_attrs_.axes;
+      if (axes && axes.y2) {
+        v = axes.y2.valueRange;
+        if (v) this.axes_[axis].valueRange = v;
+      }
+    }
+  }
+
 };
 
 /**
@@ -2776,6 +2897,19 @@ Dygraph.prototype.parseArray_ = function(data) {
  * @private
  */
 Dygraph.prototype.parseDataTable_ = function(data) {
+  var shortTextForAnnotationNum = function(num) {
+    // converts [0-9]+ [A-Z][a-z]*
+    // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab
+    // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz
+    var shortText = String.fromCharCode(65 /* A */ + num % 26);
+    num = Math.floor(num / 26);
+    while ( num > 0 ) {
+      shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26 ) + shortText.toLowerCase();
+      num = Math.floor((num - 1) / 26);
+    }
+    return shortText;
+  }
+
   var cols = data.getNumberOfColumns();
   var rows = data.getNumberOfRows();
 
@@ -2857,7 +2991,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
           var ann = {};
           ann.series = data.getColumnLabel(col);
           ann.xval = row[0];
-          ann.shortText = String.fromCharCode(65 /* A */ + annotations.length);
+          ann.shortText = shortTextForAnnotationNum(annotations.length);
           ann.text = '';
           for (var k = 0; k < annotationCols[col].length; k++) {
             if (k) ann.text += "\n";
@@ -3164,15 +3298,19 @@ Dygraph.prototype.annotations = function() {
 };
 
 /**
+ * Get the list of label names for this graph. The first column is the
+ * x-axis, so the data series names start at index 1.
+ */
+Dygraph.prototype.getLabels = function(name) {
+  return this.attr_("labels").slice();
+};
+
+/**
  * Get the index of a series (column) given its name. The first column is the
  * x-axis, so the data series start with index 1.
  */
 Dygraph.prototype.indexFromSetName = function(name) {
-  var labels = this.attr_("labels");
-  for (var i = 0; i < labels.length; i++) {
-    if (labels[i] == name) return i;
-  }
-  return null;
+  return this.setIndexByName_[name];
 };
 
 /**
index 38a5b68..08efc6f 100644 (file)
@@ -62,6 +62,12 @@ function offsetToPercentage(g, offsetX, offsetY) {
 function dblClickV3(event, g, context) {
   // Reducing by 20% makes it 80% the original size, which means
   // to restore to original size it must grow by 25%
+
+  if (!(event.offsetX && event.offsetY)){
+    event.offsetX = event.layerX - event.target.offsetLeft;
+    event.offsetY = event.layerY - event.target.offsetTop;
+  }
+
   var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
   var xPct = percentages[0];
   var yPct = percentages[1];
@@ -89,6 +95,11 @@ function scrollV3(event, g, context) {
   // that verbatim, it would be a 7.5%.
   var percentage = normal / 50;
 
+  if (!(event.offsetX && event.offsetY)){
+    event.offsetX = event.layerX - event.target.offsetLeft;
+    event.offsetY = event.layerY - event.target.offsetTop;
+  }
+
   var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
   var xPct = percentages[0];
   var yPct = percentages[1];
index 33bb7a9..c5826cb 100755 (executable)
@@ -18,7 +18,7 @@ if [ -s docs/options.html ] ; then
   ./generate-jsdoc.sh
 
   # Copy everything to the site.
-  scp -r gallery tests jsdoc experimental $site \
+  scp -r gallery common tests jsdoc experimental $site \
   && \
   scp dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/
 else
index c043894..ec458b0 100644 (file)
         data.addColumn('string', 'title2');
         data.addColumn('string', 'text2');
         data.addRows([
-          [new Date(2008, 1 ,1), 30000, undefined, undefined, 40645, undefined, undefined],
-          [new Date(2008, 1 ,2), 14045, undefined, undefined, 20374, undefined, undefined],
-          [new Date(2008, 1 ,3), 55022, undefined, undefined, 50766, undefined, undefined],
-          [new Date(2008, 1 ,4), 75284, undefined, undefined, 14334, 'Out of Stock','Ran out of stock on pens at 4pm'],
-          [new Date(2008, 1 ,5), 41476, 'Bought Pens','Bought 200k pens', 66467, undefined, undefined],
-          [new Date(2008, 1 ,6), 33322, undefined, undefined, 39463, undefined, undefined]
+          [new Date(2008, 11 ,1), 30000, undefined, undefined, 40645, undefined, undefined],
+          [new Date(2008, 11 ,2), 14045, undefined, undefined, 20374, undefined, undefined],
+          [new Date(2008, 11 ,3), 55022, undefined, undefined, 50766, undefined, undefined],
+          [new Date(2008, 11 ,4), 75284, undefined, undefined, 14334, 'Out of Stock','Ran out of stock on pens at 4pm'],
+          [new Date(2008, 11 ,5), 41476, 'Bought Pens','Bought 200k pens', 66467, undefined, undefined],
+          [new Date(2008, 11 ,6), 33322, undefined, undefined, 39463, undefined, undefined]
         ]);
 
+        for (var i = 1; i < 14; i++) {
+          data.addRows([
+            [new Date(2008, 11 , 6 + i), i * 1000, 'title1-' + i, 'text1 ' + (i * 1000 - i), (i * 2000 + i), 'title2-' + i, 'text2' + i * 1000],
+          ]);
+        }
+
         var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('gviz_div'));
         chart.draw(data, {displayAnnotations: true});
 
@@ -50,4 +56,3 @@
     <div id='dg_div' style='width: 700px; height: 240px;'></div>
   </body>
 </html>
-
index 7dc69f7..7aadb19 100644 (file)
   <body>
     <p>Plot which can be easily generated with different numbers of points for
        benchmarking/profiling and improving performance of dygraphs.</p>    
-    <p>Data to plot:
-      <input type="radio" id="sine" name="group1" value="sine"
-        onclick="clickedRadioButton(this);" checked> sinusoid function
-      <input type="radio" id="rand" name="group1" value="rand"
-        onclick="clickedRadioButton(this);"> random points <br></p>
-    <p>Number of points:
-       <input type="text" id="points" size="20"></p>
-    <p>Number of series:
-       <input type="text" id="series" size="20"></p>
-    <p>Roll period (in points):
-      <input type="text" id="rollPeriod" size="20"></p>
-    <p>Repetitions:
-      <input type="text" id="repetitions" size="20"></p>
-
-    <input type="button" value="Go!" onclick="updatePlot();">
+    <div id='parameters'>
+      <p>Data to plot:
+        <input type="radio" id="sine" name="group1" value="sine"
+          onclick="clickedRadioButton(this);" checked> sinusoid function
+        <input type="radio" id="rand" name="group1" value="rand"
+          onclick="clickedRadioButton(this);"> random points <br></p>
+      <p>Number of points:
+         <input type="text" id="points" size="20"></p>
+      <p>Number of series:
+         <input type="text" id="series" size="20"></p>
+      <p>Roll period (in points):
+        <input type="text" id="rollPeriod" size="20"></p>
+      <p>Repetitions:
+        <input type="text" id="repetitions" size="20"></p>
+      <input type="button" value="Go!" onclick="updatePlot();">
+    </div>
     <br>
     <br>
     <div id="plot"></div>
@@ -39,7 +40,8 @@
     <div id="metaperformance"></div>
 
     <script type="text/javascript">
-      var plot;
+      var graph = null;
+      var metrics = null;
       var dataType = "sine";
 
       var durations = [];
         var opts = {labels: labels, rollPeriod: rollPeriod, timingName: "x"};
         var millisecondss = [];
         for (var i = 0; i < repetitions; i++) {
+          if (graph != null) {
+            graph.destroy(); // release memory from prior graph.
+          }
           var start = new Date();
-          plot = new Dygraph(plotDiv, data, opts);
+          graph = new Dygraph(plotDiv, data, opts);
           var end = new Date();
           durations.push([start, end - start]);
           millisecondss.push(end - start);
 
         if (durations.length > 0) {
           var start2 = new Date();
-          new Dygraph(
-              document.getElementById('metrics'),
-              durations,
-              {
-                highlightCircleSize: 4,
-                labels: [ "Date", "ms" ]
-                });
+          if (!metrics) {
+             metrics = new Dygraph(
+                document.getElementById('metrics'),
+                durations,
+                {
+                  highlightCircleSize: 4,
+                  labels: [ "Date", "ms" ]
+                  });
+          } else {
+            metrics.updateOptions({file: durations});
+          }
           var end2 = new Date();
           document.getElementById("metaperformance").innerHTML =
               "completed in " + (end2 - start2) + " milliseconds.";
index ced5aef..08efc6f 100644 (file)
@@ -62,6 +62,12 @@ function offsetToPercentage(g, offsetX, offsetY) {
 function dblClickV3(event, g, context) {
   // Reducing by 20% makes it 80% the original size, which means
   // to restore to original size it must grow by 25%
+
+  if (!(event.offsetX && event.offsetY)){
+    event.offsetX = event.layerX - event.target.offsetLeft;
+    event.offsetY = event.layerY - event.target.offsetTop;
+  }
+
   var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
   var xPct = percentages[0];
   var yPct = percentages[1];
@@ -89,6 +95,11 @@ function scrollV3(event, g, context) {
   // that verbatim, it would be a 7.5%.
   var percentage = normal / 50;
 
+  if (!(event.offsetX && event.offsetY)){
+    event.offsetX = event.layerX - event.target.offsetLeft;
+    event.offsetY = event.layerY - event.target.offsetTop;
+  }
+
   var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
   var xPct = percentages[0];
   var yPct = percentages[1];
@@ -184,7 +195,7 @@ function upV4(event, g, context) {
 }
 
 function dblClickV4(event, g, context) {
-  restorePositioning(g4);
+  restorePositioning(g);
 }
 
 function drawV4(x, y) {
index c1b400a..98501ac 100644 (file)
   <body>
     <h2>Chart with per-series properties</h2>
     <div id="demodiv"></div>
-
+    <h2>Chart with per-series properties with legend.</h2>
+    <div id="demodiv2"></div>
     <script type="text/javascript">
+      data = function() {
+        var zp = function(x) { if (x < 10) return "0"+x; else return x; };
+        var r = "date,parabola,line,another line,sine wave,sine wave2\n";
+        for (var i=1; i<=31; i++) {
+          r += "200610" + zp(i);
+          r += "," + 10*(i*(31-i));
+          r += "," + 10*(8*i);
+          r += "," + 10*(250 - 8*i);
+          r += "," + 10*(125 + 125 * Math.sin(0.3*i));
+          r += "," + 10*(125 + 125 * Math.sin(0.3*i+Math.PI));
+          r += "\n";
+        }
+        return r;
+      };
       g = new Dygraph(
               document.getElementById("demodiv"),
-              function() {
-                var zp = function(x) { if (x < 10) return "0"+x; else return x; };
-                var r = "date,parabola,line,another line,sine wave\n";
-                for (var i=1; i<=31; i++) {
-                r += "200610" + zp(i);
-                r += "," + 10*(i*(31-i));
-                r += "," + 10*(8*i);
-                r += "," + 10*(250 - 8*i);
-                r += "," + 10*(125 + 125 * Math.sin(0.3*i));
-                r += "\n";
-                }
-                return r;
-              },
+              data,
               {
                 strokeWidth: 2,
                 'parabola': {
                 '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': {
+                  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
                 }
               }
           );
+
     </script>
 </body>
 </html>
diff --git a/tests/two-axes-vr.html b/tests/two-axes-vr.html
new file mode 100644 (file)
index 0000000..5814435
--- /dev/null
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Multiple y-axes with valueRange</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <!--
+    For production (minified) code, use:
+    <script type="text/javascript" src="dygraph-combined.js"></script>
+    -->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+
+  </head>
+  <body>
+    <h2>Multiple y-axes with valueRange</h2>
+    <p>The same data with both different valueRanges. Two-axis old y[40, 70] valueRange:</p>
+    <div id="demodiv" style="width: 640; height: 350; border: 1px solid black"></div>
+    <p>Two-axis new valueRange y[40, 80] set:</p>
+    <div id="demodiv_one" style="width: 640; height: 350; border: 1px solid black"></div>
+    <p>Two-axis new valueRange y[40, 80] &amp; y2[1e6, 1.2e6] set:</p>
+    <div id="demodiv_two" style="width: 640; height: 350; border: 1px solid black"></div>
+    <script type="text/javascript">
+      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))]);
+      }
+
+      g = new Dygraph(
+          document.getElementById("demodiv"),
+          data,
+          {
+            labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+            'Y3': {
+              axis: {
+              }
+            },
+            'Y4': {
+              axis: 'Y3'  // use the same y-axis as series Y3
+            },
+            valueRange: [40, 70],
+            axes: {
+              y2: {
+                // set axis-related properties here
+                labelsKMB: true
+              }
+            },
+            ylabel: 'Primary y-axis',
+            y2label: 'Secondary y-axis',
+            yAxisLabelWidth: 60
+          }
+      );
+
+      g2 = new Dygraph(
+          document.getElementById("demodiv_one"),
+          data,
+          {
+            labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+            'Y3': {
+              axis: {
+              }
+            },
+            'Y4': {
+              axis: 'Y3'  // use the same y-axis as series Y3
+            },
+            axes: {
+              y: {
+                valueRange: [40, 80]
+              },
+              y2: {
+                // set axis-related properties here
+                labelsKMB: true
+              }
+            },
+            ylabel: 'Primary y-axis',
+            y2label: 'Secondary y-axis',
+            yAxisLabelWidth: 60
+          }
+      );
+
+      g2 = new Dygraph(
+          document.getElementById("demodiv_two"),
+          data,
+          {
+            labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+            'Y3': {
+              axis: {
+              }
+            },
+            'Y4': {
+              axis: 'Y3'  // use the same y-axis as series Y3
+            },
+            axes: {
+              y: {
+                valueRange: [40, 80]
+              },
+              y2: {
+                // set axis-related properties here
+                valueRange: [1e6, 1.2e6],
+                labelsKMB: true
+              }
+            },
+            ylabel: 'Primary y-axis',
+            y2label: 'Secondary y-axis',
+            yAxisLabelWidth: 60
+          }
+      );
+
+
+    </script>
+</body>
+</html>