Merge branch 'PR723'
[dygraphs.git] / src / plugins / annotations.js
1 /**
2 * @license
3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://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;