Merge branch 'master' of github.com:danvk/dygraphs
authorDan Vanderkam <dan@dygraphs.com>
Sun, 4 Mar 2012 14:48:22 +0000 (09:48 -0500)
committerDan Vanderkam <dan@dygraphs.com>
Sun, 4 Mar 2012 14:48:22 +0000 (09:48 -0500)
36 files changed:
auto_tests/tests/callback.js
docs/index.html
dygraph-canvas.js
dygraph-options-reference.js
dygraph-utils.js
dygraph.js
gallery/annotation-gviz.js [deleted file]
gallery/annotation-native.js [deleted file]
gallery/annotation.js [deleted file]
gallery/annotations-gviz.js [new file with mode: 0644]
gallery/annotations-native.js [new file with mode: 0644]
gallery/annotations.js [new file with mode: 0644]
gallery/avoid-min-zero.js [new file with mode: 0644]
gallery/avoidMinZero.js [deleted file]
gallery/border.js
gallery/callback.js [deleted file]
gallery/callbacks.js [new file with mode: 0644]
gallery/gallery.css
gallery/highlighted-series.js [new file with mode: 0644]
gallery/independent-series.js
gallery/index.html
gallery/linear-regression.js
gallery/link-interaction.js
gallery/per-series.js
gallery/plotter.js
gallery/synchronize.js
generate-documentation.py
tests/crosshair.html [deleted file]
tests/csv-numeric-x.html [deleted file]
tests/custom-circles.html
tests/customLabelCss3.html [new file with mode: 0644]
tests/noise.html [deleted file]
tests/number-display.html [deleted file]
tests/series-highlight.html [new file with mode: 0644]
tests/two-series.html [deleted file]
tests/y-axis-formatter.html [deleted file]

index 3431070..ceb9963 100644 (file)
@@ -1,4 +1,4 @@
-/** 
+/**
  * @fileoverview Test cases for the callbacks.
  *
  * @author uemit.seren@gmail.com (Ümit Seren)
@@ -7,40 +7,43 @@
 var CallbackTestCase = TestCase("callback");
 
 CallbackTestCase.prototype.setUp = function() {
-  document.body.innerHTML = "<div id='graph'></div>";
+  document.body.innerHTML = "<div id='graph'></div><div id='selection'></div>";
+  this.styleSheet = document.createElement("style");
+  this.styleSheet.type = "text/css";
+  document.getElementsByTagName("head")[0].appendChild(this.styleSheet);
 };
 
 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";
+ "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) 
- * 
+ * 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) {
+  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,
+        height: 100,
         visibility: [false, true, true],
-        highlightCallback : highlightCallback,
+        highlightCallback: highlightCallback
       });
 
   DygraphOps.dispatchMouseMove(g, 13, 10);
@@ -60,7 +63,7 @@ CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
 
   var callback = function() {
     called = true;
-  }; 
+  };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
@@ -78,7 +81,7 @@ CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
 
   var callback = function() {
     called = true;
-  }; 
+  };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
@@ -99,7 +102,7 @@ CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
   var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
     pointSize = pointSizeParam;
     count++;
-  }; 
+  };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
@@ -121,15 +124,15 @@ CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
 
 /**
  * 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) 
- * 
+ * is properly called when the first series is hidden (setVisibility = false)
+ *
  */
 CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
   var called = false;
 
   var drawHighlightPointCallback  = function() {
     called = true;
-  }; 
+  };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data,
@@ -143,3 +146,165 @@ CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
   DygraphOps.dispatchMouseMove(g, 13, 10);
   assertTrue(called);
 };
+
+/**
+ * Test the closest-series highlighting methods for normal and stacked modes.
+ * Also pass in line widths for plain and highlighted lines for easier visual
+ * confirmation that the highlighted line is drawn on top of the others.
+ */
+var runClosestTest = function(isStacked, widthNormal, widthHighlighted) {
+  var h_row;
+  var h_pts;
+  var h_series;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data,
+      {
+        width: 600,
+        height: 400,
+        visibility: [false, true, true],
+        stackedGraph: isStacked,
+        strokeWidth: widthNormal,
+        strokeBorderWidth: 2,
+        highlightCircleSize: widthNormal * 2,
+        highlightSeriesBackgroundAlpha: 0.3,
+
+        highlightSeriesOpts: {
+          strokeWidth: widthHighlighted,
+          highlightCircleSize: widthHighlighted * 2
+        }
+      });
+
+  var highlightCallback  =  function(e, x, pts, row, set) {
+    h_row = row;
+    h_pts = pts;
+    h_series = set;
+    document.getElementById('selection').innerHTML='row=' + row + ', set=' + set;
+  };
+
+  g.updateOptions({highlightCallback: highlightCallback}, true);
+
+  if (isStacked) {
+    DygraphOps.dispatchMouseMove(g, 11.45, 1.4);
+    assertEquals(1, h_row);
+    assertEquals('c', h_series);
+
+    //now move up in the same row
+    DygraphOps.dispatchMouseMove(g, 11.45, 1.5);
+    assertEquals(1, h_row);
+    assertEquals('b', h_series);
+
+    //and a bit to the right
+    DygraphOps.dispatchMouseMove(g, 11.55, 1.5);
+    assertEquals(2, h_row);
+    assertEquals('c', h_series);
+  } else {
+    DygraphOps.dispatchMouseMove(g, 11, 1.5);
+    assertEquals(1, h_row);
+    assertEquals('c', h_series);
+
+    //now move up in the same row
+    DygraphOps.dispatchMouseMove(g, 11, 2.5);
+    assertEquals(1, h_row);
+    assertEquals('b', h_series);
+  }
+
+  return g;
+};
+
+/**
+ * Test basic closest-point highlighting.
+ */
+CallbackTestCase.prototype.testClosestPointCallback = function() {
+  runClosestTest(false, 1, 3);
+}
+
+/**
+ * Test setSelection() with series name
+ */
+CallbackTestCase.prototype.testSetSelection = function() {
+  var g = runClosestTest(false, 1, 3);
+  assertEquals(1, g.attr_('strokeWidth', 'c'));
+  g.setSelection(false, 'c');
+  assertEquals(3, g.attr_('strokeWidth', 'c'));
+}
+
+/**
+ * Test closest-point highlighting for stacked graph
+ */
+CallbackTestCase.prototype.testClosestPointStackedCallback = function() {
+  runClosestTest(true, 1, 3);
+}
+
+/**
+ * Closest-point highlighting with legend CSS - border around active series.
+ */
+CallbackTestCase.prototype.testClosestPointCallbackCss1 = function() {
+  var css = "div.dygraph-legend > span { display: block; }\n" +
+    "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n";
+  this.styleSheet.innerHTML = css;
+  runClosestTest(false, 2, 4);
+  this.styleSheet.innerHTML = '';
+}
+
+/**
+ * Closest-point highlighting with legend CSS - show only closest series.
+ */
+CallbackTestCase.prototype.testClosestPointCallbackCss2 = function() {
+  var css = "div.dygraph-legend > span { display: none; }\n" +
+    "div.dygraph-legend > span.highlight { display: inline; }\n";
+  this.styleSheet.innerHTML = css;
+  runClosestTest(false, 10, 15);
+  this.styleSheet.innerHTML = '';
+  // TODO(klausw): verify that the highlighted line is drawn on top?
+}
+
+/**
+ * This tests that closest point searches work for data containing NaNs.
+ *
+ * It's intended to catch a regression where a NaN Y value confuses the
+ * closest-point algorithm, treating it as closer as any previous point.
+ */
+CallbackTestCase.prototype.testNaNData = function() {
+  var dataNaN = [
+    [9, -1, NaN, NaN],
+    [10, -1, 1, 2],
+    [11, 0, 3, 1],
+    [12, 1, 4, NaN],
+    [13, 0, 2, 3],
+    [14, -1, 1, 4]];
+
+  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, dataNaN,
+      {
+        width: 600,
+        height: 400,
+        labels: ['x', 'a', 'b', 'c'],
+        visibility: [false, true, true],
+        highlightCallback: highlightCallback
+      });
+
+  DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
+  //check correct row is returned
+  assertEquals(1, h_row);
+
+  // Explicitly test closest point algorithms
+  var dom = g.toDomCoords(10.1, 0.9);
+  assertEquals(1, g.findClosestRow(dom[0]));
+
+  var res = g.findClosestPoint(dom[0], dom[1]);
+  assertEquals(1, res.row);
+  assertEquals('b', res.seriesName);
+
+  res = g.findStackedPoint(dom[0], dom[1]);
+  assertEquals(1, res.row);
+  assertEquals('c', res.seriesName);
+};
index 5c2444f..9b77708 100644 (file)
@@ -55,8 +55,9 @@
 
       <h2>Demos/Usage</h2>
       <ul>
-        <li><a href="#gallery">Usage Gallery</a></li>
+        <li><a href="#users">Known Users</a></li>
         <hr/>
+        <li><span style="color: red;">New!</span> <a href="gallery/">(browse gallery)</a></li>
         <li><a href="tests/">(browse demos)</a></li>
         <li><a href="tests/demo.html">Basic Demo</a></li>
         <li><a href="tests/gviz.html">GViz Demo</a></li>
@@ -74,7 +75,6 @@
         <li><a href="tests/grid_dot.html">Crazy Styles</a></li>
         <li><a href="tests/spacing.html">Tick spacing</a></li>
         <li><a href="tests/callback.html">Callbacks</a></li>
-        <li><a href="tests/crosshair.html">Crosshairs</a></li>
         <li><a href="tests/hourly.html">Hourly/Minutely data</a></li>
         <li><a href="tests/isolated-points.html">Isolated Points</a></li>
         <li><a href="tests/missing-data.html">Missing Data</a></li>
@@ -83,8 +83,6 @@
         <li><a href="tests/customLabel.html">Custom Label Styles</a></li>
         <li><a href="tests/dygraph.html">Minimal Example</a></li>
         <li><a href="tests/negative.html">Negative Numbers</a></li>
-        <li><a href="tests/noise.html">Noisy Data</a></li>
-        <li><a href="tests/two-series.html">Multiple Series</a></li>
         <li><a href="tests/highlighted-region.html">Custom Underlay / background</a></li>
         <li><a href="tests/zoom.html">Tests for zoom operations</a></li>
         <li><a href="tests/logscale.html">Log scale tests</a></li>
 
       <p>For more demos, browse the dygraph <a href="tests/">tests</a>
       directory. To see other people who are using dygraphs, check out the <a
-      href="#gallery">usage gallery</a>.</p>
+      href="#users">known users</a>.</p>
 
       <h3>Features</h3>
       <p>Some of the features of dygraphs:</p>
@@ -606,8 +604,8 @@ public static native JavaScriptObject drawDygraph(
 }-*/;
 </pre>
 
-    <a name="gallery" />
-    <h2>Usage Gallery</h2>
+    <a name="users" />
+    <h2>Known Users</h2>
     <p>Since its public release in late 2009, dygraphs has found many users
     across the web. This is a small collection of the uses that we know about.
     If you're using dygraphs, please send <a
index 10936de..5fd02e5 100644 (file)
@@ -658,6 +658,114 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() {
   }
 };
 
+DygraphCanvasRenderer.prototype._drawStyledLine = function(
+    ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+    drawPointCallback, pointSize) {
+  var isNullOrNaN = function(x) {
+    return (x === null || isNaN(x));
+  };
+
+  var stepPlot = this.attr_("stepPlot");
+  var firstIndexInSet = this.layout.setPointsOffsets[i];
+  var setLength = this.layout.setPointsLengths[i];
+  var afterLastIndexInSet = firstIndexInSet + setLength;
+  var points = this.layout.points;
+  var prevX = null;
+  var prevY = null;
+  var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
+  if (!Dygraph.isArrayLike(strokePattern)) {
+    strokePattern = null;
+  }
+
+  var point;
+  ctx.save();
+  for (var j = firstIndexInSet; j < afterLastIndexInSet; j++) {
+    point = points[j];
+    if (isNullOrNaN(point.canvasy)) {
+      if (stepPlot && prevX !== null) {
+        // Draw a horizontal line to the start of the missing data
+        ctx.beginPath();
+        ctx.strokeStyle = color;
+        ctx.lineWidth = this.attr_('strokeWidth');
+        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.
+      prevX = prevY = null;
+    } else {
+      // A point is "isolated" if it is non-null but both the previous
+      // and next points are null.
+      var isIsolated = (!prevX && (j == points.length - 1 ||
+                                   isNullOrNaN(points[j+1].canvasy)));
+      if (prevX === null) {
+        prevX = point.canvasx;
+        prevY = point.canvasy;
+      } else {
+        // Skip over points that will be drawn in the same pixel.
+        if (Math.round(prevX) == Math.round(point.canvasx) &&
+            Math.round(prevY) == Math.round(point.canvasy)) {
+          continue;
+        }
+        // TODO(antrob): skip over points that lie on a line that is already
+        // going to be drawn. There is no need to have more than 2
+        // consecutive points that are collinear.
+        if (strokeWidth) {
+          ctx.beginPath();
+          ctx.strokeStyle = color;
+          ctx.lineWidth = strokeWidth;
+          if (stepPlot) {
+            this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
+            prevX = point.canvasx;
+          }
+          this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
+          prevX = point.canvasx;
+          prevY = point.canvasy;
+          ctx.stroke();
+        }
+      }
+
+      if (drawPoints || isIsolated) {
+        pointsOnLine.push([point.canvasx, point.canvasy]);
+      }
+    }
+  }
+  for (var idx = 0; idx < pointsOnLine.length; idx++) {
+    var cb = pointsOnLine[idx];
+    ctx.save();
+    drawPointCallback(
+        this.dygraph_, setName, ctx, cb[0], cb[1], color, pointSize);
+    ctx.restore();
+  }
+  firstIndexInSet = afterLastIndexInSet;
+  ctx.restore();
+};
+
+DygraphCanvasRenderer.prototype._drawLine = function(ctx, i) {
+  var setNames = this.layout.setNames;
+  var setName = setNames[i];
+
+  var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
+  var borderWidth = this.dygraph_.attr_("strokeBorderWidth", setName);
+  var drawPointCallback = this.dygraph_.attr_("drawPointCallback", setName) ||
+      Dygraph.Circles.DEFAULT;
+  if (borderWidth && strokeWidth) {
+    this._drawStyledLine(ctx, i, setName,
+        this.dygraph_.attr_("strokeBorderColor", setName),
+        strokeWidth + 2 * borderWidth,
+        this.dygraph_.attr_("strokePattern", setName),
+        this.dygraph_.attr_("drawPoints", setName),
+        drawPointCallback,
+        this.dygraph_.attr_("pointSize", setName));
+  }
+
+  this._drawStyledLine(ctx, i, setName,
+      this.colors[setName],
+      strokeWidth,
+      this.dygraph_.attr_("strokePattern", setName),
+      this.dygraph_.attr_("drawPoints", setName),
+      drawPointCallback,
+      this.dygraph_.attr_("pointSize", setName));
+};
 
 /**
  * Actually draw the lines chart, including error bars.
@@ -665,12 +773,8 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() {
  * @private
  */
 DygraphCanvasRenderer.prototype._renderLineChart = function() {
-  var isNullOrNaN = function(x) {
-    return (x === null || isNaN(x));
-  };
-
   // TODO(danvk): use this.attr_ for many of these.
-  var context = this.elementContext;
+  var ctx = this.elementContext;
   var fillAlpha = this.attr_('fillAlpha');
   var errorBars = this.attr_("errorBars") || this.attr_("customBars");
   var fillGraph = this.attr_("fillGraph");
@@ -698,8 +802,8 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   }
 
   // create paths
-  var ctx = context;
   if (errorBars) {
+    ctx.save();
     if (fillGraph) {
       this.dygraph_.warn("Can't use fillGraph option with error bars");
     }
@@ -710,7 +814,6 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       color = this.colors[setName];
 
       // setup graphics context
-      ctx.save();
       prevX = NaN;
       prevY = NaN;
       prevYs = [-1, -1];
@@ -759,7 +862,9 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       }
       ctx.fill();
     }
+    ctx.restore();
   } else if (fillGraph) {
+    ctx.save();
     var baseline = [];  // for stacked graphs: baseline for filling
 
     // process sets in reverse order (needed for stacked graphs)
@@ -773,7 +878,6 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       axisY = this.area.h * axisY + this.area.y;
 
       // setup graphics context
-      ctx.save();
       prevX = NaN;
       prevYs = [-1, -1];
       yscale = axis.yscale;
@@ -815,6 +919,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       }
       ctx.fill();
     }
+    ctx.restore();
   }
 
   // Drawing the lines.
@@ -822,91 +927,8 @@ 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 = firstIndexInSet + setLength;
-    setName = setNames[i];
-    color = this.colors[setName];
-    var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
-
-    // setup graphics context
-    // TODO(konigsberg): This function has ctx and context. Clarify the difference.
-    context.save();
-    var pointSize = this.dygraph_.attr_("pointSize", setName);
-    prevX = null;
-    prevY = null;
-    var drawPoints = this.dygraph_.attr_("drawPoints", setName);
-    var drawPointCallback = this.dygraph_.attr_("drawPointCallback", setName);
-    if (!drawPointCallback) {
-      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)) {
-        if (stepPlot && prevX !== null) {
-          // Draw a horizontal line to the start of the missing data
-          ctx.beginPath();
-          ctx.strokeStyle = color;
-          ctx.lineWidth = this.attr_('strokeWidth');
-          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.
-        prevX = prevY = null;
-      } else {
-        // A point is "isolated" if it is non-null but both the previous
-        // and next points are null.
-        var isIsolated = (!prevX && (j == points.length - 1 ||
-                                     isNullOrNaN(points[j+1].canvasy)));
-        if (prevX === null) {
-          prevX = point.canvasx;
-          prevY = point.canvasy;
-        } else {
-          // Skip over points that will be drawn in the same pixel.
-          if (Math.round(prevX) == Math.round(point.canvasx) &&
-              Math.round(prevY) == Math.round(point.canvasy)) {
-            continue;
-          }
-          // TODO(antrob): skip over points that lie on a line that is already
-          // going to be drawn. There is no need to have more than 2
-          // consecutive points that are collinear.
-          if (strokeWidth) {
-            ctx.beginPath();
-            ctx.strokeStyle = color;
-            ctx.lineWidth = strokeWidth;
-            if (stepPlot) {
-              this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
-              prevX = point.canvasx;
-            }
-            this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
-            prevX = point.canvasx;
-            prevY = point.canvasy;
-            ctx.stroke();
-          }
-        }
-
-        if (drawPoints || isIsolated) {
-          pointsOnLine.push([point.canvasx, point.canvasy]);
-        }
-      }
-    }
-    for (var idx = 0; idx < pointsOnLine.length; idx++) {
-      var cb = pointsOnLine[idx];
-      ctx.save();
-      drawPointCallback(
-          this.dygraph_, setName, ctx, cb[0], cb[1], color, pointSize);
-      ctx.restore();
-
-    }
-    firstIndexInSet = afterLastIndexInSet;
+    this._drawLine(ctx, i);
   }
-
-  context.restore();
 };
 
 /**
index 3bef499..0e6b98e 100644 (file)
@@ -41,11 +41,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "false",
     "labels": ["Data Line display"],
     "type": "boolean",
-    "description": "Draw a small dot at each point, in addition to a line going through \
-        the point. This makes the individual data points easier to see, but  \
-        can increase visual clutter in the chart. The small dot can be \
-        replaced with a custom rendering by supplying a \
-        <a href='#drawPointCallback'>drawPointCallback</a>."
+    "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a <a href='#drawPointCallback'>drawPointCallback</a>."
   },
   "drawPointCallback": {
     "default": "null",
@@ -60,17 +56,13 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
       [ "color" , "series color" ],
       [ "pointSize" , "the radius of the image." ]
     ],
-    "description": "Draw a custom item when drawPoints is enabled. \
-        Default is a small dot matching the series color. This method \
-        should constrain drawing to within pointSize pixels from (cx, cy). \
-        Also see <a href='#drawHighlightPointCallback'>drawHighlightPointCallback</a>"
+    "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy).  Also see <a href='#drawHighlightPointCallback'>drawHighlightPointCallback</a>"
   },
   "height": {
     "default": "320",
     "labels": ["Overall display"],
     "type": "integer",
-    "description": "Height, in pixels, of the chart. If the container div has \
-      been explicitly sized, this will be ignored."
+    "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
   },
   "zoomCallback": {
     "default": "null",
@@ -81,8 +73,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
       [ "maxDate" , "milliseconds since epoch." ],
       [ "yRanges" , "is an array of [bottom, top] pairs, one for each y-axis." ]
     ],
-    "description": "A function to call when the zoom window is changed (either \
-      by zooming in or out)."
+    "description": "A function to call when the zoom window is changed (either by zooming in or out)."
   },
   "pointClickCallback": {
     "snippet": "function(e, point){<br>&nbsp;&nbsp;alert(point);<br>}",
@@ -111,13 +102,14 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
   "highlightCallback": {
     "default": "null",
     "labels": ["Callbacks"],
-    "type": "function(event, x, points, row)",
+    "type": "function(event, x, points, row, seriesName)",
     "description": "When set, this callback gets called every time a new point is highlighted.",
     "parameters": [
       ["event", "the JavaScript mousemove event"],
       ["x", "the x-coordinate of the highlighted points"],
       ["points", "an array of highlighted points: <code>[ {name: 'series', yval: y-value}, &hellip; ]</code>"],
-      ["row", "???"]
+      ["row", "integer index of the highlighted row in the data table, starting from 0"],
+      ["seriesName", "name of the highlighted series, only present if highlightSeriesOpts is set."]
     ]
   },
   "drawHighlightPointCallback": {
@@ -133,10 +125,19 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
       [ "color" , "series color" ],
       [ "pointSize" , "the radius of the image." ]
     ],
-    "description": "Draw a custom item when a point is highlighted. \
-        Default is a small dot matching the series color. This method \
-        should constrain drawing to within pointSize pixels from (cx, cy) \
-        Also see <a href='#drawPointCallback'>drawPointCallback</a>"
+    "description": "Draw a custom item when a point is highlighted.  Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see <a href='#drawPointCallback'>drawPointCallback</a>"
+  },
+  "highlightSeriesOpts": {
+    "default": "null",
+    "labels": ["Interactive Elements"],
+    "type": "Object",
+    "description": "When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also 'highlightCallback'. Example: highlightSeriesOpts: { strokeWidth: 3 }."
+  },
+  "highlightSeriesBackgroundAlpha": {
+    "default": "0.5",
+    "labels": ["Interactive Elements"],
+    "type": "float",
+    "description": "Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only)."
   },
   "includeZero": {
     "default": "false",
@@ -354,7 +355,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
   "strokeWidth": {
     "default": "1.0",
     "labels": ["Data Line display"],
-    "type": "integer",
+    "type": "float",
     "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."
   },
@@ -365,6 +366,20 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "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."
   },
+  "strokeBorderWidth": {
+    "default": "null",
+    "labels": ["Data Line display"],
+    "type": "float",
+    "example": "1.0",
+    "description": "Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines."
+  },
+  "strokeBorderColor": {
+    "default": "white",
+    "labels": ["Data Line display"],
+    "type": "string",
+    "example": "red, #ccffdd",
+    "description": "Color for the line border used if strokeBorderWidth is set."
+  },
   "wilsonInterval": {
     "default": "true",
     "labels": ["Error Bars"],
index 61aba43..3103986 100644 (file)
@@ -332,6 +332,18 @@ Dygraph.isOK = function(x) {
 };
 
 /**
+ * @private
+ * @param { Object } p The point to consider, valid points are {x, y} objects
+ * @return { Boolean } Whether the point has numeric x and y.
+ */
+Dygraph.isValidPoint = function(p) {
+  if (!p) return false; // null or undefined object
+  if (isNaN(p.x) || p.x === null || p.x === undefined) return false;
+  if (isNaN(p.y) || p.y === null || p.y === undefined) return false;
+  return true;
+};
+
+/**
  * Number formatting function which mimicks the behavior of %g in printf, i.e.
  * either exponential or fixed format (without trailing 0s) is used depending on
  * the length of the generated string.  The advantage of this format is that
@@ -844,14 +856,14 @@ Dygraph.regularShape_ = function(
     var coords = computeCoordinates();
     ctx.lineTo(coords[0], coords[1]);
   }
+  ctx.fill();
   ctx.stroke();
-  ctx.closePath();
 }
 
 Dygraph.shapeFunction_ = function(sides, rotationRadians, delta) {
   return function(g, name, ctx, cx, cy, color, radius) {
-    ctx.lineWidth = 1;
     ctx.strokeStyle = color;
+    ctx.fillStyle = "white";
     Dygraph.regularShape_(ctx, sides, radius, cx, cy, rotationRadians, delta);
   };
 };
@@ -875,12 +887,13 @@ Dygraph.Circles = {
   CIRCLE : function(g, name, ctx, cx, cy, color, radius) {
     ctx.beginPath();
     ctx.strokeStyle = color;
+    ctx.fillStyle = "white";
     ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false);
+    ctx.fill();
     ctx.stroke();
   },
   STAR : Dygraph.shapeFunction_(5, 0, 4 * Math.PI / 5),
   PLUS : function(g, name, ctx, cx, cy, color, radius) {
-    ctx.lineWidth = 1;
     ctx.strokeStyle = color;
 
     ctx.beginPath();
@@ -893,12 +906,10 @@ Dygraph.Circles = {
     ctx.moveTo(cx, cy + radius);
     ctx.lineTo(cx, cy - radius);
     ctx.closePath();
-
     ctx.stroke();
   },
   EX : function(g, name, ctx, cx, cy, color, radius) {
-    ctx.lineWidth = 1;
-    ctx.strokeStyle = "black";
+    ctx.strokeStyle = color;
 
     ctx.beginPath();
     ctx.moveTo(cx + radius, cy + radius);
@@ -910,7 +921,6 @@ Dygraph.Circles = {
     ctx.moveTo(cx + radius, cy - radius);
     ctx.lineTo(cx - radius, cy + radius);
     ctx.closePath();
-
     ctx.stroke();
   }
 };
index 680aa81..40f1d11 100644 (file)
@@ -186,6 +186,8 @@ Dygraph.dateAxisFormatter = function(date, granularity) {
 // Default attribute values.
 Dygraph.DEFAULT_ATTRS = {
   highlightCircleSize: 3,
+  highlightSeriesOpts: null,
+  highlightSeriesBackgroundAlpha: 0.5,
 
   labelsDivWidth: 250,
   labelsDivStyles: {
@@ -202,6 +204,8 @@ Dygraph.DEFAULT_ATTRS = {
   sigFigs: null,
 
   strokeWidth: 1.0,
+  strokeBorderWidth: 0,
+  strokeBorderColor: "white",
 
   axisTickSize: 3,
   axisLabelFontSize: 14,
@@ -398,6 +402,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
 
   this.boundaryIds_ = [];
   this.setIndexByName_ = {};
+  this.datasetIndex_ = [];
 
   // Create the containing DIV and other interactive elements
   this.createInterface_();
@@ -452,18 +457,31 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
     Dygraph.OPTIONS_REFERENCE[name] = true;
   }
 // </REMOVE_FOR_COMBINED>
-  if (this.user_attrs_ !== null && seriesName &&
-      typeof(this.user_attrs_[seriesName]) != 'undefined' &&
-      this.user_attrs_[seriesName] !== null &&
-      typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
-    return this.user_attrs_[seriesName][name];
-  } else if (this.user_attrs_ !== null && typeof(this.user_attrs_[name]) != 'undefined') {
-    return this.user_attrs_[name];
-  } else if (this.attrs_ !== null && typeof(this.attrs_[name]) != 'undefined') {
-    return this.attrs_[name];
-  } else {
-    return null;
+
+  var sources = [];
+  sources.push(this.attrs_);
+  if (this.user_attrs_) {
+    sources.push(this.user_attrs_);
+    if (seriesName) {
+      if (this.user_attrs_.hasOwnProperty(seriesName)) {
+        sources.push(this.user_attrs_[seriesName]);
+      }
+      if (seriesName === this.highlightSet_ &&
+          this.user_attrs_.hasOwnProperty('highlightSeriesOpts')) {
+        sources.push(this.user_attrs_['highlightSeriesOpts']);
+      }
+    }
+  }
+
+  var ret = null;
+  for (var i = sources.length - 1; i >= 0; --i) {
+    var source = sources[i];
+    if (source.hasOwnProperty(name)) {
+      ret = source[name];
+      break;
+    }
   }
+  return ret;
 };
 
 /**
@@ -1003,7 +1021,11 @@ Dygraph.prototype.createStatusMessage_ = function() {
     div.className = "dygraph-legend";
     for (var name in messagestyle) {
       if (messagestyle.hasOwnProperty(name)) {
-        div.style[name] = messagestyle[name];
+        try {
+          div.style[name] = messagestyle[name];
+        } catch (e) {
+          this.warn("You are using unsupported css properties for your browser in labelsDivStyles");
+        }
       }
     }
     this.graphDiv.appendChild(div);
@@ -1461,74 +1483,183 @@ Dygraph.prototype.doAnimatedZoom = function(oldXRange, newXRange, oldYRanges, ne
 };
 
 /**
- * When the mouse moves in the canvas, display information about a nearby data
- * point and draw dots over those points in the data series. This function
- * takes care of cleanup of previously-drawn dots.
- * @param {Object} event The mousemove event from the browser.
- * @private
+ * Get the current graph's area object.
+ *
+ * Returns: {x, y, w, h}
  */
-Dygraph.prototype.mouseMove_ = function(event) {
-  // This prevents JS errors when mousing over the canvas before data loads.
-  var points = this.layout_.points;
-  if (points === undefined) return;
+Dygraph.prototype.getArea = function() {
+  return this.plotter_.area;
+};
 
+/**
+ * Convert a mouse event to DOM coordinates relative to the graph origin.
+ *
+ * Returns a two-element array: [X, Y].
+ */
+Dygraph.prototype.eventToDomCoords = function(event) {
   var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
+  var canvasy = Dygraph.pageY(event) - Dygraph.findPosY(this.mouseEventElement_);
+  return [canvasx, canvasy];
+};
 
-  var lastx = -1;
-  var i;
-
-  // Loop through all the points and find the date nearest to our current
-  // location.
-  var minDist = 1e+100;
+/**
+ * Given a canvas X coordinate, find the closest row.
+ * @param {Number} domX graph-relative DOM X coordinate
+ * Returns: row number, integer
+ * @private
+ */
+Dygraph.prototype.findClosestRow = function(domX) {
+  var minDistX = null;
   var idx = -1;
-  for (i = 0; i < points.length; i++) {
+  var points = this.layout_.points;
+  var l = points.length;
+  for (var i = 0; i < l; i++) {
     var point = points[i];
-    if (point === null) continue;
-    var dist = Math.abs(point.canvasx - canvasx);
-    if (dist > minDist) continue;
-    minDist = dist;
-    idx = i;
+    if (!Dygraph.isValidPoint(point)) continue;
+    var dist = Math.abs(point.canvasx - domX);
+    if (minDistX === null || dist < minDistX) {
+      minDistX = dist;
+      idx = i;
+    }
   }
-  if (idx >= 0) lastx = points[idx].xval;
+  return this.idxToRow_(idx);
+};
 
-  // Extract the points we've selected
-  this.selPoints_ = [];
-  var l = points.length;
-  if (!this.attr_("stackedGraph")) {
-    for (i = 0; i < l; i++) {
-      if (points[i].xval == lastx) {
-        this.selPoints_.push(points[i]);
+/**
+ * Given canvas X,Y coordinates, find the closest point.
+ *
+ * This finds the individual data point across all visible series
+ * that's closest to the supplied DOM coordinates using the standard
+ * Euclidean X,Y distance.
+ *
+ * @param {Number} domX graph-relative DOM X coordinate
+ * @param {Number} domY graph-relative DOM Y coordinate
+ * Returns: {row, seriesName, point}
+ * @private
+ */
+Dygraph.prototype.findClosestPoint = function(domX, domY) {
+  var minDist = null;
+  var idx = -1;
+  var points = this.layout_.points;
+  var dist, dx, dy, point, closestPoint, closestSeries;
+  for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+    var first = this.layout_.setPointsOffsets[setIdx];
+    var len = this.layout_.setPointsLengths[setIdx];
+    for (var i = 0; i < len; ++i) {
+      var point = points[first + i];
+      if (!Dygraph.isValidPoint(point)) continue;
+      dx = point.canvasx - domX;
+      dy = point.canvasy - domY;
+      dist = dx * dx + dy * dy;
+      if (minDist === null || dist < minDist) {
+        minDist = dist;
+        closestPoint = point;
+        closestSeries = setIdx;
+        idx = i;
       }
     }
-  } else {
-    // Need to 'unstack' points starting from the bottom
-    var cumulative_sum = 0;
-    for (i = l - 1; i >= 0; i--) {
-      if (points[i].xval == lastx) {
-        var p = {};  // Clone the point since we modify it
-        for (var k in points[i]) {
-          p[k] = points[i][k];
+  }
+  var name = this.layout_.setNames[closestSeries];
+  return {
+    row: idx,
+    seriesName: name,
+    point: closestPoint
+  };
+};
+
+/**
+ * Given canvas X,Y coordinates, find the touched area in a stacked graph.
+ *
+ * This first finds the X data point closest to the supplied DOM X coordinate,
+ * then finds the series which puts the Y coordinate on top of its filled area,
+ * using linear interpolation between adjacent point pairs.
+ *
+ * @param {Number} domX graph-relative DOM X coordinate
+ * @param {Number} domY graph-relative DOM Y coordinate
+ * Returns: {row, seriesName, point}
+ * @private
+ */
+Dygraph.prototype.findStackedPoint = function(domX, domY) {
+  var row = this.findClosestRow(domX);
+  var points = this.layout_.points;
+  var closestPoint, closestSeries;
+  for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+    var first = this.layout_.setPointsOffsets[setIdx];
+    var len = this.layout_.setPointsLengths[setIdx];
+    if (row >= len) continue;
+    var p1 = points[first + row];
+    if (!Dygraph.isValidPoint(p1)) continue;
+    var py = p1.canvasy;
+    if (domX > p1.canvasx && row + 1 < len) {
+      // interpolate series Y value using next point
+      var p2 = points[first + row + 1];
+      if (Dygraph.isValidPoint(p2)) {
+        var dx = p2.canvasx - p1.canvasx;
+        if (dx > 0) {
+          var r = (domX - p1.canvasx) / dx;
+          py += r * (p2.canvasy - p1.canvasy);
         }
-        p.yval -= cumulative_sum;
-        cumulative_sum += p.yval;
-        this.selPoints_.push(p);
       }
+    } else if (domX < p1.canvasx && row > 0) {
+      // interpolate series Y value using previous point
+      var p0 = points[first + row - 1];
+      if (Dygraph.isValidPoint(p0)) {
+        var dx = p1.canvasx - p0.canvasx;
+        if (dx > 0) {
+          var r = (p1.canvasx - domX) / dx;
+          py += r * (p0.canvasy - p1.canvasy);
+        }
+      }
+    }
+    // Stop if the point (domX, py) is above this series' upper edge
+    if (setIdx == 0 || py < domY) {
+      closestPoint = p1;
+      closestSeries = setIdx;
     }
-    this.selPoints_.reverse();
   }
+  var name = this.layout_.setNames[closestSeries];
+  return {
+    row: row,
+    seriesName: name,
+    point: closestPoint
+  };
+};
+
+/**
+ * When the mouse moves in the canvas, display information about a nearby data
+ * point and draw dots over those points in the data series. This function
+ * takes care of cleanup of previously-drawn dots.
+ * @param {Object} event The mousemove event from the browser.
+ * @private
+ */
+Dygraph.prototype.mouseMove_ = function(event) {
+  // This prevents JS errors when mousing over the canvas before data loads.
+  var points = this.layout_.points;
+  if (points === undefined) return;
 
-  if (this.attr_("highlightCallback")) {
-    var px = this.lastx_;
-    if (px !== null && lastx != px) {
-      // only fire if the selected point has changed.
-      this.attr_("highlightCallback")(event, lastx, this.selPoints_, this.idxToRow_(idx));
+  var canvasCoords = this.eventToDomCoords(event);
+  var canvasx = canvasCoords[0];
+  var canvasy = canvasCoords[1];
+
+  var highlightSeriesOpts = this.attr_("highlightSeriesOpts");
+  var selectionChanged = false;
+  if (highlightSeriesOpts) {
+    var closest;
+    if (this.attr_("stackedGraph")) {
+      closest = this.findStackedPoint(canvasx, canvasy);
+    } else {
+      closest = this.findClosestPoint(canvasx, canvasy);
     }
+    selectionChanged = this.setSelection(closest.row, closest.seriesName);
+  } else {
+    var idx = this.findClosestRow(canvasx);
+    selectionChanged = this.setSelection(idx);
   }
 
-  // Save last x position for callbacks.
-  this.lastx_ = lastx;
-
-  this.updateSelection_();
+  var callback = this.attr_("highlightCallback");
+  if (callback && selectionChanged) {
+    callback(event, this.lastx_, this.selPoints_, this.lastRow_, this.highlightSet_);
+  }
 };
 
 /**
@@ -1659,7 +1790,7 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points, oneEmWidth) {
       if (html !== '') html += (sepLines ? '<br/>' : ' ');
       strokePattern = this.attr_("strokePattern", labels[i]);
       dash = this.generateLegendDashHTML_(strokePattern, c, oneEmWidth);
-      html += "<span style='font-weight: bold; color: " + c + ";'>" + dash + 
+      html += "<span style='font-weight: bold; color: " + c + ";'>" + dash +
         " " + labels[i] + "</span>";
     }
     return html;
@@ -1687,9 +1818,10 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points, oneEmWidth) {
     c = this.plotter_.colors[pt.name];
     var yval = fmtFunc(pt.yval, yOptView, pt.name, this);
 
+    var cls = (pt.name == this.highlightSet_) ? " class='highlight'" : "";
     // TODO(danvk): use a template string here and make it an attribute.
-    html += " <b><span style='color: " + c + ";'>" + pt.name +
-        "</span></b>:" + yval;
+    html += "<span" + cls + ">" + " <b><span style='color: " + c + ";'>" + pt.name +
+        "</span></b>:" + yval + "</span>";
   }
   return html;
 };
@@ -1721,16 +1853,68 @@ Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
   }
 };
 
+Dygraph.prototype.animateSelection_ = function(direction) {
+  var totalSteps = 10;
+  var millis = 30;
+  if (this.fadeLevel === undefined) this.fadeLevel = 0;
+  if (this.animateId === undefined) this.animateId = 0;
+  var start = this.fadeLevel;
+  var steps = direction < 0 ? start : totalSteps - start;
+  if (steps <= 0) {
+    if (this.fadeLevel) {
+      this.updateSelection_(1.0);
+    }
+    return;
+  }
+
+  var thisId = ++this.animateId;
+  var that = this;
+  Dygraph.repeatAndCleanup(
+    function(n) {
+      // ignore simultaneous animations
+      if (that.animateId != thisId) return;
+
+      that.fadeLevel += direction;
+      if (that.fadeLevel === 0) {
+        that.clearSelection();
+      } else {
+        that.updateSelection_(that.fadeLevel / totalSteps);
+      }
+    },
+    steps, millis, function() {});
+};
+
 /**
  * Draw dots over the selectied points in the data series. This function
  * takes care of cleanup of previously-drawn dots.
  * @private
  */
-Dygraph.prototype.updateSelection_ = function() {
+Dygraph.prototype.updateSelection_ = function(opt_animFraction) {
   // Clear the previously drawn vertical, if there is one
   var i;
   var ctx = this.canvas_ctx_;
-  if (this.previousVerticalX_ >= 0) {
+  if (this.attr_('highlightSeriesOpts')) {
+    ctx.clearRect(0, 0, this.width_, this.height_);
+    var alpha = 1.0 - this.attr_('highlightSeriesBackgroundAlpha');
+    if (alpha) {
+      // Activating background fade includes an animation effect for a gradual
+      // fade. TODO(klausw): make this independently configurable if it causes
+      // issues? Use a shared preference to control animations?
+      var animateBackgroundFade = true;
+      if (animateBackgroundFade) {
+        if (opt_animFraction === undefined) {
+          // start a new animation
+          this.animateSelection_(1);
+          return;
+        }
+        alpha *= opt_animFraction;
+      }
+      ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')';
+      ctx.fillRect(0, 0, this.width_, this.height_);
+    }
+    var setIdx = this.datasetIndexFromSetName_(this.highlightSet_);
+    this.plotter_._drawLine(ctx, setIdx);
+  } else if (this.previousVerticalX_ >= 0) {
     // Determine the maximum highlight circle size.
     var maxCircleSize = 0;
     var labels = this.attr_('labels');
@@ -1762,11 +1946,15 @@ Dygraph.prototype.updateSelection_ = function() {
 
       var circleSize = this.attr_('highlightCircleSize', pt.name);
       var callback = this.attr_("drawHighlightPointCallback", pt.name);
+      var color = this.plotter_.colors[pt.name];
       if (!callback) {
         callback = Dygraph.Circles.DEFAULT;
       }
+      ctx.lineWidth = this.attr_('strokeWidth', pt.name);
+      ctx.strokeStyle = color;
+      ctx.fillStyle = color;
       callback(this.g, pt.name, ctx, canvasx, pt.canvasy,
-          this.plotter_.colors[pt.name], circleSize);
+          color, circleSize);
     }
     ctx.restore();
 
@@ -1780,17 +1968,27 @@ Dygraph.prototype.updateSelection_ = function() {
  * using getSelection().
  * @param { Integer } row number that should be highlighted (i.e. appear with
  * hover dots on the chart). Set to false to clear any selection.
+ * @param { seriesName } optional series name to highlight that series with the
+ * the highlightSeriesOpts setting.
  */
-Dygraph.prototype.setSelection = function(row) {
+Dygraph.prototype.setSelection = function(row, opt_seriesName) {
   // Extract the points we've selected
   this.selPoints_ = [];
   var pos = 0;
 
   if (row !== false) {
-    row = row - this.boundaryIds_[0][0];
+    for (var i = 0; i < this.boundaryIds_.length; i++) {
+      if (this.boundaryIds_[i] !== undefined) {
+        row -= this.boundaryIds_[i][0];
+        break;
+      }
+    }
   }
 
+  var changed = false;
   if (row !== false && row >= 0) {
+    if (row != this.lastRow_) changed = true;
+    this.lastRow_ = row;
     for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
       var set = this.layout_.datasets[setIdx];
       if (row < set.length) {
@@ -1804,15 +2002,26 @@ Dygraph.prototype.setSelection = function(row) {
       }
       pos += set.length;
     }
+  } else {
+    if (this.lastRow_ >= 0) changed = true;
+    this.lastRow_ = -1;
   }
 
   if (this.selPoints_.length) {
     this.lastx_ = this.selPoints_[0].xval;
-    this.updateSelection_();
   } else {
-    this.clearSelection();
+    this.lastx_ = -1;
+  }
+
+  if (opt_seriesName !== undefined) {
+    if (this.highlightSet_ !== opt_seriesName) changed = true;
+    this.highlightSet_ = opt_seriesName;
   }
 
+  if (changed) {
+    this.updateSelection_(undefined);
+  }
+  return changed;
 };
 
 /**
@@ -1836,10 +2045,17 @@ Dygraph.prototype.mouseOut_ = function(event) {
  */
 Dygraph.prototype.clearSelection = function() {
   // Get rid of the overlay data
+  if (this.fadeLevel) {
+    this.animateSelection_(-1);
+    return;
+  }
   this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);
+  this.fadeLevel = 0;
   this.setLegendHTML_();
   this.selPoints_ = [];
   this.lastx_ = -1;
+  this.lastRow_ = -1;
+  this.highlightSet_ = null;
 };
 
 /**
@@ -1860,6 +2076,10 @@ Dygraph.prototype.getSelection = function() {
   return -1;
 };
 
+Dygraph.prototype.getHighlightSeries = function() {
+  return this.highlightSet_;
+};
+
 /**
  * Fires when there's data available to be graphed.
  * @param {String} data Raw CSV data to be plotted
@@ -2133,10 +2353,12 @@ Dygraph.prototype.drawGraph_ = function(clearSelection) {
   if (labels.length > 0) {
     this.setIndexByName_[labels[0]] = 0;
   }
+  var dataIdx = 0;
   for (var i = 1; i < datasets.length; i++) {
     this.setIndexByName_[labels[i]] = i;
     if (!this.visibility()[i - 1]) continue;
     this.layout_.addDataset(labels[i], datasets[i]);
+    this.datasetIndex_[i] = dataIdx++;
   }
 
   this.computeYAxisRanges_(extremes);
@@ -3322,6 +3544,15 @@ Dygraph.prototype.indexFromSetName = function(name) {
 };
 
 /**
+ * Get the internal dataset index given its name. These are numbered starting from 0,
+ * and only count visible sets.
+ * @private
+ */
+Dygraph.prototype.datasetIndexFromSetName_ = function(name) {
+  return this.datasetIndex_[this.indexFromSetName(name)];
+};
+
+/**
  * @private
  * Adds a default style for the annotation CSS classes to the document. This is
  * only executed when annotations are actually used. It is designed to only be
diff --git a/gallery/annotation-gviz.js b/gallery/annotation-gviz.js
deleted file mode 100644 (file)
index 0bdd097..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-Gallery.register(
-  'annotation-gviz',
-  {
-    name: 'Annotation Gviz (broken)',
-    title: 'Comparison of Gviz and Dygraphs annotated timelines',
-    setup : function(parent) {
-      parent.innerHTML = 
-          "<h3>Google AnnotatedTimeline:</h3>" +
-          "<div id='gviz_div' style='width: 700px; height: 240px;'></div>" +
-          "<h3>Dygraph.GVizChart:</h3>" +
-          "<div id='dg_div' style='width: 700px; height: 240px;'></div>";
-    },
-    run: function() {
-      drawChart = function() {
-        var data = new google.visualization.DataTable();
-        data.addColumn('date', 'Date');
-        data.addColumn('number', 'Sold Pencils');
-        data.addColumn('string', 'title1');
-        data.addColumn('string', 'text1');
-        data.addColumn('number', 'Sold Pens');
-        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]
-        ]);
-
-        var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('gviz_div'));
-        chart.draw(data, {displayAnnotations: true});
-
-      };
-      google.setOnLoadCallback(drawChart);
-      var f = function() { alert("!"); };
-      google.setOnLoadCallback(f);
-      var g = new Dygraph.GVizChart(document.getElementById("dg_div"));
-      g.draw(data, {displayAnnotations: true, labelsKMB: true});
-    }
-  });
diff --git a/gallery/annotation-native.js b/gallery/annotation-native.js
deleted file mode 100644 (file)
index 8ed7ff9..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-Gallery.register(
-  'annotations-native',
-  {
-    name: 'Annotations with Native format',
-    setup: function(parent) {
-      parent.innerHTML =
-        "<p>This test demonstrates how annotations can be used with " +
-        "<a href='http://dygraphs.com/data.html#array'>native-format</a> data.</p>" +
-        "<div id='demodiv'></div>";
-    },
-    run: function() {
-      g = new Dygraph(
-              document.getElementById("demodiv"),
-              [
-                [ new Date("2011/11/01"), 100 ],
-                [ new Date("2011/11/02"), 200 ],
-                [ new Date("2011/11/03"), 300 ],
-                [ new Date("2011/11/04"), 100 ],
-                [ new Date("2011/11/05"), 200 ],
-                [ new Date("2011/11/06"), 300 ],
-                [ new Date("2011/11/07"), 200 ],
-                [ new Date("2011/11/08"), 100 ]
-              ],
-              {
-                labels: [ 'Date', 'Value' ]
-              }
-          );
-
-      g.setAnnotations([{
-        series: 'Value',
-        x: Date.parse('2011/11/04'),
-        shortText: 'M',
-        text: 'Marker'
-      }]);
-  }
-});
diff --git a/gallery/annotation.js b/gallery/annotation.js
deleted file mode 100644 (file)
index e0e0319..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-Gallery.register(
-  'annotations',
-  {
-    name: 'Annotations', 
-    title: 'Dynamic Annotations Demo',
-    setup: function(parent) {
-      parent.innerHTML = [
-          "<p>Click any point to add an annotation to it or click 'Add Annotation'.</p>",
-          "<button id='add'>Add Annotation></button>",
-          "<button id='bottom'>Shove to bottom</button>",
-          "<div id='list'></div>",
-          "<div id='g_div'></div>",
-          "<div id='events'></div>" ].join("\n");
-     },
-
-    run: function() {
-      var eventDiv = document.getElementById("events");
-      function nameAnnotation(ann) {
-        return "(" + ann.series + ", " + ann.x + ")";
-      }
-  
-      g = new Dygraph(
-              document.getElementById("g_div"),
-              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;
-              },
-              {
-                rollPeriod: 1,
-                showRoller: true,
-                width: 480,
-                height: 320,
-                drawCallback: function(g) {
-                  var ann = g.annotations();
-                  var html = "";
-                  for (var i = 0; i < ann.length; i++) {
-                    var name = nameAnnotation(ann[i]);
-                    html += "<span id='" + name + "'>"
-                    html += name + ": " + (ann[i].shortText || '(icon)')
-                    html += " -> " + ann[i].text + "</span><br/>";
-                  }
-                  document.getElementById("list").innerHTML = html;
-            }
-          }
-          );
-    
-      var last_ann = 0;
-      annotations = [];
-      for (var x = 10; x < 15; x += 2) {
-        annotations.push( {
-          series: 'sine wave',
-          x: "200610" + x,
-          shortText: x,
-          text: 'Stock Market Crash ' + x
-        } );
-        last_ann = x;
-      }
-      annotations.push( {
-        series: 'another line',
-        x: "20061013",
-        icon: 'images/dollar.png',
-        width: 18,
-        height: 23,
-        tickHeight: 4,
-        text: 'Another one',
-        cssClass: 'annotation',
-        clickHandler: function() {
-          eventDiv.innerHTML += "special handler<br/>";
-        }
-      } );
-      g.setAnnotations(annotations);
-    
-      document.getElementById('add').onclick = function() {
-        var x = last_ann + 2;
-        var annnotations = g.annotations();
-        annotations.push( {
-          series: 'line',
-          x: "200610" + x,
-          shortText: x,
-          text: 'Line ' + x,
-          tickHeight: 10
-        } );
-        last_ann = x;
-        g.setAnnotations(annotations);
-      }
-
-      var bottom = document.getElementById('bottom');
-
-      bottom.onclick = function() {
-        var to_bottom = bottom.textContent == 'Shove to bottom';
-    
-        var anns = g.annotations();
-        for (var i = 0; i < anns.length; i++) {
-          anns[i].attachAtBottom = to_bottom;
-        }
-        g.setAnnotations(anns);
-    
-        if (to_bottom) {
-          bottom.textContent = 'Lift back up';
-        } else {
-          bottom.textContent = 'Shove to bottom';
-        }
-      }
-    
-      var saveBg = '';
-      var num = 0;
-      g.updateOptions( {
-        annotationClickHandler: function(ann, point, dg, event) {
-          eventDiv.innerHTML += "click: " + nameAnnotation(ann) + "<br/>";
-        },
-        annotationDblClickHandler: function(ann, point, dg, event) {
-          eventDiv.innerHTML += "dblclick: " + nameAnnotation(ann) + "<br/>";
-        },
-        annotationMouseOverHandler: function(ann, point, dg, event) {
-          document.getElementById(nameAnnotation(ann)).style.fontWeight = 'bold';
-          saveBg = ann.div.style.backgroundColor;
-          ann.div.style.backgroundColor = '#ddd';
-        },
-        annotationMouseOutHandler: function(ann, point, dg, event) {
-          document.getElementById(nameAnnotation(ann)).style.fontWeight = 'normal';
-          ann.div.style.backgroundColor = saveBg;
-        },
-    
-        pointClickCallback: function(event, p) {
-          // Check if the point is already annotated.
-          if (p.annotation) return;
-    
-          // If not, add one.
-          var ann = {
-            series: p.name,
-            xval: p.xval,
-            shortText: num,
-            text: "Annotation #" + num
-          };
-          var anns = g.annotations();
-          anns.push(ann);
-          g.setAnnotations(anns);
-    
-          num++;
-        }
-      });
-    }
-  });
diff --git a/gallery/annotations-gviz.js b/gallery/annotations-gviz.js
new file mode 100644 (file)
index 0000000..a9ce26c
--- /dev/null
@@ -0,0 +1,42 @@
+Gallery.register(
+  'annotations-gviz',
+  {
+    name: 'Annotation Gviz (broken)',
+    title: 'Comparison of Gviz and Dygraphs annotated timelines',
+    setup : function(parent) {
+      parent.innerHTML = 
+          "<h3>Google AnnotatedTimeline:</h3>" +
+          "<div id='gviz_div' style='width: 700px; height: 240px;'></div>" +
+          "<h3>Dygraph.GVizChart:</h3>" +
+          "<div id='dg_div' style='width: 700px; height: 240px;'></div>";
+    },
+    run: function() {
+      drawChart = function() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('date', 'Date');
+        data.addColumn('number', 'Sold Pencils');
+        data.addColumn('string', 'title1');
+        data.addColumn('string', 'text1');
+        data.addColumn('number', 'Sold Pens');
+        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]
+        ]);
+
+        var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('gviz_div'));
+        chart.draw(data, {displayAnnotations: true});
+
+      };
+      google.setOnLoadCallback(drawChart);
+      var f = function() { alert("!"); };
+      google.setOnLoadCallback(f);
+      var g = new Dygraph.GVizChart(document.getElementById("dg_div"));
+      g.draw(data, {displayAnnotations: true, labelsKMB: true});
+    }
+  });
diff --git a/gallery/annotations-native.js b/gallery/annotations-native.js
new file mode 100644 (file)
index 0000000..8ed7ff9
--- /dev/null
@@ -0,0 +1,36 @@
+Gallery.register(
+  'annotations-native',
+  {
+    name: 'Annotations with Native format',
+    setup: function(parent) {
+      parent.innerHTML =
+        "<p>This test demonstrates how annotations can be used with " +
+        "<a href='http://dygraphs.com/data.html#array'>native-format</a> data.</p>" +
+        "<div id='demodiv'></div>";
+    },
+    run: function() {
+      g = new Dygraph(
+              document.getElementById("demodiv"),
+              [
+                [ new Date("2011/11/01"), 100 ],
+                [ new Date("2011/11/02"), 200 ],
+                [ new Date("2011/11/03"), 300 ],
+                [ new Date("2011/11/04"), 100 ],
+                [ new Date("2011/11/05"), 200 ],
+                [ new Date("2011/11/06"), 300 ],
+                [ new Date("2011/11/07"), 200 ],
+                [ new Date("2011/11/08"), 100 ]
+              ],
+              {
+                labels: [ 'Date', 'Value' ]
+              }
+          );
+
+      g.setAnnotations([{
+        series: 'Value',
+        x: Date.parse('2011/11/04'),
+        shortText: 'M',
+        text: 'Marker'
+      }]);
+  }
+});
diff --git a/gallery/annotations.js b/gallery/annotations.js
new file mode 100644 (file)
index 0000000..e0e0319
--- /dev/null
@@ -0,0 +1,152 @@
+Gallery.register(
+  'annotations',
+  {
+    name: 'Annotations', 
+    title: 'Dynamic Annotations Demo',
+    setup: function(parent) {
+      parent.innerHTML = [
+          "<p>Click any point to add an annotation to it or click 'Add Annotation'.</p>",
+          "<button id='add'>Add Annotation></button>",
+          "<button id='bottom'>Shove to bottom</button>",
+          "<div id='list'></div>",
+          "<div id='g_div'></div>",
+          "<div id='events'></div>" ].join("\n");
+     },
+
+    run: function() {
+      var eventDiv = document.getElementById("events");
+      function nameAnnotation(ann) {
+        return "(" + ann.series + ", " + ann.x + ")";
+      }
+  
+      g = new Dygraph(
+              document.getElementById("g_div"),
+              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;
+              },
+              {
+                rollPeriod: 1,
+                showRoller: true,
+                width: 480,
+                height: 320,
+                drawCallback: function(g) {
+                  var ann = g.annotations();
+                  var html = "";
+                  for (var i = 0; i < ann.length; i++) {
+                    var name = nameAnnotation(ann[i]);
+                    html += "<span id='" + name + "'>"
+                    html += name + ": " + (ann[i].shortText || '(icon)')
+                    html += " -> " + ann[i].text + "</span><br/>";
+                  }
+                  document.getElementById("list").innerHTML = html;
+            }
+          }
+          );
+    
+      var last_ann = 0;
+      annotations = [];
+      for (var x = 10; x < 15; x += 2) {
+        annotations.push( {
+          series: 'sine wave',
+          x: "200610" + x,
+          shortText: x,
+          text: 'Stock Market Crash ' + x
+        } );
+        last_ann = x;
+      }
+      annotations.push( {
+        series: 'another line',
+        x: "20061013",
+        icon: 'images/dollar.png',
+        width: 18,
+        height: 23,
+        tickHeight: 4,
+        text: 'Another one',
+        cssClass: 'annotation',
+        clickHandler: function() {
+          eventDiv.innerHTML += "special handler<br/>";
+        }
+      } );
+      g.setAnnotations(annotations);
+    
+      document.getElementById('add').onclick = function() {
+        var x = last_ann + 2;
+        var annnotations = g.annotations();
+        annotations.push( {
+          series: 'line',
+          x: "200610" + x,
+          shortText: x,
+          text: 'Line ' + x,
+          tickHeight: 10
+        } );
+        last_ann = x;
+        g.setAnnotations(annotations);
+      }
+
+      var bottom = document.getElementById('bottom');
+
+      bottom.onclick = function() {
+        var to_bottom = bottom.textContent == 'Shove to bottom';
+    
+        var anns = g.annotations();
+        for (var i = 0; i < anns.length; i++) {
+          anns[i].attachAtBottom = to_bottom;
+        }
+        g.setAnnotations(anns);
+    
+        if (to_bottom) {
+          bottom.textContent = 'Lift back up';
+        } else {
+          bottom.textContent = 'Shove to bottom';
+        }
+      }
+    
+      var saveBg = '';
+      var num = 0;
+      g.updateOptions( {
+        annotationClickHandler: function(ann, point, dg, event) {
+          eventDiv.innerHTML += "click: " + nameAnnotation(ann) + "<br/>";
+        },
+        annotationDblClickHandler: function(ann, point, dg, event) {
+          eventDiv.innerHTML += "dblclick: " + nameAnnotation(ann) + "<br/>";
+        },
+        annotationMouseOverHandler: function(ann, point, dg, event) {
+          document.getElementById(nameAnnotation(ann)).style.fontWeight = 'bold';
+          saveBg = ann.div.style.backgroundColor;
+          ann.div.style.backgroundColor = '#ddd';
+        },
+        annotationMouseOutHandler: function(ann, point, dg, event) {
+          document.getElementById(nameAnnotation(ann)).style.fontWeight = 'normal';
+          ann.div.style.backgroundColor = saveBg;
+        },
+    
+        pointClickCallback: function(event, p) {
+          // Check if the point is already annotated.
+          if (p.annotation) return;
+    
+          // If not, add one.
+          var ann = {
+            series: p.name,
+            xval: p.xval,
+            shortText: num,
+            text: "Annotation #" + num
+          };
+          var anns = g.annotations();
+          anns.push(ann);
+          g.setAnnotations(anns);
+    
+          num++;
+        }
+      });
+    }
+  });
diff --git a/gallery/avoid-min-zero.js b/gallery/avoid-min-zero.js
new file mode 100644 (file)
index 0000000..9d72980
--- /dev/null
@@ -0,0 +1,60 @@
+Gallery.register(
+  'avoid-min-zero',
+  {
+    name: "Avoid Min Zero",
+    setup: function(parent) {
+      parent.innerHTML =
+          "<p>1: Line chart with axis at zero problem:</p><div id='graph1'></div> " +
+          "<p>2: Step chart with axis at zero problem:</p><div id='graphd2'></div> " +
+          "<p>3: Line chart with <code>avoidMinZero</code> option:</p><div id='graph3'></div> " +
+          "<p>4: Step chart with <code>avoidMinZero</code> option:</p><div id='graphd4'></div> ";
+    },
+    run: function() {
+    var g1 = new Dygraph(document.getElementById("graph1"),
+        "Date,Temperature\n" +
+        "2008-05-07,0\n" +
+        "2008-05-08,1\n" +
+        "2008-05-09,0\n" +
+        "2008-05-10,0\n" +
+        "2008-05-11,3\n" +
+        "2008-05-12,4\n"
+    )
+    var g2 = new Dygraph(document.getElementById("graphd2"),
+        "Date,Temperature\n" +
+        "2008-05-07,0\n" +
+        "2008-05-08,1\n" +
+        "2008-05-09,0\n" +
+        "2008-05-10,0\n" +
+        "2008-05-11,3\n" +
+        "2008-05-12,4\n",
+        {
+           stepPlot: true
+        }
+    )
+    var g3 = new Dygraph(document.getElementById("graph3"),
+        "Date,Temperature\n" +
+        "2008-05-07,0\n" +
+        "2008-05-08,1\n" +
+        "2008-05-09,0\n" +
+        "2008-05-10,0\n" +
+        "2008-05-11,3\n" +
+        "2008-05-12,4\n",
+        {
+            avoidMinZero: true
+        }
+    )
+    var g4 = new Dygraph(document.getElementById("graphd4"),
+        "Date,Temperature\n" +
+        "2008-05-07,0\n" +
+        "2008-05-08,1\n" +
+        "2008-05-09,0\n" +
+        "2008-05-10,0\n" +
+        "2008-05-11,3\n" +
+        "2008-05-12,4\n",
+        {
+           stepPlot: true,
+           avoidMinZero: true
+        }
+    )
+  }
+});
diff --git a/gallery/avoidMinZero.js b/gallery/avoidMinZero.js
deleted file mode 100644 (file)
index 9d72980..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-Gallery.register(
-  'avoid-min-zero',
-  {
-    name: "Avoid Min Zero",
-    setup: function(parent) {
-      parent.innerHTML =
-          "<p>1: Line chart with axis at zero problem:</p><div id='graph1'></div> " +
-          "<p>2: Step chart with axis at zero problem:</p><div id='graphd2'></div> " +
-          "<p>3: Line chart with <code>avoidMinZero</code> option:</p><div id='graph3'></div> " +
-          "<p>4: Step chart with <code>avoidMinZero</code> option:</p><div id='graphd4'></div> ";
-    },
-    run: function() {
-    var g1 = new Dygraph(document.getElementById("graph1"),
-        "Date,Temperature\n" +
-        "2008-05-07,0\n" +
-        "2008-05-08,1\n" +
-        "2008-05-09,0\n" +
-        "2008-05-10,0\n" +
-        "2008-05-11,3\n" +
-        "2008-05-12,4\n"
-    )
-    var g2 = new Dygraph(document.getElementById("graphd2"),
-        "Date,Temperature\n" +
-        "2008-05-07,0\n" +
-        "2008-05-08,1\n" +
-        "2008-05-09,0\n" +
-        "2008-05-10,0\n" +
-        "2008-05-11,3\n" +
-        "2008-05-12,4\n",
-        {
-           stepPlot: true
-        }
-    )
-    var g3 = new Dygraph(document.getElementById("graph3"),
-        "Date,Temperature\n" +
-        "2008-05-07,0\n" +
-        "2008-05-08,1\n" +
-        "2008-05-09,0\n" +
-        "2008-05-10,0\n" +
-        "2008-05-11,3\n" +
-        "2008-05-12,4\n",
-        {
-            avoidMinZero: true
-        }
-    )
-    var g4 = new Dygraph(document.getElementById("graphd4"),
-        "Date,Temperature\n" +
-        "2008-05-07,0\n" +
-        "2008-05-08,1\n" +
-        "2008-05-09,0\n" +
-        "2008-05-10,0\n" +
-        "2008-05-11,3\n" +
-        "2008-05-12,4\n",
-        {
-           stepPlot: true,
-           avoidMinZero: true
-        }
-    )
-  }
-});
index a07c761..38adf5c 100644 (file)
@@ -1,5 +1,5 @@
 Gallery.register(
-  'border-test',
+  'border',
   { 
     name: "Border test",
     title: 'Graph stays within the border',
diff --git a/gallery/callback.js b/gallery/callback.js
deleted file mode 100644 (file)
index 8891b4c..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-Gallery.register(
-  'callbacks',
-  {
-    name: "Callbacks",
-    title: "Hover, click and zoom to test the callbacks.",
-    setup: function(parent) {
-      parent.innerHTML = 
-          "<div id='div_g' style='width:600px; height:300px;'></div>" +
-          "<button id='clear'>Clear list<Button>" +
-          "<input type='checkbox' id='highlight' checked><label for='highlight'> Show 'highlight' events</label>" +
-          "<input type='checkbox' id='unhighlight' checked><label for='unhighlight'>Show 'unhighlight' events</label>" +
-          "<input type='checkbox' id='showLabels' checked>" +
-          "<label for='showLabels'> Show Labels on highlight</label>" +
-          "<div id='status' style='width:100%; height:200px;'></div>";
-    },
-    run: function() {
-      var showLabels = document.getElementById('showLabels');
-      showLabels.onclick =  function() {
-        g.updateOptions({showLabelsOnHighlight: showLabels.checked});
-      }
-
-      var s = document.getElementById("status");
-      var clearStatus = function() {
-        s.innerHTML = '';
-      }
-      document.getElementById('clear').onclick = clearStatus;
-
-      var g = null;
-      pts_info = function(e, x, pts, row) {
-        var str = "(" + x + ") ";
-        for (var i = 0; i < pts.length; i++) {
-          var p = pts[i];
-          if (i) str += ", ";
-          str += p.name + ": " + p.yval;
-        }
-
-        var x = e.offsetX;
-        var y = e.offsetY;
-        var dataXY = g.toDataCoords(x, y);
-        str += ", (" + x + ", " + y + ")";
-        str += " -> (" + dataXY[0] + ", " + dataXY[1] + ")";
-        str += ", row #"+row;
-
-        return str;
-      };
-
-      g = new Dygraph(
-          document.getElementById("div_g"),
-          NoisyData, {
-            rollPeriod: 7,
-            showRoller: true,
-            errorBars: true,
-  
-            highlightCallback: function(e, x, pts, row) {
-              if (document.getElementById('highlight').checked) {
-                s.innerHTML += "<b>Highlight</b> " + pts_info(e,x,pts,row) + "<br/>";
-              }
-            },
-  
-            unhighlightCallback: function(e) {
-              if (document.getElementById('unhighlight').checked) {
-                s.innerHTML += "<b>Unhighlight</b><br/>";
-              }
-            },
-  
-            clickCallback: function(e, x, pts) {
-              s.innerHTML += "<b>Click</b> " + pts_info(e,x,pts) + "<br/>";
-            },
-  
-            pointClickCallback: function(e, p) {
-              s.innerHTML += "<b>Point Click</b> " + p.name + ": " + p.x + "<br/>";
-            },
-  
-            zoomCallback: function(minX, maxX, yRanges) {
-              s.innerHTML += "<b>Zoom</b> [" + minX + ", " + maxX + ", [" + yRanges + "]]<br/>";
-            },
-  
-            drawCallback: function(g) {
-              s.innerHTML += "<b>Draw</b> [" + g.xAxisRange() + "]<br/>";
-            }
-          }
-        );
-    }
-  });
diff --git a/gallery/callbacks.js b/gallery/callbacks.js
new file mode 100644 (file)
index 0000000..8891b4c
--- /dev/null
@@ -0,0 +1,84 @@
+Gallery.register(
+  'callbacks',
+  {
+    name: "Callbacks",
+    title: "Hover, click and zoom to test the callbacks.",
+    setup: function(parent) {
+      parent.innerHTML = 
+          "<div id='div_g' style='width:600px; height:300px;'></div>" +
+          "<button id='clear'>Clear list<Button>" +
+          "<input type='checkbox' id='highlight' checked><label for='highlight'> Show 'highlight' events</label>" +
+          "<input type='checkbox' id='unhighlight' checked><label for='unhighlight'>Show 'unhighlight' events</label>" +
+          "<input type='checkbox' id='showLabels' checked>" +
+          "<label for='showLabels'> Show Labels on highlight</label>" +
+          "<div id='status' style='width:100%; height:200px;'></div>";
+    },
+    run: function() {
+      var showLabels = document.getElementById('showLabels');
+      showLabels.onclick =  function() {
+        g.updateOptions({showLabelsOnHighlight: showLabels.checked});
+      }
+
+      var s = document.getElementById("status");
+      var clearStatus = function() {
+        s.innerHTML = '';
+      }
+      document.getElementById('clear').onclick = clearStatus;
+
+      var g = null;
+      pts_info = function(e, x, pts, row) {
+        var str = "(" + x + ") ";
+        for (var i = 0; i < pts.length; i++) {
+          var p = pts[i];
+          if (i) str += ", ";
+          str += p.name + ": " + p.yval;
+        }
+
+        var x = e.offsetX;
+        var y = e.offsetY;
+        var dataXY = g.toDataCoords(x, y);
+        str += ", (" + x + ", " + y + ")";
+        str += " -> (" + dataXY[0] + ", " + dataXY[1] + ")";
+        str += ", row #"+row;
+
+        return str;
+      };
+
+      g = new Dygraph(
+          document.getElementById("div_g"),
+          NoisyData, {
+            rollPeriod: 7,
+            showRoller: true,
+            errorBars: true,
+  
+            highlightCallback: function(e, x, pts, row) {
+              if (document.getElementById('highlight').checked) {
+                s.innerHTML += "<b>Highlight</b> " + pts_info(e,x,pts,row) + "<br/>";
+              }
+            },
+  
+            unhighlightCallback: function(e) {
+              if (document.getElementById('unhighlight').checked) {
+                s.innerHTML += "<b>Unhighlight</b><br/>";
+              }
+            },
+  
+            clickCallback: function(e, x, pts) {
+              s.innerHTML += "<b>Click</b> " + pts_info(e,x,pts) + "<br/>";
+            },
+  
+            pointClickCallback: function(e, p) {
+              s.innerHTML += "<b>Point Click</b> " + p.name + ": " + p.x + "<br/>";
+            },
+  
+            zoomCallback: function(minX, maxX, yRanges) {
+              s.innerHTML += "<b>Zoom</b> [" + minX + ", " + maxX + ", [" + yRanges + "]]<br/>";
+            },
+  
+            drawCallback: function(g) {
+              s.innerHTML += "<b>Draw</b> [" + g.xAxisRange() + "]<br/>";
+            }
+          }
+        );
+    }
+  });
index 74bbbef..d1da4a3 100644 (file)
@@ -157,3 +157,7 @@ a {
 #workarea #temperature-sf-ny #bordered {
   border: 1px solid red;
 }
+
+#workarea #highlighted-series .few .dygraph-legend > span.highlight { border: 1px solid grey; }
+#workarea #highlighted-series .many .dygraph-legend > span { display: none; }
+#workarea #highlighted-series .many .dygraph-legend > span.highlight { display: inline; }
diff --git a/gallery/highlighted-series.js b/gallery/highlighted-series.js
new file mode 100644 (file)
index 0000000..8cbe3c8
--- /dev/null
@@ -0,0 +1,72 @@
+Gallery.register(
+  'highlighted-series',
+  {
+    name: 'Highlight Closest Series',
+    title: 'Interactive closest-series highlighting',
+    setup: function(parent) {
+      parent.innerHTML = "<div id='demo'></div>";
+    },
+    run: function() {
+var getData = function(numSeries, numRows, isStacked) {
+  var data = [];
+
+  for (var j = 0; j < numRows; ++j) {
+    data[j] = [j];
+  }
+  for (var i = 0; i < numSeries; ++i) {
+    var val = 0;
+    for (var j = 0; j < numRows; ++j) {
+      if (isStacked) {
+        val = Math.random();
+      } else {
+        val += Math.random() - 0.5;
+      }
+      data[j][i + 1] = val;
+    }
+  }
+  return data;
+};
+
+var makeGraph = function(className, numSeries, numRows, isStacked) {
+  var demo = document.getElementById('demo');
+  var div = document.createElement('div');
+  div.className = className;
+  div.style.display = 'inline-block';
+  div.style.margin = '4px';
+  demo.appendChild(div);
+
+  var labels = ['x'];
+  for (var i = 0; i < numSeries; ++i) {
+    var label = '' + i;
+    label = 's' + '000'.substr(label.length) + label;
+    labels[i + 1] = label;
+  }
+  var g = new Dygraph(
+      div,
+      getData(numSeries, numRows, isStacked),
+      {
+        width: 480,
+        height: 320,
+        labels: labels.slice(),
+        stackedGraph: isStacked,
+
+        highlightCircleSize: 2,
+        strokeWidth: 1,
+        strokeBorderWidth: isStacked ? null : 1,
+
+        highlightSeriesOpts: {
+          strokeWidth: 3,
+          strokeBorderWidth: 1,
+          highlightCircleSize: 5,
+        },
+      });
+  g.setSelection(false, 's005');
+  //console.log(g);
+};
+
+makeGraph("few", 20, 50, false);
+makeGraph("few", 10, 20, true);
+makeGraph("many", 75, 50, false);
+makeGraph("many", 40, 50, true);
+    }
+  });
index 5ff8d13..cc41153 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'independent-series',
   {
index 311427e..ca10598 100644 (file)
     <!-- script src='http://www.google.com/jsapi'></script> -->
 
     <!-- gallery entries. Can these be auto-loaded? -->
-    <script src="annotation.js"></script>
+    <script src="annotations.js"></script>
     <script src="drawing.js"></script>
     <script src="dynamic-update.js"></script>
+    <script src="highlighted-series.js"></script>
     <script src="highlighted-region.js"></script>
     <script src="independent-series.js"></script>
     <script src="plotter.js"></script>
     <script src="dygraph-simple.js"></script>
     <script src="demo.js"></script>
     <script src="border.js"></script>
-    <script src="callback.js"></script>
-    <script src="avoidMinZero.js"></script>
+    <script src="callbacks.js"></script>
+    <script src="avoid-min-zero.js"></script>
     <script src="color-cycle.js"></script>
     <script src="color-visibility.js"></script>
     <script src="two-axes.js"></script>
     <script src="number-format.js"></script>
     <script src="no-range.js"></script>
     <script src="negative.js"></script>
-    <script src="annotation-gviz.js"></script>
-    <script src="annotation-native.js"></script>
+    <script src="annotations-gviz.js"></script>
+    <script src="annotations-native.js"></script>
     -->
     <link rel="stylesheet" type="text/css" href="../common/textarea.css"></link>
     <link title="gallery" rel="stylesheet" type="text/css" href="gallery.css"></link>
index de02075..f381e4c 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'linear-regression',
   {
index f0bfecf..e1d6faa 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'link-interaction',
   {
@@ -87,4 +86,4 @@ Gallery.register(
       document.getElementById('left').onclick = function() { pan(-1); };
       document.getElementById('right').onclick = function() { pan(+1); };
     }
-  });
\ No newline at end of file
+  });
index 82fd5c9..4959964 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'per-series',
   {
index d077b09..0ecaefa 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'plotter',
   {
index 9675464..3fd85b8 100644 (file)
@@ -1,4 +1,3 @@
-// Use this as a template for new Gallery entries.
 Gallery.register(
   'synchronize',
   {
index 613dec7..ddaa68a 100755 (executable)
@@ -21,8 +21,6 @@ for line in file('dygraph-options-reference.js'):
   elif '</JSON>' in line:
     in_json = False
   elif in_json:
-    if line.endswith("\\\n"): # hacked in line continuation support with trailing \.
-      line = line[:-2]
     js += line
 
 # TODO(danvk): better errors here.
@@ -32,6 +30,7 @@ docs = json.loads(js)
 # Go through the tests and find uses of each option.
 for opt in docs:
   docs[opt]['tests'] = []
+  docs[opt]['gallery'] = []
 
 # This is helpful for differentiating uses of options like 'width' and 'height'
 # from appearances of identically-named options in CSS.
@@ -49,20 +48,30 @@ def find_braces(txt):
       level -= 1
   return out
 
-# Find text followed by a colon. These won't all be options, but those that
-# have the same name as a Dygraph option probably will be.
-prop_re = re.compile(r'\b([a-zA-Z0-9]+) *:')
-tests = debug_tests or glob.glob('tests/*.html')
-for test_file in tests:
-  braced_html = find_braces(file(test_file).read())
-  if debug_tests:
-    print braced_html
-
-  ms = re.findall(prop_re, braced_html)
-  for opt in ms:
-    if debug_tests: print '\n'.join(ms)
-    if opt in docs and test_file not in docs[opt]['tests']:
-      docs[opt]['tests'].append(test_file)
+def search_files(type, files):
+  # Find text followed by a colon. These won't all be options, but those that
+  # have the same name as a Dygraph option probably will be.
+  prop_re = re.compile(r'\b([a-zA-Z0-9]+) *:')
+  for test_file in files:
+    text = file(test_file).read()
+    # Hack for slipping past gallery demos that have title in their attributes
+    # so they don't appear as reasons for the demo to have 'title' options.
+    if type == "gallery":
+      idx = text.find("function(")
+      if idx >= 0:
+        text = text[idx:]
+    braced_html = find_braces(text)
+    if debug_tests:
+      print braced_html
+
+    ms = re.findall(prop_re, braced_html)
+    for opt in ms:
+      if debug_tests: print '\n'.join(ms)
+      if opt in docs and test_file not in docs[opt][type]:
+        docs[opt][type].append(test_file)
+
+search_files("tests", glob.glob("tests/*.html"))
+search_files("gallery", glob.glob("gallery/*.js")) #TODO add grep "Gallery.register\("
 
 if debug_tests: sys.exit(0)
 
@@ -112,10 +121,6 @@ for label in sorted(labels):
   print '  <li><a href="#%s">%s</a>\n' % (label, label)
 print '</ul>\n</div>\n\n'
 
-def name(f):
-  """Takes 'tests/demo.html' -> 'demo'"""
-  return f.replace('tests/', '').replace('.html', '')
-
 print """
 <div id='content'>
 <h2>Options Reference</h2>
@@ -140,6 +145,20 @@ print """
 </pre>
 <p>And, without further ado, here's the complete list of options:</p>
 """
+
+def test_name(f):
+  """Takes 'tests/demo.html' -> 'demo'"""
+  return f.replace('tests/', '').replace('.html', '')
+
+def gallery_name(f):
+  """Takes 'gallery/demo.js' -> 'demo'"""
+  return f.replace('gallery/', '').replace('.js', '')
+
+def urlify_gallery(f):
+  """Takes 'gallery/demo.js' -> 'demo'"""
+  return f.replace('gallery/', 'gallery/#g/').replace('.js', '')
+
+
 for label in sorted(labels):
   print '<a name="%s"><h3>%s</h3>\n' % (label, label)
 
@@ -151,7 +170,14 @@ for label in sorted(labels):
       examples_html = '<font color=red>NONE</font>'
     else:
       examples_html = ' '.join(
-        '<a href="%s">%s</a>' % (f, name(f)) for f in tests)
+        '<a href="%s">%s</a>' % (f, test_name(f)) for f in tests)
+
+    gallery = opt['gallery']
+    if not gallery:
+      gallery_html = '<font color=red>NONE</font>'
+    else:
+      gallery_html = ' '.join(
+        '<a href="%s">%s</a>' % (urlify_gallery(f), gallery_name(f)) for f in gallery)
 
     if 'parameters' in opt:
       parameters = opt['parameters']
@@ -166,17 +192,19 @@ for label in sorted(labels):
 
     print """
   <div class='option'><a name="%(name)s"></a><b>%(name)s</b><br/>
-  %(desc)s<br/>
+  <p>%(desc)s</p>
   <i>Type: %(type)s</i><br/>%(parameters)s
-  <i>Default: %(default)s</i><br/>
-  Examples: %(examples_html)s<br/>
+  <i>Default: %(default)s</i></p>
+  Gallery Samples: %(gallery_html)s<br/>
+  Other Examples: %(examples_html)s<br/>
   <br/></div>
   """ % { 'name': opt_name,
           'type': opt['type'],
           'parameters': parameters_html,
           'default': opt['default'],
           'desc': opt['description'],
-          'examples_html': examples_html}
+          'examples_html': examples_html,
+          'gallery_html': gallery_html}
 
 
 print """
diff --git a/tests/crosshair.html b/tests/crosshair.html
deleted file mode 100644 (file)
index f3720f5..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-    <title>crosshairs</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>
-
-    <script type="text/javascript" src="data.js"></script>
-  </head>
-  <body>
-    <p>Hover, click and zoom to test the callbacks:</p>
-    <div id="div_g" style="width:600px; height:300px; position:relative;">
-    </div>
-
-    <script type="text/javascript">
-      var lines = [];
-      var xline;
-      g = new Dygraph(
-            document.getElementById("div_g"),
-            NoisyData, {
-              rollPeriod: 7,
-              showRoller: true,
-              errorBars: true,
-
-              highlightCallback: function(e, x, pts) {
-                for (var i = 0; i < pts.length; i++) {
-                  var y = pts[i].canvasy;
-                  lines[i].style.display = "";
-                  lines[i].style.top = y + "px";
-                  if (i == 0) xline.style.left = pts[i].canvasx + "px";
-                }
-                xline.style.display = "";
-              },
-
-              unhighlightCallback: function(e) {
-                for (var i = 0; i < 2; i++) {
-                  lines[i].style.display = "none";
-                }
-                xline.style.display = "none";
-              }
-            }
-          );
-
-      for (var i = 0; i < 2; i++) {
-        var line = document.createElement("div");
-        line.style.display = "none";
-        line.style.width = "100%";
-        line.style.height = "1px";
-        line.style.backgroundColor = "black";
-        line.style.position = "absolute";
-        document.getElementById("div_g").appendChild(line);
-        lines.push(line);
-      }
-
-      xline = document.createElement("div");
-      xline.style.display = "none";
-      xline.style.width = "1px";
-      xline.style.height = "100%";
-      xline.style.top = "0px";
-      xline.style.backgroundColor = "black";
-      xline.style.position = "absolute";
-      document.getElementById("div_g").appendChild(xline);
-    </script>
-  </body>
-</html>
diff --git a/tests/csv-numeric-x.html b/tests/csv-numeric-x.html
deleted file mode 100644 (file)
index 6bea2fb..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-    <title>CSV with numeric X series</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>
-    <p>CSV data with a numeric (non-date) X series.</p>
-    <div id="graph"></div>
-    <script type="text/javascript">
-    g = new Dygraph(
-      document.getElementById("graph"),
-      "X,Y\n" +
-      "1,0\n" +
-      "2,10\n" +
-      "3,0\n" +
-      "4,-10\n" +
-      "5,0\n"
-    );
-    </script>
-  </body>
-</html>
index 7a30ea2..f2ef094 100644 (file)
   </head>
   <body>
     <h2>Custom circles and hover circles</h2>
-    <div id="demodiv"></div>
 
     <script type="text/javascript">
+      // Simple version
+      var div = document.createElement('div');
+      document.body.appendChild(div);
+
+      var data = 'x,A,B\n' +
+        '1,1,2\n' +
+        '2,2,4\n' +
+        '3,3,6\n' +
+        '4,4,8\n' +
+        '5,5,7\n';
+      var g = new Dygraph(div, data, {
+            drawPoints : true,
+            pointSize : 5,
+            highlightCircleSize: 8,
+            A : {
+              drawPointCallback : Dygraph.Circles.TRIANGLE,
+              drawHighlightPointCallback : Dygraph.Circles.TRIANGLE
+            },
+            B : {
+              drawPointCallback : Dygraph.Circles.HEXAGON,
+              drawHighlightPointCallback : Dygraph.Circles.HEXAGON
+            }
+        });
+
+
+      // Fancy demos
       var smile = function(g, series, ctx, cx, cy, color, radius) {
         mouthlessFace(g, series, ctx, cx, cy, color, radius);
 
@@ -30,6 +55,7 @@
       var frown = function(g, series, ctx, cx, cy, color, radius) {
         mouthlessFace(g, series, ctx, cx, cy, color, radius);
 
+        ctx.lineWidth = 1;
         ctx.fillStyle = "#000000";
         ctx.beginPath();
         ctx.arc(cx, cy + radius, radius - 2, Math.PI + .3, -.3, false);
@@ -37,6 +63,7 @@
       };
 
       var mouthlessFace = function(g, series, ctx, cx, cy, color, radius) {
+        ctx.lineWidth = 1;
         ctx.strokeStyle = "#000000";
         ctx.fillStyle = "#FFFF00";
         ctx.beginPath();
         ctx.fill();
       };
 
-      g = new Dygraph(
-          document.getElementById("demodiv"),
-          function() {
+      var makeGraph = function(title, yFunc, extraOpts) {
+        var opts = {
+            drawPoints : true,
+            pointSize : 5
+        };
+
+        var shapes = [];
+        var addShape = function(name, pointFn, highlightPointFn) {
+          shapes.push(name);
+          opts[name] = {
+            drawPointCallback: pointFn,
+            drawHighlightPointCallback: highlightPointFn
+          };
+        };
+
+        for (var shape in Dygraph.Circles) {
+          if (!Dygraph.Circles.hasOwnProperty(shape)) continue;
+          var fn = Dygraph.Circles[shape];
+          if (typeof fn !== 'function') continue;
+          addShape(shape.toLowerCase(), fn, fn);
+        };
+        addShape('custom', frown, smile);
 
-            var r = "xval,default,triangle,square,diamond,pentagon,hexagon,circle,star,plus,ex,custom\n";
+        for (var key in extraOpts) {
+          if (extraOpts.hasOwnProperty(key)) {
+            opts[key] = extraOpts[key];
+          }
+        };
+
+        var header = document.createElement('h3');
+        header.appendChild(document.createTextNode(title));
+        document.body.appendChild(header);
+
+        var div = document.createElement('div');
+        document.body.appendChild(div);
+
+        var g = new Dygraph(
+          div,
+          function() {
+            var r = "xval," + shapes.join(',') + "\n";
+            var n = shapes.length;
             for (var i=1; i<=20; i++) {
               r += i;
-              for (var j = 0; j < 11; j++) {
-                r += "," + j + (i / 3);
+              for (var j = 0; j < n; j++) {
+                r += "," + yFunc(i, j, n);
               }
               r += "\n";
             }
             return r;
-          },
-          {
-            drawPoints : true,
-            pointSize : 5,
-            highlightCircleSize : 8,
-            'default' : {
-              drawPointCallback : Dygraph.Circles.DEFAULT,
-              drawHighlightPointCallback : Dygraph.Circles.DEFAULT
-            },
-            'triangle' : {
-              drawPointCallback : Dygraph.Circles.TRIANGLE,
-              drawHighlightPointCallback : Dygraph.Circles.TRIANGLE
-            },
-            'square' : {
-              drawPointCallback : Dygraph.Circles.SQUARE,
-              drawHighlightPointCallback : Dygraph.Circles.SQUARE
-            },
-            'diamond' : {
-              drawPointCallback : Dygraph.Circles.DIAMOND,
-              drawHighlightPointCallback : Dygraph.Circles.DIAMOND
-            },
-            'pentagon' : {
-              drawPointCallback : Dygraph.Circles.PENTAGON,
-              drawHighlightPointCallback : Dygraph.Circles.PENTAGON
-            },
-            'hexagon' : {
-              drawPointCallback : Dygraph.Circles.HEXAGON,
-              drawHighlightPointCallback : Dygraph.Circles.HEXAGON
-            },
-            'circle' : {
-              drawPointCallback : Dygraph.Circles.CIRCLE,
-              drawHighlightPointCallback : Dygraph.Circles.CIRCLE
-            },
-            'star' : {
-              drawPointCallback : Dygraph.Circles.STAR,
-              drawHighlightPointCallback : Dygraph.Circles.STAR
-            },
-            'plus' : {
-              drawPointCallback : Dygraph.Circles.PLUS,
-              drawHighlightPointCallback : Dygraph.Circles.PLUS
-            },
-            'ex' : {
-              drawPointCallback : Dygraph.Circles.EX,
-              drawHighlightPointCallback : Dygraph.Circles.EX
-            },
-            'custom' : {
-              drawPointCallback : frown,
-              drawHighlightPointCallback : smile
-            }
-          }
-      );
+          }, opts);
+        };
+
+        makeGraph(
+          "Gallery of predefined shapes, adding a custom shape:",
+          function(x, c, n) {
+            return x / 3 + c * 10;
+          }, {
+            highlightCircleSize : 8
+          });
+        makeGraph(
+          "With interactive per-series highlighting:",
+          function(x, c, n) {
+            return Math.sin(x * c / n);
+          }, {
+            strokeBorderWidth: 2,
+            highlightSeriesOpts: {
+              pointSize: 6,
+              highlightCircleSize: 10,
+              strokeWidth: 2,
+            }});
     </script>
 </body>
 </html>
diff --git a/tests/customLabelCss3.html b/tests/customLabelCss3.html
new file mode 100644 (file)
index 0000000..a5c7c3a
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Label styles</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>
+
+    <script type="text/javascript" src="data.js"></script>
+  </head>
+  <body>
+    <p>Labels are styled with css3:</p>
+    <div id="div_g14" style="width:600px; height:300px;"></div>
+
+    <script type="text/javascript">
+      g14 = new Dygraph(
+            document.getElementById("div_g14"),
+            NoisyData, {
+              rollPeriod: 14,
+              errorBars: true,
+              labelsDivWidth: 100,
+              labelsDivStyles: {
+                'backgroundColor': 'rgba(200, 200, 255, 0.75)',
+                'padding': '4px',
+                'border': '1px solid black',
+                'borderRadius': '10px',
+                'boxShadow': '4px 4px 4px #888'
+              },
+              labelsSeparateLines: true,
+              yAxisLabelWidth: 20
+            }
+          );
+    </script>
+  </body>
+</html>
diff --git a/tests/noise.html b/tests/noise.html
deleted file mode 100644 (file)
index 4ddb492..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-    <title>noise</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>
-
-    <script type="text/javascript" src="data.js"></script>
-  </head>
-  <body>
-    <div id="div_g" style="width:600px; height:300px;"></div>
-    <br/>
-    <div id="div_g30" style="width:600px; height:300px;"></div>
-
-    <script type="text/javascript">
-      g = new Dygraph(
-            document.getElementById("div_g"),
-            NoisyData, {
-              rollPeriod: 7,
-              errorBars: true,
-              legend: 'always',
-              ylabel: 'Percent Error',
-              title: 'Error Rates over Time (7-day rollup)'
-            }
-          );
-      g30 = new Dygraph(
-            document.getElementById("div_g30"),
-            NoisyData().replace(/,/g, "\t"), {
-              rollPeriod: 14,
-              errorBars: true,
-              legend: 'always',
-              labelsDivStyles: {
-                'textAlign': 'right',
-              },
-              labelsDivWidth: 170,
-              ylabel: 'Percent Error',
-              title: 'Error Rates over Time (14-day rollup)'
-            }
-          );
-    </script>
-  </body>
-</html>
diff --git a/tests/number-display.html b/tests/number-display.html
deleted file mode 100644 (file)
index c6d93b3..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-    <title>dygraphs number display</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>
-
-    <style type="text/css">
-      .thinborder {
-        border: 1px solid black;
-        border-spacing: 0px;
-        border-collapse: collapse;
-      }
-      .thinborder td,
-      .thinborder th {
-        border: 1px solid black;
-        padding: 5px;
-      }
-    </style>
-  </head>
-  <body>
-    <h2>dygraphs number display</h2>
-    <p>Dygraphs can display numbers in either scientific mode (fixed number of significant figures) or fixed-point mode (fixed number of digits after the decimal point). It is in fixed-point mode by default.</p>
-    <p>To switch to scientific mode, set the <i>sigFigs</i> option to the number of significant figures in your data.</p>
-    <p>In fixed-point mode, you can control the number of digits after the decimal using the <i>digitsAfterDecimal</i> option. For particularly large numbers, this format can get unwieldy (i.e. '100000000' for 100M is a bit lengthy). Once the numbers get to a certain length, dygraphs will switch over to scientific notation. This is controlled by the <i>maxNumberWidth</i> option.</p>
-
-    <div id='blah'></div>
-
-    <script type="text/javascript">
-      var nums = [
-        -1.234e10,
-        -1e10,
-        -1.23e4,
-        -123.456789,
-        -123,
-        -1,
-        -0.123456,
-        -0.1,
-        -0.001234567,
-        -0.001,
-        -0.0000000001,
-        0,
-        0.0000000001,
-        0.001,
-        0.001234567,
-        0.1,
-        0.123456,
-        1,
-        3,
-        3.14,
-        3.14159,
-        3.14159265,
-        3.14159265358,
-        123,
-        123.456789,
-        1.23e4,
-        1e5,
-        1e6,
-        1e7,
-        1e8,
-        1e9,
-        1e10,
-        1.234e10
-      ];
-
-    var scientific = [ 1, 2, 3, 4, 5, 6 ];
-    var fixed = [ [2, 6], [3, 6], [5, 6], [1, 10], [2, 10], [5, 10] ];
-
-    // Helper functions for generating an HTML table for holding the test
-    // results.
-    createRow = function(columnType, columns) {
-      var row = document.createElement('tr');
-      for (var i = 0; i  < columns.length; i ++) {
-        var th = document.createElement(columnType);
-        var text = document.createTextNode(columns[i]);
-        th.appendChild(text);
-        row.appendChild(th);
-      };
-      return row;
-    };
-
-    var html = '<table class=thinborder>';
-    html += '<tr><th>&nbsp;</th><th colspan=' + scientific.length + '>Scientific (sigFigs)</th><th colspan=' + fixed.length + '>Fixed (digitsAfterDecimal, maxNumberWidth)</th></tr>\n';
-    html += '<tr><th>Number</th>';
-    for (var i = 0; i < scientific.length; i++) {
-      html += '<th>' + scientific[i] + '</th>';
-    }
-    for (var i = 0; i < fixed.length; i++) {
-      html += '<th>' + fixed[i] + '</th>';
-    }
-    html += '</tr>\n';
-
-    var attr = {};
-    var opts = function(x) {
-      return attr[x];
-    };
-    for (var j = 0; j < nums.length; j++) {
-      var x = nums[j];
-      html += '<tr>';
-      html += '<td>' + x + '</td>';
-      for (var i = 0; i < scientific.length; i++) {
-        attr = { sigFigs: scientific[i] };
-        html += '<td>' + Dygraph.numberFormatter(x, opts) + '</td>';
-      }
-      for (var i = 0; i < fixed.length; i++) {
-        attr = { sigFigs: null, digitsAfterDecimal: fixed[i][0], maxNumberWidth: fixed[i][1] };
-        html += '<td>' + Dygraph.numberFormatter(x, opts) + '</td>';
-      }
-      html += '</tr>\n';
-    }
-
-    html += '</table>';
-    document.getElementById('blah').innerHTML = html;
-    </script>
-</body>
-</html>
diff --git a/tests/series-highlight.html b/tests/series-highlight.html
new file mode 100644 (file)
index 0000000..94e09d6
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Series highlighting</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>
+
+    <style type='text/css'>
+      .few .dygraph-legend > span.highlight { border: 1px solid grey; }
+      .many .dygraph-legend > span { display: none; }
+      .many .dygraph-legend > span.highlight { display: inline; }
+    </style>
+  </head>
+  <body>
+    <h2>Series highlighting demo</h2>
+<script type='text/javascript'>
+
+var getData = function(numSeries, numRows, isStacked) {
+  var data = [];
+
+  for (var j = 0; j < numRows; ++j) {
+    data[j] = [j];
+  }
+  for (var i = 0; i < numSeries; ++i) {
+    var val = 0;
+    for (var j = 0; j < numRows; ++j) {
+      if (isStacked) {
+        val = Math.random();
+      } else {
+        val += Math.random() - 0.5;
+      }
+      data[j][i + 1] = val;
+    }
+  }
+  return data;
+};
+
+var makeGraph = function(className, numSeries, numRows, isStacked) {
+  var div = document.createElement('div');
+  div.className = className;
+  div.style.display = 'inline-block';
+  document.body.appendChild(div);
+
+  var labels = ['x'];
+  for (var i = 0; i < numSeries; ++i) {
+    var label = '' + i;
+    label = 's' + '000'.substr(label.length) + label;
+    labels[i + 1] = label;
+  }
+  var g = new Dygraph(
+      div,
+      getData(numSeries, numRows, isStacked),
+      {
+        width: 480,
+        height: 320,
+        labels: labels.slice(),
+        stackedGraph: isStacked,
+
+        highlightCircleSize: 2,
+        strokeWidth: 1,
+        strokeBorderWidth: isStacked ? null : 1,
+
+        highlightSeriesOpts: {
+          strokeWidth: 3,
+          strokeBorderWidth: 1,
+          highlightCircleSize: 5,
+        },
+      });
+  g.setSelection(false, 's005');
+  //console.log(g);
+};
+
+makeGraph("few", 20, 50, false);
+makeGraph("few", 10, 20, true);
+makeGraph("many", 75, 50, false);
+makeGraph("many", 40, 50, true);
+</script>
+</body>
+</html>
diff --git a/tests/two-series.html b/tests/two-series.html
deleted file mode 100644 (file)
index 6054f2e..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-    <title>two series</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>
-
-    <script type="text/javascript" src="data.js"></script>
-  </head>
-  <body>
-    <p>No rollup:</p>
-    <div id="div_g" style="width:600px; height:300px;"></div>
-    <p>30-Day Rollup:</p>
-    <div id="div_g30" style="width:600px; height:300px;"></div>
-
-    <script type="text/javascript">
-      g = new Dygraph(
-            document.getElementById("div_g"),
-            data, {}
-          );
-      g30 = new Dygraph(
-            document.getElementById("div_g30"),
-            data, {
-              rollPeriod: 30
-            }
-          );
-    </script>
-  </body>
-</html>
diff --git a/tests/y-axis-formatter.html b/tests/y-axis-formatter.html
deleted file mode 100644 (file)
index 76f6ffd..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
-        <title>dygraph</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>
-
-        <h1>Potential Y Axis formatting problems for small values</h1>
-
-        <p>The problem using default y axis formatting for very small values:<br/>
-        (this was more of a problem before dygraphs automatically switched to scientific notation)</p>
-        <div id="graph1"></div>
-        <script type="text/javascript">
-            var g1 = new Dygraph(
-                document.getElementById("graph1"),
-                [
-                    [1, 0.0],
-                    [2, 0.0001],
-                    [3, 0.0002],
-                    [4, 0.0004],
-                    [5, 0.0005]
-                ],
-                {
-                    stepPlot: true,
-                    labels: ["X", "Data"]
-                }
-            );
-        </script>
-
-        <p>The solution using a Y axis formatting function:</p>
-        <div id="graph2"></div>
-        <script type="text/javascript">
-            var g2 = new Dygraph(
-                document.getElementById("graph2"),
-                [
-                    [1, 0.0],
-                    [2, 0.0001],
-                    [3, 0.0002],
-                    [4, 0.0004],
-                    [5, 0.0005]
-                ],
-                {
-                    stepPlot: true,
-                    labels: ["X", "Data"],
-                    axes: {
-                      y: {
-                        valueFormatter: function(x) {
-                          var shift = Math.pow(10, 5)
-                          return Math.round(x * shift) / shift
-                        },
-                      }
-                    }
-                }
-            );
-        </script>
-
-        <p>Different yValueFormatter and yAxisLabelFormatter functions:</p>
-        <div id="graph3"></div>
-        <script type="text/javascript">
-            var g3 = new Dygraph(
-                document.getElementById("graph3"),
-                [
-                    [1, 0.0],
-                    [2, 0.0001],
-                    [3, 0.0002],
-                    [4, 0.0004],
-                    [5, 0.0005]
-                ],
-                {
-                    stepPlot: true,
-                    labels: ["X", "Data"],
-                    axes: {
-                      y: {
-                        valueFormatter: function(x) {
-                          var shift = Math.pow(10, 5)
-                          return "*" + Math.round(x * shift) / shift
-                        },
-                        axisLabelFormatter: function(x) {
-                          var shift = Math.pow(10, 5)
-                          return "+" + Math.round(x * shift) / shift
-                        }
-                      }
-                    }
-                }
-            );
-        </script>
-
-    </body>
-</html>