1 /**
  2  * @license
  3  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
  4  * MIT-licenced: https://opensource.org/licenses/MIT
  5  */
  6 
  7 /*global Dygraph:false */
  8 
  9 "use strict";
 10 
 11 /**
 12 Current bits of jankiness:
 13 - Uses dygraph.layout_ to get the parsed annotations.
 14 - Uses dygraph.plotter_.area
 15 
 16 It would be nice if the plugin didn't require so much special support inside
 17 the core dygraphs classes, but annotations involve quite a bit of parsing and
 18 layout.
 19 
 20 TODO(danvk): cache DOM elements.
 21 */
 22 
 23 var annotations = function() {
 24   this.annotations_ = [];
 25 };
 26 
 27 annotations.prototype.toString = function() {
 28   return "Annotations Plugin";
 29 };
 30 
 31 annotations.prototype.activate = function(g) {
 32   return {
 33     clearChart: this.clearChart,
 34     didDrawChart: this.didDrawChart
 35   };
 36 };
 37 
 38 annotations.prototype.detachLabels = function() {
 39   for (var i = 0; i < this.annotations_.length; i++) {
 40     var a = this.annotations_[i];
 41     if (a.parentNode) a.parentNode.removeChild(a);
 42     this.annotations_[i] = null;
 43   }
 44   this.annotations_ = [];
 45 };
 46 
 47 annotations.prototype.clearChart = function(e) {
 48   this.detachLabels();
 49 };
 50 
 51 annotations.prototype.didDrawChart = function(e) {
 52   var g = e.dygraph;
 53 
 54   // Early out in the (common) case of zero annotations.
 55   var points = g.layout_.annotated_points;
 56   if (!points || points.length === 0) return;
 57 
 58   var containerDiv = e.canvas.parentNode;
 59 
 60   var bindEvt = function(eventName, classEventName, pt) {
 61     return function(annotation_event) {
 62       var a = pt.annotation;
 63       if (a.hasOwnProperty(eventName)) {
 64         a[eventName](a, pt, g, annotation_event);
 65       } else if (g.getOption(classEventName)) {
 66         g.getOption(classEventName)(a, pt, g, annotation_event );
 67       }
 68     };
 69   };
 70 
 71   // Add the annotations one-by-one.
 72   var area = e.dygraph.getArea();
 73 
 74   // x-coord to sum of previous annotation's heights (used for stacking).
 75   var xToUsedHeight = {};
 76 
 77   for (var i = 0; i < points.length; i++) {
 78     var p = points[i];
 79     if (p.canvasx < area.x || p.canvasx > area.x + area.w ||
 80         p.canvasy < area.y || p.canvasy > area.y + area.h) {
 81       continue;
 82     }
 83 
 84     var a = p.annotation;
 85     var tick_height = 6;
 86     if (a.hasOwnProperty("tickHeight")) {
 87       tick_height = a.tickHeight;
 88     }
 89 
 90     // TODO: deprecate axisLabelFontSize in favor of CSS
 91     var div = document.createElement("div");
 92     div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px";
 93     var className = 'dygraph-annotation';
 94     if (!a.hasOwnProperty('icon')) {
 95       // camelCase class names are deprecated.
 96       className += ' dygraphDefaultAnnotation dygraph-default-annotation';
 97     }
 98     if (a.hasOwnProperty('cssClass')) {
 99       className += " " + a.cssClass;
100     }
101     div.className = className;
102 
103     var width = a.hasOwnProperty('width') ? a.width : 16;
104     var height = a.hasOwnProperty('height') ? a.height : 16;
105     if (a.hasOwnProperty('icon')) {
106       var img = document.createElement("img");
107       img.src = a.icon;
108       img.width = width;
109       img.height = height;
110       div.appendChild(img);
111     } else if (p.annotation.hasOwnProperty('shortText')) {
112       div.appendChild(document.createTextNode(p.annotation.shortText));
113     }
114     var left = p.canvasx - width / 2;
115     div.style.left = left + "px";
116     var divTop = 0;
117     if (a.attachAtBottom) {
118       var y = (area.y + area.h - height - tick_height);
119       if (xToUsedHeight[left]) {
120         y -= xToUsedHeight[left];
121       } else {
122         xToUsedHeight[left] = 0;
123       }
124       xToUsedHeight[left] += (tick_height + height);
125       divTop = y;
126     } else {
127       divTop = p.canvasy - height - tick_height;
128     }
129     div.style.top = divTop + "px";
130     div.style.width = width + "px";
131     div.style.height = height + "px";
132     div.title = p.annotation.text;
133     div.style.color = g.colorsMap_[p.name];
134     div.style.borderColor = g.colorsMap_[p.name];
135     a.div = div;
136 
137     g.addAndTrackEvent(div, 'click',
138         bindEvt('clickHandler', 'annotationClickHandler', p, this));
139     g.addAndTrackEvent(div, 'mouseover',
140         bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this));
141     g.addAndTrackEvent(div, 'mouseout',
142         bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this));
143     g.addAndTrackEvent(div, 'dblclick',
144         bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this));
145 
146     containerDiv.appendChild(div);
147     this.annotations_.push(div);
148 
149     var ctx = e.drawingContext;
150     ctx.save();
151     ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name];
152     ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth');
153     ctx.beginPath();
154     if (!a.attachAtBottom) {
155       ctx.moveTo(p.canvasx, p.canvasy);
156       ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height);
157     } else {
158       var y = divTop + height;
159       ctx.moveTo(p.canvasx, y);
160       ctx.lineTo(p.canvasx, y + tick_height);
161     }
162     ctx.closePath();
163     ctx.stroke();
164     ctx.restore();
165   }
166 };
167 
168 annotations.prototype.destroy = function() {
169   this.detachLabels();
170 };
171 
172 export default annotations;
173