| 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> |