Merge pull request #499 from danvk/synchronizer
[dygraphs.git] / tests / hairlines.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
5 <title>Hairlines demo</title>
6 <!--[if IE]>
7 <script type="text/javascript" src="../excanvas.js"></script>
8 <![endif]-->
9 <script type="text/javascript" src="../dygraph-dev.js"></script>
10
11 <!-- Include the Javascript for the plug-in -->
12 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
13 <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js"></script>
14
15 <link rel='stylesheet' href='http://code.jquery.com/ui/1.10.1/themes/base/jquery-ui.css' />
16
17 <script type="text/javascript" src="../extras/hairlines.js"></script>
18 <script type="text/javascript" src="../extras/super-annotations.js"></script>
19
20 <style>
21 #demodiv {
22 position: absolute;
23 left: 10px;
24 right: 200px;
25 height: 400px;
26 display: inline-block;
27 }
28 #status {
29 position: absolute;
30 right: 10px;
31 width: 180px;
32 height: 400px;
33 display: inline-block;
34 }
35 #controls {
36 position: absolute;
37 left: 10px;
38 margin-top: 420px;
39 }
40
41 /* This style & the next show how you can customize the appearance of the
42 hairlines */
43 .hairline-info {
44 border: 1px solid black;
45 border-top-right-radius: 5px;
46 border-bottom-right-radius: 5px;
47
48 display: table; /* shrink to fit */
49 min-width: 100px;
50
51 z-index: 10; /* should appear on top of the chart */
52 padding: 3px;
53 background: white;
54 font-size: 14px;
55 cursor: move;
56 }
57
58 .dygraph-hairline {
59 /* border-right-style: dotted !important; */
60 cursor: move;
61 }
62
63 .dygraph-hairline.selected div {
64 left: 2px !important;
65 width: 2px !important;
66 }
67 .hairline-info.selected {
68 border: 2px solid black;
69 padding: 2px;
70 }
71
72 .annotation-info {
73 background: white;
74 border-width: 1px;
75 border-style: solid;
76 padding: 4px;
77 display: table; /* shrink to fit */
78 box-shadow: 0 0 4px gray;
79 cursor: move;
80
81 min-width: 120px; /* prevents squishing at the right edge of the chart */
82 }
83 .annotation-info.editable {
84 min-width: 180px; /* prevents squishing at the right edge of the chart */
85 }
86
87 .dygraph-annotation-line {
88 box-shadow: 0 0 4px gray;
89 }
90 </style>
91 </head>
92 <body>
93 <h2>Hairlines Demo</h2>
94
95 <p>Click the chart to add a hairline. Drag the hairline to move it.</p>
96 <p>Click a point to add an editable annotation. Drag it to move it up/down.</p>
97
98 <!--
99 The "info box" for each hairline is based on this template.
100 Customize it as you wish. The .hairline-legend element will be populated
101 with data about the current points and the .hairline-kill-button element
102 will remove the hairline when clicked. Everything else will be untouched.
103 -->
104 <div id="hairline-template" class="hairline-info" style="display:none">
105 <button class='hairline-kill-button'>Kill</button>
106 <div class='hairline-legend'></div>
107 </div>
108 <div id="annotation-template" class="annotation-info" style="display:none">
109 <div>{{text}}</div>
110 <div>{{x}}, {{series}}: {{y}}</div>
111 </div>
112 <div id="annotation-editable-template" class="annotation-info" style="display:none">
113 <button class='annotation-kill-button'>Delete</button>
114 <button class='annotation-update'>Change</button>
115 <button class='annotation-cancel'>Cancel</button><br/>
116 <input dg-ann-field='text' type='text' size=30 value='{{text}}' />
117 <div>{{x}}, {{series}}: {{y}}</div>
118 </div>
119 <script type="text/javascript">
120 $(document).on('keyup', '.annotation-info input', function(e) {
121 var $annotationDiv = $(this).parent('.annotation-info');
122 if (e.keyCode == 13 || e.keyCode == 10) { // enter
123 $annotationDiv.find('.annotation-update').click();
124 } else if (e.keyCode == 27) { // escape
125 $annotationDiv.find('.annotation-cancel').click();
126 }
127 })
128 .on('dblclick', '.annotation-info', function(e) {
129 if (e.target.tagName == 'INPUT') return;
130 $(this).find('.annotation-cancel').click();
131 });
132 </script>
133
134 <div id="demodiv"></div>
135 <div id="status"></div>
136
137 <div id="controls">
138 <input type="checkbox" id="update" checked=true><label for="update"> Update</label>
139
140 <button id="add-button">Add a Hairline</button>
141 <button id="remove-button">Remove a Hairline</button>
142 <button id="reset-button">Reset Hairlines</button>
143 <br/>
144 Hairline mode:
145 <input type=radio name="hairline-mode" id="hairline-interpolated" checked=true>
146 <label for="hairline-interpolated"> Interpolated</label>
147 <input type=radio name="hairline-mode" id="hairline-closest">
148 <label for="hairline-closest"> Closest</label>
149
150 <p>Learn more about the <a href="https://docs.google.com/document/d/1OHNE8BNNmMtFlRQ969DACIYIJ9VVJ7w3dSPRJDEeIew/edit">Hairlines/Super-annotations plugins and their APIs</a>.</p>
151
152 </div>
153
154
155 <script type="text/javascript">
156 var last_t = 0;
157 var data = [];
158 var fn = function(t) {
159 return Math.sin(Math.PI/180 * t * 4);
160 };
161 for (; last_t < 200; last_t++) {
162 data.push([last_t, fn(last_t)]);
163 }
164
165 hairlines = new Dygraph.Plugins.Hairlines({
166 divFiller: function(div, data) {
167 // This behavior is identical to what you'd get if you didn't set
168 // this option. It illustrates how to write a 'divFiller'.
169 var html = Dygraph.Plugins.Legend.generateLegendHTML(
170 data.dygraph, data.hairline.xval, data.points, 10);
171 $('.hairline-legend', div).html(html);
172 $(div).data({xval: data.hairline.xval}); // see .hover() below.
173 }
174 });
175 annotations = new Dygraph.Plugins.SuperAnnotations({
176 defaultAnnotationProperties: {
177 'text': 'Annotation Description'
178 }
179 });
180 g = new Dygraph(
181 document.getElementById("demodiv"),
182 data,
183 {
184 labelsDiv: document.getElementById('status'),
185 labelsSeparateLines: true,
186 legend: 'always',
187 labels: [ 'Time', 'Value' ],
188
189 axes: {
190 x: {
191 valueFormatter: function(val) {
192 return val.toFixed(2);
193 }
194 },
195 y: {
196 pixelsPerLabel: 50
197 }
198 },
199
200 // Set the plug-ins in the options.
201 plugins : [
202 annotations,
203 hairlines
204 ]
205 }
206 );
207
208 var shouldUpdate = true;
209 var update = function() {
210 if (!shouldUpdate) return;
211 data.push([last_t, fn(last_t)]);
212 last_t++;
213 data.splice(0, 1);
214 g.updateOptions({file: data});
215 };
216 window.setInterval(update, 1000);
217
218 // Control handlers
219 $('#update').on('change', function() {
220 shouldUpdate = $(this).is(':checked');
221 });
222
223 $('#add-button').on('click', function(e) {
224 var h = hairlines.get();
225 h.push({xval: 137});
226 hairlines.set(h);
227 });
228 $('#remove-button').on('click', function(e) {
229 var h = hairlines.get();
230 if (h.length > 0) {
231 var idx = Math.floor(h.length / 2);
232 h.splice(idx, 1);
233 }
234 hairlines.set(h);
235 });
236 $('#reset-button').on('click', function(e) {
237 setDefaultState();
238 });
239 function setHairlineModeRadio() {
240 var hs = hairlines.get();
241 if (hs.length) {
242 var interpolated = hs[0].interpolated;
243 $('#hairline-interpolated').prop('checked', interpolated);
244 $('#hairline-closest').prop('checked', !interpolated);
245 }
246 }
247 $('[name=hairline-mode]').change(function() {
248 var interpolated = $('#hairline-interpolated').is(':checked');
249 var hs = hairlines.get();
250 for (var i = 0; i < hs.length; i++) {
251 hs[i].interpolated = interpolated;
252 }
253 hairlines.set(hs);
254 });
255
256 // Persistence
257 function loadFromStorage() {
258 hairlines.set(JSON.parse(localStorage.getItem('hairlines')));
259 annotations.set(JSON.parse(localStorage.getItem('annotations')));
260 setHairlineModeRadio();
261 }
262 $(hairlines).on('hairlinesChanged', function(e) {
263 localStorage.setItem('hairlines', JSON.stringify(hairlines.get()));
264 setHairlineModeRadio();
265 });
266 $(annotations).on('annotationsChanged', function(e) {
267 localStorage.setItem('annotations', JSON.stringify(annotations.get()));
268 });
269 function setDefaultState() {
270 // triggers 'hairlinesChanged' and 'annotationsChanged' events, above.
271 hairlines.set([{xval: 55}]);
272 annotations.set([{
273 xval: 67,
274 series: 'Value',
275 text: 'Bottom'
276 },
277 {
278 xval: 137,
279 series: 'Value',
280 text: 'Fast Change'
281 }]);
282 }
283
284 if (!localStorage.getItem('hairlines') ||
285 !localStorage.getItem('annotations')) {
286 setDefaultState();
287 } else {
288 loadFromStorage();
289 }
290
291 // Set focus on text box when you edit an annotation.
292 $(annotations).on('beganEditAnnotation', function(e, a) {
293 $('input[type=text]', a.infoDiv).focus();
294 });
295
296 // Select/Deselect hairlines on click.
297 $(document).on('click', '.hairline-info', function() {
298 console.log('click');
299 var xval = $(this).data('xval');
300 var hs = hairlines.get();
301 for (var i = 0; i < hs.length; i++) {
302 if (hs[i].xval == xval) {
303 hs[i].selected = !hs[i].selected;
304 }
305 }
306 hairlines.set(hs);
307 });
308
309 // Demonstration of how to use various other event listeners
310 $(hairlines).on({
311 'hairlineMoved': function(e, data) {
312 // console.log('hairline moved from', data.oldXVal, ' to ', data.newXVal);
313 },
314 'hairlineCreated': function(e, data) {
315 console.log('hairline created at ', data.xval);
316 },
317 'hairlineDeleted': function(e, data) {
318 console.log('hairline deleted at ', data.xval);
319 }
320 });
321 $(annotations).on({
322 'annotationCreated': function(e, data) {
323 console.log('annotation created at ', data.series, data.xval);
324 },
325 'annotationMoved': function(e, data) {
326 console.log('annotation moved from ', data.oldYFrac, ' to ', data.newYFrac);
327 },
328 'annotationDeleted': function(e, data) {
329 console.log('annotation deleted at ', data.series, data.xval);
330 },
331 'annotationEdited': function(e, data) {
332 console.log('edited annotation at ', data.series, data.xval);
333 },
334 'cancelEditAnnotation': function(e, data) {
335 console.log('edit canceled on annotation at ', data.series, data.xval);
336 }
337 });
338
339 // TODO(danvk): demonstrate other annotations API methods.
340 </script>
341 </body>
342 </html>