From bafc26ccbba91b1f282aee4fec4e4a909f5ca7a1 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam <danvdk@gmail.com> Date: Thu, 9 May 2013 11:10:37 -0700 Subject: [PATCH] Hairlines API & persistence working --- extras/hairlines.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- tests/hairlines.html | 58 ++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/extras/hairlines.js b/extras/hairlines.js index a91e544..f8a9d42 100644 --- a/extras/hairlines.js +++ b/extras/hairlines.js @@ -49,7 +49,7 @@ hairlines.prototype.toString = function() { hairlines.prototype.activate = function(g) { this.dygraph_ = g; - this.hairlines_ = [this.createHairline(0.55)]; + this.hairlines_ = []; return { didDrawChart: this.didDrawChart, @@ -70,10 +70,16 @@ hairlines.prototype.detachLabels = function() { hairlines.prototype.hairlineWasDragged = function(h, event, ui) { var area = this.dygraph_.getArea(); + var oldFrac = h.xFraction; h.xFraction = (ui.position.left - area.x) / area.w; this.moveHairlineToTop(h); this.updateHairlineDivPositions(); this.updateHairlineInfo(); + $(this).triggerHandler('hairlineMoved', { + oldXFraction: oldFrac, + newXFraction: h.xFraction + }); + $(this).triggerHandler('hairlinesChanged', {}); }; // This creates the hairline object and returns it. @@ -124,6 +130,10 @@ hairlines.prototype.createHairline = function(xFraction) { var that = this; $infoDiv.on('click', '.hairline-kill-button', function() { that.removeHairline(h); + $(that).triggerHandler('hairlineDeleted', { + xFraction: h.xFraction + }); + $(that).triggerHandler('hairlinesChanged', {}); }); return h; @@ -258,6 +268,11 @@ hairlines.prototype.click = function(e) { that.updateHairlineDivPositions(); that.updateHairlineInfo(); that.attachHairlinesToChart_(); + + $(that).triggerHandler('hairlineCreated', { + xFraction: xFraction + }); + $(that).triggerHandler('hairlinesChanged', {}); }, CLICK_DELAY_MS); }; @@ -272,6 +287,74 @@ hairlines.prototype.destroy = function() { this.detachLabels(); }; + +// Public API + +/** + * This is a restricted view of this.hairlines_ which doesn't expose + * implementation details like the handle divs. + * + * @typedef { + * xFraction: number, // invariant across resize + * interpolated: bool // alternative is to snap to closest + * } PublicHairline + */ + +/** + * @return {!Array.<!PublicHairline>} The current set of hairlines, ordered + * from back to front. + */ +hairlines.prototype.get = function() { + var result = []; + for (var i = 0; i < this.hairlines_.length; i++) { + var h = this.hairlines_[i]; + result.push({ + xFraction: h.xFraction, + interpolated: h.interpolated + }); + } + return result; +}; + +/** + * Calling this will result in a hairlinesChanged event being triggered, no + * matter whether it consists of additions, deletions, moves or no changes at + * all. + * + * @param {!Array.<!PublicHairline>} hairlines The new set of hairlines, + * ordered from back to front. + */ +hairlines.prototype.set = function(hairlines) { + // Re-use divs from the old hairlines array so far as we can. + // They're already correctly z-ordered. + var anyCreated = false; + for (var i = 0; i < hairlines.length; i++) { + var h = hairlines[i]; + + if (this.hairlines_.length > i) { + this.hairlines_[i].xFraction = h.xFraction; + this.hairlines_[i].interpolated = h.interpolated; + } else { + // TODO(danvk): pass in |interpolated| value. + this.hairlines_.push(this.createHairline(h.xFraction)); + anyCreated = true; + } + } + + // If there are any remaining hairlines, destroy them. + while (hairlines.length < this.hairlines_.length) { + this.removeHairline(this.hairlines_[hairlines.length]); + } + + this.updateHairlineDivPositions(); + this.updateHairlineInfo(); + if (anyCreated) { + this.attachHairlinesToChart_(); + } + + $(this).triggerHandler('hairlinesChanged', {}); +}; + return hairlines; })(); diff --git a/tests/hairlines.html b/tests/hairlines.html index 514b2b5..d358578 100644 --- a/tests/hairlines.html +++ b/tests/hairlines.html @@ -76,6 +76,10 @@ <div id="controls"> <input type="checkbox" id="update" checked=true><label for="update"> Update</label> + + <button id="add-button">Add a Hairline</button> + <button id="remove-button">Remove a Hairline</button> + <button id="reset-button">Reset Hairlines</button> </div> <script type="text/javascript"> @@ -88,6 +92,7 @@ data.push([last_t, fn(last_t)]); } + hairlines = new Dygraph.Plugins.Hairlines(); g = new Dygraph( document.getElementById("demodiv"), data, @@ -111,7 +116,7 @@ // Set the plug-ins in the options. plugins : [ - Dygraph.Plugins.Hairlines + hairlines ] } ); @@ -126,9 +131,60 @@ }; window.setInterval(update, 1000); + // Control handlers $('#update').on('change', function() { shouldUpdate = $(this).is(':checked'); }); + + $('#add-button').on('click', function(e) { + var h = hairlines.get(); + h.push({xFraction: 0.75}); + hairlines.set(h); + }); + $('#remove-button').on('click', function(e) { + var h = hairlines.get(); + if (h.length > 0) { + var idx = Math.floor(h.length / 2); + h.splice(idx, 1); + } + hairlines.set(h); + }); + $('#reset-button').on('click', function(e) { + setDefaultHairlines(); + }); + + // Hairline persistence + function loadFromStorage() { + hairlines.set(JSON.parse(localStorage.getItem('hairlines'))); + } + function storeHairlines() { + localStorage.setItem('hairlines', JSON.stringify(hairlines.get())); + } + $(hairlines).on('hairlinesChanged', function(e) { + storeHairlines(); + }); + function setDefaultHairlines() { + // triggers 'hairlinesChanged' event, above. + hairlines.set([{xFraction: 0.55}]); + } + + if (!localStorage.getItem('hairlines')) { + setDefaultHairlines(); + } else { + loadFromStorage(); + } + + + // Demonstration of how to use various other event listeners + $(hairlines).on('hairlineMoved', function(e, data) { + // console.log('hairline moved from', data.oldXFraction, ' to ', data.newXFraction); + }); + $(hairlines).on('hairlineCreated', function(e, data) { + console.log('hairline created at ', data.xFraction); + }); + $(hairlines).on('hairlineDeleted', function(e, data) { + console.log('hairline deleted at ', data.xFraction); + }); </script> </body> </html> -- 2.7.4