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