Add support for HiDPI displays
authorPaul Holden <paul.holden@gmail.com>
Mon, 26 Aug 2013 13:52:02 +0000 (14:52 +0100)
committerPaul Holden <paul.holden@gmail.com>
Sun, 15 Sep 2013 22:36:56 +0000 (23:36 +0100)
auto_tests/tests/PixelSampler.js
auto_tests/tests/Util.js
auto_tests/tests/grid_per_axis.js
auto_tests/tests/stacked.js
dygraph-utils.js
dygraph.js

index 89c520f..e1b3be3 100644 (file)
@@ -17,6 +17,7 @@ var PixelSampler = function(dygraph) {
   var canvas = dygraph.hidden_;
   var ctx = canvas.getContext("2d");
   this.imageData_ = ctx.getImageData(0, 0, canvas.width, canvas.height);
+  this.scale = canvas.width / dygraph.width_;
 };
 
 /**
@@ -26,7 +27,7 @@ var PixelSampler = function(dygraph) {
  * are in [0, 255]. A pixel which has never been touched will be [0,0,0,0].
  */
 PixelSampler.prototype.colorAtPixel = function(x, y) {
-  var i = 4 * (x + this.imageData_.width * y);
+  var i = 4 * (x * this.scale + this.imageData_.width * y * this.scale);
   var d = this.imageData_.data;
   return [d[i], d[i+1], d[i+2], d[i+3]];
 };
index 8a14e06..53a83f8 100644 (file)
@@ -91,7 +91,9 @@ Util.samplePixel = function(canvas, x, y) {
   // TODO(danvk): Any performance issues with this?
   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
 
-  var i = 4 * (x + imageData.width * y);
+  var scale = Dygraph.getContextPixelRatio(ctx);
+
+  var i = 4 * (x * scale + imageData.width * y * scale);
   var d = imageData.data;
   return [d[i], d[i+1], d[i+2], d[i+3]];
 };
index 8e2c41a..cac2534 100644 (file)
@@ -198,6 +198,8 @@ GridPerAxisTestCase.prototype.testPerAxisGridWidth = function() {
     for ( var i = 0; i < gridlines[axis].length; i++) {
       y = halfDown(g.toDomYCoord(gridlines[axis][i], axis));
       // Ignore the alpha value
+
+      // FIXME(pholden): this test fails with a context pixel ratio of 2.
       var drawnPixeldown2 = Util.samplePixel(g.hidden_, x, y - 2).slice(0, 3);
       var drawnPixeldown1 = Util.samplePixel(g.hidden_, x, y - 1).slice(0, 3);
       var drawnPixel = Util.samplePixel(g.hidden_, x, y).slice(0, 3);
index b8c095e..1185f19 100644 (file)
@@ -47,24 +47,9 @@ stackedTestCase.prototype.testCorrectColors = function() {
   // y pixel 100 = y1 line (green)
   // y pixels 0-99 = nothing (white)
 
-  // TODO(danvk): factor this and getPixel() into a utility usable by all tests.
-  var ctx = g.hidden_ctx_;
-  var imageData = ctx.getImageData(0, 0, 400, 300);
-
-  assertEquals(400, imageData.width);
-  assertEquals(300, imageData.height);
-
-  // returns an (r, g, b, alpha) tuple for the pixel.
-  // values are in [0, 255].
-  var getPixel = function(imageData, x, y) {
-    var i = 4 * (x + imageData.width * y);
-    var d = imageData.data;
-    return [d[i], d[i+1], d[i+2], d[i+3]];
-  };
-
   // 38 = round(0.15 * 255)
-  assertEquals([0, 0, 255, 38], getPixel(imageData, 200, 250));
-  assertEquals([0, 255, 0, 38], getPixel(imageData, 200, 150));
+  assertEquals([0, 0, 255, 38], Util.samplePixel(g.hidden_, 200, 250));
+  assertEquals([0, 255, 0, 38], Util.samplePixel(g.hidden_, 200, 150));
 };
 
 // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=358
index 1ec1795..d90b3b7 100644 (file)
@@ -768,6 +768,30 @@ Dygraph.createCanvas = function() {
 };
 
 /**
+ * Returns the context's pixel ratio, which is the ratio between the device
+ * pixel ratio and the backing store ratio. Typically this is 1 for conventional
+ * displays, and > 1 for HiDPI displays (such as the Retina MBP).
+ * See http://www.html5rocks.com/en/tutorials/canvas/hidpi/ for more details.
+ *
+ * @param {!CanvasRenderingContext2D} context The canvas's 2d context.
+ * @return {number} The ratio of the device pixel ratio and the backing store
+ * ratio for the specified context.
+ */
+Dygraph.getContextPixelRatio = function(context) {
+  try {
+    var devicePixelRatio = window.devicePixelRatio || 1,
+        backingStoreRatio = context.webkitBackingStorePixelRatio ||
+                            context.mozBackingStorePixelRatio ||
+                            context.msBackingStorePixelRatio ||
+                            context.oBackingStorePixelRatio ||
+                            context.backingStorePixelRatio || 1;
+    return devicePixelRatio / backingStoreRatio;
+  } catch (e) {
+    return 1;
+  }
+};
+
+/**
  * Checks whether the user is on an Android browser.
  * Android does not fully support the <canvas> tag, e.g. w/r/t/ clipping.
  * @return {boolean}
index 6e52555..81e3c09 100644 (file)
@@ -1008,11 +1008,11 @@ Dygraph.prototype.createInterface_ = function() {
   // ... and for static parts of the chart.
   this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
 
-  this.resizeElements_();
-
   this.canvas_ctx_ = Dygraph.getContext(this.canvas_);
   this.hidden_ctx_ = Dygraph.getContext(this.hidden_);
 
+  this.resizeElements_();
+
   // The interactive parts of the graph are drawn on top of the chart.
   this.graphDiv.appendChild(this.hidden_);
   this.graphDiv.appendChild(this.canvas_);
@@ -1058,14 +1058,24 @@ Dygraph.prototype.createInterface_ = function() {
 Dygraph.prototype.resizeElements_ = function() {
   this.graphDiv.style.width = this.width_ + "px";
   this.graphDiv.style.height = this.height_ + "px";
-  this.canvas_.width = this.width_;
-  this.canvas_.height = this.height_;
+
+  var canvasScale = Dygraph.getContextPixelRatio(this.canvas_ctx_);
+  this.canvas_.width = this.width_ * canvasScale;
+  this.canvas_.height = this.height_ * canvasScale;
   this.canvas_.style.width = this.width_ + "px";    // for IE
   this.canvas_.style.height = this.height_ + "px";  // for IE
-  this.hidden_.width = this.width_;
-  this.hidden_.height = this.height_;
+  if (canvasScale !== 1) {
+    this.canvas_ctx_.scale(canvasScale, canvasScale);
+  }
+
+  var hiddenScale = Dygraph.getContextPixelRatio(this.hidden_ctx_);
+  this.hidden_.width = this.width_ * hiddenScale;
+  this.hidden_.height = this.height_ * hiddenScale;
   this.hidden_.style.width = this.width_ + "px";    // for IE
   this.hidden_.style.height = this.height_ + "px";  // for IE
+  if (hiddenScale !== 1) {
+    this.hidden_ctx_.scale(hiddenScale, hiddenScale);
+  }
 };
 
 /**