Merge branch 'master' of git://github.com/danvk/dygraphs into highlight3
authorKlaus Weidner <klausw@google.com>
Sat, 25 Feb 2012 20:18:26 +0000 (12:18 -0800)
committerKlaus Weidner <klausw@google.com>
Sat, 25 Feb 2012 20:18:26 +0000 (12:18 -0800)
Conflicts:
auto_tests/tests/callback.js
dygraph-canvas.js
dygraph-options-reference.js

1  2 
auto_tests/tests/callback.js
dygraph-canvas.js
dygraph-options-reference.js
dygraph.js

@@@ -1,4 -1,4 +1,4 @@@
--/** 
++/**
   * @fileoverview Test cases for the callbacks.
   *
   * @author uemit.seren@gmail.com (Ümit Seren)
@@@ -54,113 -51,95 +54,206 @@@ CallbackTestCase.prototype.testHighligh
    assertEquals(2, h_pts.length);
  };
  
 -  }; 
+ /**
+  * Test that drawPointCallback isn't called when drawPoints is false
+  */
+ CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
+   var called = false;
+   var callback = function() {
+     called = true;
 -  }; 
++  };
+   var graph = document.getElementById("graph");
+   var g = new Dygraph(graph, data, {
+       drawPointCallback : callback,
+     });
+   assertFalse(called);
+ };
+ /**
+  * Test that drawPointCallback is called when drawPoints is true
+  */
+ CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
+   var called = false;
+   var callback = function() {
+     called = true;
 -  }; 
++  };
+   var graph = document.getElementById("graph");
+   var g = new Dygraph(graph, data, {
+       drawPoints : true,
+       drawPointCallback : callback
+     });
+   assertTrue(called);
+ };
+ /**
+  * Test that drawPointCallback is called when drawPoints is true
+  */
+ CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
+   var pointSize = 0;
+   var count = 0;
+   var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
+     pointSize = pointSizeParam;
+     count++;
 - * is properly called when the first series is hidden (setVisibility = false) 
 - * 
++  };
+   var graph = document.getElementById("graph");
+   var g = new Dygraph(graph, data, {
+       drawPoints : true,
+       drawPointCallback : callback
+     });
+   assertEquals(1.5, pointSize);
+   assertEquals(12, count); // one call per data point.
+   var g = new Dygraph(graph, data, {
+       drawPoints : true,
+       drawPointCallback : callback,
+       pointSize : 8
+     });
+   assertEquals(8, pointSize);
+ };
+ /**
+  * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
 -  }; 
++ * is properly called when the first series is hidden (setVisibility = false)
++ *
+  */
+ CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
+   var called = false;
+   var drawHighlightPointCallback  = function() {
+     called = true;
++  };
+   var graph = document.getElementById("graph");
+   var g = new Dygraph(graph, data,
+       {
+         width: 100,
+         height : 100,
+         drawHighlightPointCallback : drawHighlightPointCallback
+       });
+   assertFalse(called);
+   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,
 +        highlightSeriesBackgroundFade: 0.7,
 +        highlightSeriesAnimate: true,
 +
 +        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);
 +}
 +
 +/**
 + * 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);
 +  // TODO(klausw): verify that the highlighted line is drawn on top?
 +}
@@@ -658,104 -658,6 +658,114 @@@ DygraphCanvasRenderer.prototype._render
    }
  };
  
-     ctx, i, color, strokeWidth, strokePattern, drawPoints, pointSize) {
 +DygraphCanvasRenderer.prototype._drawStyledLine = function(
-         ctx.beginPath();
-         ctx.fillStyle = color;
-         ctx.arc(point.canvasx, point.canvasy, pointSize,
-                 0, 2 * Math.PI, false);
-         ctx.fill();
++    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) {
-     this._drawStyledLine(ctx, i,
++        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,
++    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.
@@@ -78,27 -93,21 +93,40 @@@ Dygraph.OPTIONS_REFERENCE =  // <JSON
    "highlightCallback": {
      "default": "null",
      "labels": ["Callbacks"],
-     "type": "function(event, x, points, row, closestSeries)",
-     "description": "When set, this callback gets called every time a new point is highlighted. The parameters are the JavaScript mousemove event, the x-coordinate of the highlighted points, an array of highlighted points: <code>[ {name: 'series', yval: y-value}, &hellip; ]</code>, and the index of the data row corresponding to the x-coordinate. If highlightSeriesOpts is set, closestSeries is passed as an additional argument giving the name of the timeseries closest to the mouse pointer, and the callback gets called whenever this changes, including vertical movement."
 -    "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": {
+     "default": "null",
+     "labels": ["Data Line display"],
+     "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)",
+     "description": "Draw a custom item when a point is highlighted. Default is a small dot matching the series color."
    },
 +  "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 }."
 +  },
 +  "highlightSeriesBackgroundFade": {
 +    "default": "0",
 +    "labels": ["Interactive Elements"],
 +    "type": "number",
 +    "description": "When nonzero, dim the background while highlighting series. 0=fully visible, 1=hidden"
 +  },
 +  "highlightSeriesAnimate": {
 +    "default": "false",
 +    "labels": ["Interactive Elements"],
 +    "type": "boolean",
 +    "description": "Animate the background dimming for nonzero highlightSeriesBackgroundFade."
 +  },
    "includeZero": {
      "default": "false",
      "labels": ["Axis display"],
diff --cc dygraph.js
Simple merge