Hairlines API & persistence working
authorDan Vanderkam <danvdk@gmail.com>
Thu, 9 May 2013 18:10:37 +0000 (11:10 -0700)
committerDan Vanderkam <danvdk@gmail.com>
Thu, 9 May 2013 18:10:37 +0000 (11:10 -0700)
extras/hairlines.js
tests/hairlines.html

index a91e544..f8a9d42 100644 (file)
@@ -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;
 
 })();
index 514b2b5..d358578 100644 (file)
 
     <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,
 
                 // Set the plug-ins in the options.
                 plugins : [
-                  Dygraph.Plugins.Hairlines
+                  hairlines
                 ]
               }
           );
       };
       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>