Merge pull request #565 from danvk/gulp
[dygraphs.git] / src / plugins / annotations.js
diff --git a/src/plugins/annotations.js b/src/plugins/annotations.js
new file mode 100644 (file)
index 0000000..8576104
--- /dev/null
@@ -0,0 +1,182 @@
+/**
+ * @license
+ * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/*global Dygraph:false */
+
+Dygraph.Plugins.Annotations = (function() {
+
+"use strict";
+
+/**
+Current bits of jankiness:
+- Uses dygraph.layout_ to get the parsed annotations.
+- Uses dygraph.plotter_.area
+
+It would be nice if the plugin didn't require so much special support inside
+the core dygraphs classes, but annotations involve quite a bit of parsing and
+layout.
+
+TODO(danvk): cache DOM elements.
+
+*/
+
+var annotations = function() {
+  this.annotations_ = [];
+};
+
+annotations.prototype.toString = function() {
+  return "Annotations Plugin";
+};
+
+annotations.prototype.activate = function(g) {
+  return {
+    clearChart: this.clearChart,
+    didDrawChart: this.didDrawChart
+  };
+};
+
+annotations.prototype.detachLabels = function() {
+  for (var i = 0; i < this.annotations_.length; i++) {
+    var a = this.annotations_[i];
+    if (a.parentNode) a.parentNode.removeChild(a);
+    this.annotations_[i] = null;
+  }
+  this.annotations_ = [];
+};
+
+annotations.prototype.clearChart = function(e) {
+  this.detachLabels();
+};
+
+annotations.prototype.didDrawChart = function(e) {
+  var g = e.dygraph;
+
+  // Early out in the (common) case of zero annotations.
+  var points = g.layout_.annotated_points;
+  if (!points || points.length === 0) return;
+
+  var containerDiv = e.canvas.parentNode;
+  var annotationStyle = {
+    "position": "absolute",
+    "fontSize": g.getOption('axisLabelFontSize') + "px",
+    "zIndex": 10,
+    "overflow": "hidden"
+  };
+
+  var bindEvt = function(eventName, classEventName, pt) {
+    return function(annotation_event) {
+      var a = pt.annotation;
+      if (a.hasOwnProperty(eventName)) {
+        a[eventName](a, pt, g, annotation_event);
+      } else if (g.getOption(classEventName)) {
+        g.getOption(classEventName)(a, pt, g, annotation_event );
+      }
+    };
+  };
+
+  // Add the annotations one-by-one.
+  var area = e.dygraph.plotter_.area;
+
+  // x-coord to sum of previous annotation's heights (used for stacking).
+  var xToUsedHeight = {};
+
+  for (var i = 0; i < points.length; i++) {
+    var p = points[i];
+    if (p.canvasx < area.x || p.canvasx > area.x + area.w ||
+        p.canvasy < area.y || p.canvasy > area.y + area.h) {
+      continue;
+    }
+
+    var a = p.annotation;
+    var tick_height = 6;
+    if (a.hasOwnProperty("tickHeight")) {
+      tick_height = a.tickHeight;
+    }
+
+    var div = document.createElement("div");
+    for (var name in annotationStyle) {
+      if (annotationStyle.hasOwnProperty(name)) {
+        div.style[name] = annotationStyle[name];
+      }
+    }
+    if (!a.hasOwnProperty('icon')) {
+      div.className = "dygraphDefaultAnnotation";
+    }
+    if (a.hasOwnProperty('cssClass')) {
+      div.className += " " + a.cssClass;
+    }
+
+    var width = a.hasOwnProperty('width') ? a.width : 16;
+    var height = a.hasOwnProperty('height') ? a.height : 16;
+    if (a.hasOwnProperty('icon')) {
+      var img = document.createElement("img");
+      img.src = a.icon;
+      img.width = width;
+      img.height = height;
+      div.appendChild(img);
+    } else if (p.annotation.hasOwnProperty('shortText')) {
+      div.appendChild(document.createTextNode(p.annotation.shortText));
+    }
+    var left = p.canvasx - width / 2;
+    div.style.left = left + "px";
+    var divTop = 0;
+    if (a.attachAtBottom) {
+      var y = (area.y + area.h - height - tick_height);
+      if (xToUsedHeight[left]) {
+        y -= xToUsedHeight[left];
+      } else {
+        xToUsedHeight[left] = 0;
+      }
+      xToUsedHeight[left] += (tick_height + height);
+      divTop = y;
+    } else {
+      divTop = p.canvasy - height - tick_height;
+    }
+    div.style.top = divTop + "px";
+    div.style.width = width + "px";
+    div.style.height = height + "px";
+    div.title = p.annotation.text;
+    div.style.color = g.colorsMap_[p.name];
+    div.style.borderColor = g.colorsMap_[p.name];
+    a.div = div;
+
+    g.addAndTrackEvent(div, 'click',
+        bindEvt('clickHandler', 'annotationClickHandler', p, this));
+    g.addAndTrackEvent(div, 'mouseover',
+        bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this));
+    g.addAndTrackEvent(div, 'mouseout',
+        bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this));
+    g.addAndTrackEvent(div, 'dblclick',
+        bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this));
+
+    containerDiv.appendChild(div);
+    this.annotations_.push(div);
+
+    var ctx = e.drawingContext;
+    ctx.save();
+    ctx.strokeStyle = g.colorsMap_[p.name];
+    ctx.beginPath();
+    if (!a.attachAtBottom) {
+      ctx.moveTo(p.canvasx, p.canvasy);
+      ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height);
+    } else {
+      var y = divTop + height;
+      ctx.moveTo(p.canvasx, y);
+      ctx.lineTo(p.canvasx, y + tick_height);
+    }
+    ctx.closePath();
+    ctx.stroke();
+    ctx.restore();
+  }
+};
+
+annotations.prototype.destroy = function() {
+  this.detachLabels();
+};
+
+return annotations;
+
+})();