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