From 378194819965ebefef3b6c93e180cf343dcff997 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Mon, 26 Aug 2013 14:52:02 +0100 Subject: [PATCH] Add support for HiDPI displays --- auto_tests/tests/PixelSampler.js | 3 ++- auto_tests/tests/Util.js | 4 +++- auto_tests/tests/grid_per_axis.js | 2 ++ auto_tests/tests/stacked.js | 19 ++----------------- dygraph-utils.js | 24 ++++++++++++++++++++++++ dygraph.js | 22 ++++++++++++++++------ 6 files changed, 49 insertions(+), 25 deletions(-) diff --git a/auto_tests/tests/PixelSampler.js b/auto_tests/tests/PixelSampler.js index 89c520f..e1b3be3 100644 --- a/auto_tests/tests/PixelSampler.js +++ b/auto_tests/tests/PixelSampler.js @@ -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]]; }; diff --git a/auto_tests/tests/Util.js b/auto_tests/tests/Util.js index 8a14e06..53a83f8 100644 --- a/auto_tests/tests/Util.js +++ b/auto_tests/tests/Util.js @@ -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]]; }; diff --git a/auto_tests/tests/grid_per_axis.js b/auto_tests/tests/grid_per_axis.js index 8e2c41a..cac2534 100644 --- a/auto_tests/tests/grid_per_axis.js +++ b/auto_tests/tests/grid_per_axis.js @@ -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); diff --git a/auto_tests/tests/stacked.js b/auto_tests/tests/stacked.js index b8c095e..1185f19 100644 --- a/auto_tests/tests/stacked.js +++ b/auto_tests/tests/stacked.js @@ -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 diff --git a/dygraph-utils.js b/dygraph-utils.js index 1ec1795..d90b3b7 100644 --- a/dygraph-utils.js +++ b/dygraph-utils.js @@ -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 tag, e.g. w/r/t/ clipping. * @return {boolean} diff --git a/dygraph.js b/dygraph.js index 6e52555..81e3c09 100644 --- a/dygraph.js +++ b/dygraph.js @@ -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); + } }; /** -- 2.7.4