| 1 | <!DOCTYPE html> |
| 2 | <html> |
| 3 | <head> |
| 4 | <title>Hairlines demo</title> |
| 5 | <script type="text/javascript" src="../dygraph-dev.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="../extras/hairlines.js"></script> |
| 14 | <script type="text/javascript" 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> |