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

auto_tests/tests/callback.js
dygraph-canvas.js
dygraph-options-reference.js
dygraph-utils.js
dygraph.js
generate-documentation.py
tests/custom-circles.html [new file with mode: 0644]

index 6281b92..1b851b5 100644 (file)
@@ -1,4 +1,4 @@
-/** 
+/**
  * @fileoverview Test cases for the callbacks.
  *
  * @author uemit.seren@gmail.com (Ümit Seren)
@@ -54,6 +54,99 @@ CallbackTestCase.prototype.testHighlightCallbackIsCalled = function() {
   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++;
+  };
+
+  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
index 7ae3c87..5fd02e5 100644 (file)
@@ -659,7 +659,8 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() {
 };
 
 DygraphCanvasRenderer.prototype._drawStyledLine = function(
-    ctx, i, color, strokeWidth, strokePattern, drawPoints, pointSize) {
+    ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+    drawPointCallback, pointSize) {
   var isNullOrNaN = function(x) {
     return (x === null || isNaN(x));
   };
@@ -671,6 +672,7 @@ DygraphCanvasRenderer.prototype._drawStyledLine = function(
   var points = this.layout.points;
   var prevX = null;
   var prevY = null;
+  var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
   if (!Dygraph.isArrayLike(strokePattern)) {
     strokePattern = null;
   }
@@ -723,14 +725,18 @@ DygraphCanvasRenderer.prototype._drawStyledLine = function(
       }
 
       if (drawPoints || isIsolated) {
-        ctx.beginPath();
-        ctx.fillStyle = color;
-        ctx.arc(point.canvasx, point.canvasy, pointSize,
-                0, 2 * Math.PI, false);
-        ctx.fill();
+        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();
 };
 
@@ -740,20 +746,24 @@ DygraphCanvasRenderer.prototype._drawLine = function(ctx, 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,
+  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));
 };
 
index 15c1507..9217f3d 100644 (file)
@@ -41,7 +41,13 @@ 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."
+    "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 drawPointCallback."
+  },
+  "drawPointCallback": {
+    "default": "null",
+    "labels": ["Data Line display"],
+    "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)",
+    "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color."
   },
   "height": {
     "default": "320",
@@ -53,14 +59,23 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "labels": ["Callbacks"],
     "type": "function(minDate, maxDate, yRanges)",
-    "description": "A function to call when the zoom window is changed (either by zooming in or out). minDate and maxDate are milliseconds since epoch. yRanges is an array of [bottom, top] pairs, one for each y-axis."
+    "parameters": [
+      [ "minDate" , "milliseconds since epoch" ],
+      [ "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)."
   },
   "pointClickCallback": {
     "snippet": "function(e, point){<br>&nbsp;&nbsp;alert(point);<br>}",
     "default": "null",
     "labels": ["Callbacks", "Interactive Elements"],
     "type": "function(e, point)",
-    "description": "A function to call when a data point is clicked. The function should take two arguments, the event object for the click, and the point that was clicked. The 'point' argument has these properties:\n * xval/yval: The data coordinates of the point (with dates/times as millis since epoch) \n * canvasx/canvasy: The canvas coordinates at which the point is drawn. \n name: The name of the data series to which the point belongs"
+    "parameters": [
+      [ "e" , "the event object for the click" ],
+      [ "point" , "the point that was clicked See <a href='#point_properties'>Point properties</a> for details" ]
+    ],
+    "description": "A function to call when a data point is clicked. and the point that was clicked."
   },
   "colors": {
     "default": "(see description)",
@@ -78,8 +93,21 @@ 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, 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", "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",
@@ -115,7 +143,10 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "labels": ["Callbacks"],
     "type": "function(event)",
-    "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph.  The parameter is the mouseout event."
+    "parameters": [
+      [ "event" , "the mouse event" ]
+    ],
+    "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph."
   },
   "axisTickSize": {
     "default": "3.0",
@@ -157,25 +188,47 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "labels": ["Annotations"],
     "type": "function(annotation, point, dygraph, event)",
+    "parameters": [
+      [ "annotation" , "the annotation left" ],
+      [ "point" , "the point associated with the annotation" ],
+      [ "dygraph" , "the reference graph" ],
+      [ "event" , "the mouse event" ]
+    ],
     "description": "If provided, this function is called whenever the user mouses out of an annotation."
   },
   "annotationClickHandler": {
     "default": "null",
     "labels": ["Annotations"],
     "type": "function(annotation, point, dygraph, event)",
+    "parameters": [
+      [ "annotation" , "the annotation left" ],
+      [ "point" , "the point associated with the annotation" ],
+      [ "dygraph" , "the reference graph" ],
+      [ "event" , "the mouse event" ]
+    ],
     "description": "If provided, this function is called whenever the user clicks on an annotation."
   },
   "annotationDblClickHandler": {
     "default": "null",
     "labels": ["Annotations"],
     "type": "function(annotation, point, dygraph, event)",
+    "parameters": [
+      [ "annotation" , "the annotation left" ],
+      [ "point" , "the point associated with the annotation" ],
+      [ "dygraph" , "the reference graph" ],
+      [ "event" , "the mouse event" ]
+    ],
     "description": "If provided, this function is called whenever the user double-clicks on an annotation."
   },
   "drawCallback": {
     "default": "null",
     "labels": ["Callbacks"],
     "type": "function(dygraph, is_initial)",
-    "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning. The first parameter is the dygraph being drawn. The second is a boolean value indicating whether this is the initial draw."
+    "parameters": [
+      [ "dygraph" , "The graph being drawn" ],
+      [ "is_initial" , "True if this is the initial draw, false for subsequent draws." ]
+    ],
+    "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning."
   },
   "labelsKMG2": {
     "default": "false",
@@ -199,6 +252,11 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "labels": ["Callbacks"],
     "type": "function(canvas, area, dygraph)",
+    "parameters": [
+      [ "canvas" , "the canvas to draw on" ],
+      [ "area" , "" ],
+      [ "dygraph" , "the reference graph" ]
+    ],
     "description": "When set, this callback gets called before the chart is drawn. It details on how to use this."
   },
   "width": {
@@ -217,6 +275,14 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "Dygraph.dateTicker or Dygraph.numericTicks",
     "labels": ["Axis display"],
     "type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]",
+    "parameters": [
+      [ "min" , "" ],
+      [ "max" , "" ],
+      [ "pixels" , "" ],
+      [ "opts" , "" ],
+      [ "dygraph" , "the reference graph" ],
+      [ "vals" , "" ]
+    ],
     "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a <a href='per-axis.html'>per-axis</a> basis."
   },
   "xAxisLabelWidth": {
@@ -415,14 +481,25 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "Depends on the data type",
     "labels": ["Axis display"],
     "type": "function(number or Date, granularity, opts, dygraph)",
-    "description": "Function to call to format the tick values that appear along an axis. This is usually set on a <a href='per-axis.html'>per-axis</a> basis. The first parameter is either a number (for a numeric axis) or a Date object (for a date axis). The second argument specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY. opts is a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')."
+    "parameters": [
+      [ "number or date" , "Either a number (for a numeric axis) or a Date object (for a date axis)" ],
+      [ "granularity" , "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY." ],
+      [ "opts" , "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')." ],
+      [ "dygraph" , "the referenced graph" ]
+    ],
+    "description": "Function to call to format the tick values that appear along an axis. This is usually set on a <a href='per-axis.html'>per-axis</a> basis."
   },
   "clickCallback": {
     "snippet": "function(e, date_millis){<br>&nbsp;&nbsp;alert(new Date(date_millis));<br>}",
     "default": "null",
     "labels": ["Callbacks"],
     "type": "function(e, x, points)",
-    "description": "A function to call when the canvas is clicked. The function should take three arguments, the event object for the click, the x-value that was clicked (for dates this is millis since epoch), and the closest points along that date. The points have these properties:\n * xval/yval: The data coordinates of the point (with dates/times as millis since epoch) \n * canvasx/canvasy: The canvas coordinates at which the point is drawn. \n name: The name of the data series to which the point belongs"
+    "parameters": [
+      [ "e" , "The event object for the click" ],
+      [ "x" , "The x value that was clicked (for dates, this is milliseconds since epoch)" ],
+      [ "points" , "The closest points along that date. See <a href='#point_properties'>Point properties</a> for details." ]
+    ],
+    "description": "A function to call when the canvas is clicked."
   },
   "yAxisLabelFormatter": {
     "default": "",
index 5a21430..61aba43 100644 (file)
@@ -700,7 +700,9 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
     'clickCallback': true,
     'digitsAfterDecimal': true,
     'drawCallback': true,
+    'drawHighlightPointCallback': true,
     'drawPoints': true,
+    'drawPointCallback': true,
     'drawXGrid': true,
     'drawYGrid': true,
     'fillAlpha': true,
@@ -805,3 +807,110 @@ Dygraph.compareArrays = function(array1, array2) {
   }
   return true;
 };
+
+/**
+ * ctx: the canvas context
+ * sides: the number of sides in the shape.
+ * radius: the radius of the image.
+ * cx: center x coordate
+ * cy: center y coordinate
+ * rotationRadians: the shift of the initial angle, in radians.
+ * delta: the angle shift for each line. If missing, creates a regular
+ *   polygon.
+ */
+Dygraph.regularShape_ = function(
+    ctx, sides, radius, cx, cy, rotationRadians, delta) {
+  rotationRadians = rotationRadians ? rotationRadians : 0;
+  delta = delta ? delta : Math.PI * 2 / sides;
+
+  ctx.beginPath();
+  var first = true;
+  var initialAngle = rotationRadians;
+  var angle = initialAngle;
+
+  var computeCoordinates = function() {
+    var x = cx + (Math.sin(angle) * radius);
+    var y = cy + (-Math.cos(angle) * radius);
+    return [x, y]; 
+  };
+
+  var initialCoordinates = computeCoordinates();
+  var x = initialCoordinates[0];
+  var y = initialCoordinates[1];
+  ctx.moveTo(x, y);
+
+  for (var idx = 0; idx < sides; idx++) {
+    angle = (idx == sides - 1) ? initialAngle : (angle + delta);
+    var coords = computeCoordinates();
+    ctx.lineTo(coords[0], coords[1]);
+  }
+  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;
+    Dygraph.regularShape_(ctx, sides, radius, cx, cy, rotationRadians, delta);
+  };
+};
+
+Dygraph.DrawPolygon_ = function(sides, rotationRadians, ctx, cx, cy, color, radius, delta) {
+  new Dygraph.RegularShape_(sides, rotationRadians, delta).draw(ctx, cx, cy, radius);
+}
+
+Dygraph.Circles = {
+  DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) {
+    ctx.beginPath();
+    ctx.fillStyle = color;
+    ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false);
+    ctx.fill();
+  },
+  TRIANGLE : Dygraph.shapeFunction_(3),
+  SQUARE : Dygraph.shapeFunction_(4, Math.PI / 4),
+  DIAMOND : Dygraph.shapeFunction_(4),
+  PENTAGON : Dygraph.shapeFunction_(5),
+  HEXAGON : Dygraph.shapeFunction_(6),
+  CIRCLE : function(g, name, ctx, cx, cy, color, radius) {
+    ctx.beginPath();
+    ctx.strokeStyle = color;
+    ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false);
+    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();
+    ctx.moveTo(cx + radius, cy);
+    ctx.lineTo(cx - radius, cy);
+    ctx.closePath();
+    ctx.stroke();
+
+    ctx.beginPath();
+    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.beginPath();
+    ctx.moveTo(cx + radius, cy + radius);
+    ctx.lineTo(cx - radius, cy - radius);
+    ctx.closePath();
+    ctx.stroke();
+
+    ctx.beginPath();
+    ctx.moveTo(cx + radius, cy - radius);
+    ctx.lineTo(cx - radius, cy + radius);
+    ctx.closePath();
+
+    ctx.stroke();
+  }
+};
index 148da34..1d97bda 100644 (file)
@@ -1922,10 +1922,12 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) {
       if (!Dygraph.isOK(pt.canvasy)) continue;
 
       var circleSize = this.attr_('highlightCircleSize', pt.name);
-      ctx.beginPath();
-      ctx.fillStyle = this.plotter_.colors[pt.name];
-      ctx.arc(canvasx, pt.canvasy, circleSize, 0, 2 * Math.PI, false);
-      ctx.fill();
+      var callback = this.attr_("drawHighlightPointCallback", pt.name);
+      if (!callback) {
+        callback = Dygraph.Circles.DEFAULT;
+      }
+      callback(this.g, pt.name, ctx, canvasx, pt.canvasy,
+          this.plotter_.colors[pt.name], circleSize);
     }
     ctx.restore();
 
index 2dd5b98..0614bef 100755 (executable)
@@ -71,7 +71,7 @@ for nu, opt in docs.iteritems():
     if label not in labels:
       labels.append(label)
 
-print """
+print """<!DOCTYPE HTML>
 <html>
 <head>
   <title>Dygraphs Options Reference</title>
@@ -80,6 +80,9 @@ print """
     p.option {
       padding-left: 25px;
     }
+    div.parameters {
+      padding-left: 15px;
+    }
     #nav {
       position: fixed;
     }
@@ -92,7 +95,7 @@ print """
 """
 
 print """
-<div id=nav>
+<div id='nav'>
 <h2>Dygraphs</h2>
 <ul>
   <li><a href="index.html">Home</a>
@@ -112,12 +115,12 @@ def name(f):
   return f.replace('tests/', '').replace('.html', '')
 
 print """
-<div id=content>
+<div id='content'>
 <h2>Options Reference</h2>
 <p>Dygraphs tries to do a good job of displaying your data without any further configuration. But inevitably, you're going to want to tinker. Dygraphs provides a rich set of options for configuring its display and behavior.</p>
 
-<a name="usage"><h3>Usage</h3>
-<p>You specify options in the third parameter to the dygraphs constructor:
+<a name="usage"></a><h3>Usage</h3>
+<p>You specify options in the third parameter to the dygraphs constructor:</p>
 <pre>g = new Dygraph(div,
                 data,
                 {
@@ -127,13 +130,12 @@ print """
                 });
 </pre>
 
-After you've created a Dygraph, you can change an option by calling the <code>updateOptions</code> method:
+<p>After you've created a Dygraph, you can change an option by calling the <code>updateOptions</code> method:</p>
 <pre>g.updateOptions({
                   new_option1: value1,
                   new_option2: value2
                 });
 </pre>
-
 <p>And, without further ado, here's the complete list of options:</p>
 """
 for label in sorted(labels):
@@ -149,25 +151,40 @@ for label in sorted(labels):
       examples_html = ' '.join(
         '<a href="%s">%s</a>' % (f, name(f)) for f in tests)
 
+    if 'parameters' in opt:
+      parameters = opt['parameters']
+      parameters_html = '\n'.join("<i>%s</i>: %s<br/>" % (p[0], p[1]) for p in parameters)
+      parameters_html = "\n  <div class='parameters'>\n%s</div>" % (parameters_html);
+    else:
+      parameters_html = ''
+
     if not opt['type']: opt['type'] = '(missing)'
     if not opt['default']: opt['default'] = '(missing)'
     if not opt['description']: opt['description'] = '(missing)'
 
     print """
-  <p class='option'><a name="%(name)s"/><b>%(name)s</b><br/>
+  <div class='option'><a name="%(name)s"></a><b>%(name)s</b><br/>
   %(desc)s<br/>
-  <i>Type: %(type)s<br/>
-  Default: %(default)s</i><br/>
+  <i>Type: %(type)s</i><br/>%(parameters)s
+  <i>Default: %(default)s</i><br/>
   Examples: %(examples_html)s<br/>
-  <br/>
+  <br/></div>
   """ % { 'name': opt_name,
           'type': opt['type'],
+          'parameters': parameters_html,
           'default': opt['default'],
           'desc': opt['description'],
           'examples_html': examples_html}
 
 
 print """
+<a name="point_properties"></a><h3>Point Properties</h3>
+Some callbacks take a point argument. Its properties are:<br/>
+<ul>
+<li>xval/yval: The data coordinates of the point (with dates/times as millis since epoch)</li>
+<li>canvasx/canvasy: The canvas coordinates at which the point is drawn.</li>
+<li>name: The name of the data series to which the point belongs</li>
+</ul>
 </div>
 </body>
 </html>
diff --git a/tests/custom-circles.html b/tests/custom-circles.html
new file mode 100644 (file)
index 0000000..7a30ea2
--- /dev/null
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Custom Circles</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <!--
+    For production (minified) code, use:
+    <script type="text/javascript" src="dygraph-combined.js"></script>
+    -->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+
+  </head>
+  <body>
+    <h2>Custom circles and hover circles</h2>
+    <div id="demodiv"></div>
+
+    <script type="text/javascript">
+      var smile = function(g, series, ctx, cx, cy, color, radius) {
+        mouthlessFace(g, series, ctx, cx, cy, color, radius);
+
+        ctx.fillStyle = "#000000";
+        ctx.beginPath();
+        ctx.arc(cx, cy, radius - 2, .3, Math.PI - .3, false);
+        ctx.stroke();
+      };
+
+      var frown = function(g, series, ctx, cx, cy, color, radius) {
+        mouthlessFace(g, series, ctx, cx, cy, color, radius);
+
+        ctx.fillStyle = "#000000";
+        ctx.beginPath();
+        ctx.arc(cx, cy + radius, radius - 2, Math.PI + .3, -.3, false);
+        ctx.stroke();
+      };
+
+      var mouthlessFace = function(g, series, ctx, cx, cy, color, radius) {
+        ctx.strokeStyle = "#000000";
+        ctx.fillStyle = "#FFFF00";
+        ctx.beginPath();
+        ctx.arc(cx, cy, radius, Math.PI * 2, false);
+        ctx.closePath();
+        ctx.stroke();
+        ctx.fill();
+
+        ctx.fillStyle = "#000000";
+        ctx.beginPath();
+        ctx.arc(cx - (radius / 3) , cy - (radius / 4), 1, Math.PI * 2, false);
+        ctx.closePath();
+        ctx.stroke();
+        ctx.fill();
+
+        ctx.beginPath();
+        ctx.arc(cx + (radius / 3) , cy - (radius / 4), 1, Math.PI * 2, false);
+        ctx.closePath();
+        ctx.stroke();
+        ctx.fill();
+      };
+
+      g = new Dygraph(
+          document.getElementById("demodiv"),
+          function() {
+
+            var r = "xval,default,triangle,square,diamond,pentagon,hexagon,circle,star,plus,ex,custom\n";
+            for (var i=1; i<=20; i++) {
+              r += i;
+              for (var j = 0; j < 11; j++) {
+                r += "," + j + (i / 3);
+              }
+              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
+            }
+          }
+      );
+    </script>
+</body>
+</html>