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