From 51569480be35fc660d0d2e027f2ae08e0607e3a9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 2 Nov 2015 17:29:38 -0500 Subject: [PATCH 1/8] Handle HiDPI displays in WebAgg/NbAgg backends --- .../backends/backend_webagg_core.py | 19 ++++++++++++++++-- lib/matplotlib/backends/web_backend/mpl.js | 20 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 1750fc53d48a..6dabfc076e65 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -158,6 +158,10 @@ def __init__(self, *args, **kwargs): # to the connected clients. self._current_image_mode = 'full' + # Store the DPI ratio of the browser. This is the scaling that + # occurs automatically for all images on a HiDPI display. + self._dpi_ratio = 1 + def show(self): # show the figure window from matplotlib.pyplot import show @@ -342,7 +346,7 @@ def handle_refresh(self, event): def handle_resize(self, event): x, y = event.get('width', 800), event.get('height', 800) - x, y = int(x), int(y) + x, y = int(x) * 2, int(y) * 2 fig = self.figure # An attempt at approximating the figure size in pixels. fig.set_size_inches(x / fig.dpi, y / fig.dpi) @@ -359,6 +363,17 @@ def handle_send_image_mode(self, event): # The client requests notification of what the current image mode is. self.send_event('image_mode', mode=self._current_image_mode) + def handle_set_dpi_ratio(self, event): + dpi_ratio = event.get('dpi_ratio', 1) + if dpi_ratio != self._dpi_ratio: + # We don't want to scale up the figure dpi more than once. + if not hasattr(self.figure, '_original_dpi'): + self.figure._original_dpi = self.figure.dpi + self.figure.dpi = dpi_ratio * self.figure._original_dpi + self._dpi_ratio = dpi_ratio + self._force_full = True + self.draw_idle() + def send_event(self, event_type, **kwargs): self.manager._send_event(event_type, **kwargs) @@ -444,7 +459,7 @@ def _get_toolbar(self, canvas): return toolbar def resize(self, w, h): - self._send_event('resize', size=(w, h)) + self._send_event('resize', size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio)) def set_window_title(self, title): self._send_event('figure_label', label=title) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index dce7c9309989..c8610ef69043 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -1,6 +1,7 @@ /* Put everything inside the global mpl namespace */ window.mpl = {}; + mpl.get_websocket_type = function() { if (typeof(WebSocket) !== 'undefined') { return WebSocket; @@ -49,7 +50,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { $(parent_element).append(this.root); this._init_header(this); - this._init_canvas(this); + ratio = this._init_canvas(this); this._init_toolbar(this); var fig = this; @@ -59,6 +60,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); fig.send_message("send_image_mode", {}); + fig.send_message("set_dpi_ratio", {'dpi_ratio': ratio}); fig.send_message("refresh", {}); } @@ -184,8 +186,9 @@ mpl.figure.prototype._init_canvas = function() { canvas_div.css('width', width) canvas_div.css('height', height) - canvas.attr('width', width); - canvas.attr('height', height); + canvas.attr('width', width * ratio); + canvas.attr('height', height * ratio); + canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;'); rubberband.attr('width', width); rubberband.attr('height', height); @@ -206,6 +209,17 @@ mpl.figure.prototype._init_canvas = function() { } window.setTimeout(set_focus, 100); + + var backingStore = this.context.backingStorePixelRatio || + this.context.webkitBackingStorePixelRatio || + this.context.mozBackingStorePixelRatio || + this.context.msBackingStorePixelRatio || + this.context.oBackingStorePixelRatio || + this.context.backingStorePixelRatio || 1; + + var ratio = (window.devicePixelRatio || 1) / backingStore; + + return ratio } mpl.figure.prototype._init_toolbar = function() { From 249f19efcefc8d4730e39c8ac3b4a28dabd00fbc Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 2 Nov 2015 17:58:53 -0500 Subject: [PATCH 2/8] Remove hardcoding --- lib/matplotlib/backends/backend_webagg_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 6dabfc076e65..06875660f8d4 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -346,7 +346,7 @@ def handle_refresh(self, event): def handle_resize(self, event): x, y = event.get('width', 800), event.get('height', 800) - x, y = int(x) * 2, int(y) * 2 + x, y = int(x) * self._dpi_ratio, int(y) * self._dpi_ratio fig = self.figure # An attempt at approximating the figure size in pixels. fig.set_size_inches(x / fig.dpi, y / fig.dpi) From 68cdfa9766fa18324c65041e3e72872b5313b3b6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 2 Nov 2015 18:38:46 -0500 Subject: [PATCH 3/8] Fix nbagg --- lib/matplotlib/backends/web_backend/mpl.js | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index c8610ef69043..028b795b72ee 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -50,7 +50,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { $(parent_element).append(this.root); this._init_header(this); - ratio = this._init_canvas(this); + this._init_canvas(this); this._init_toolbar(this); var fig = this; @@ -60,7 +60,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); fig.send_message("send_image_mode", {}); - fig.send_message("set_dpi_ratio", {'dpi_ratio': ratio}); + fig.send_message("set_dpi_ratio", {'dpi_ratio': fig.ratio}); fig.send_message("refresh", {}); } @@ -130,6 +130,15 @@ mpl.figure.prototype._init_canvas = function() { this.canvas = canvas[0]; this.context = canvas[0].getContext("2d"); + var backingStore = this.context.backingStorePixelRatio || + this.context.webkitBackingStorePixelRatio || + this.context.mozBackingStorePixelRatio || + this.context.msBackingStorePixelRatio || + this.context.oBackingStorePixelRatio || + this.context.backingStorePixelRatio || 1; + + this.ratio = (window.devicePixelRatio || 1) / backingStore; + var rubberband = $(''); rubberband.attr('style', "position: absolute; left: 0; top: 0; z-index: 1;") @@ -186,8 +195,8 @@ mpl.figure.prototype._init_canvas = function() { canvas_div.css('width', width) canvas_div.css('height', height) - canvas.attr('width', width * ratio); - canvas.attr('height', height * ratio); + canvas.attr('width', width * this.ratio); + canvas.attr('height', height * this.ratio); canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;'); rubberband.attr('width', width); @@ -209,17 +218,6 @@ mpl.figure.prototype._init_canvas = function() { } window.setTimeout(set_focus, 100); - - var backingStore = this.context.backingStorePixelRatio || - this.context.webkitBackingStorePixelRatio || - this.context.mozBackingStorePixelRatio || - this.context.msBackingStorePixelRatio || - this.context.oBackingStorePixelRatio || - this.context.backingStorePixelRatio || 1; - - var ratio = (window.devicePixelRatio || 1) / backingStore; - - return ratio } mpl.figure.prototype._init_toolbar = function() { From 256e906f8c3bc66d2c04924ecf653ab89d02c0d8 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 2 Nov 2015 19:43:08 -0500 Subject: [PATCH 4/8] Only send ratio if needed --- lib/matplotlib/backends/web_backend/mpl.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index 028b795b72ee..7e1882629235 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -60,7 +60,9 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); fig.send_message("send_image_mode", {}); - fig.send_message("set_dpi_ratio", {'dpi_ratio': fig.ratio}); + if (fig.ratio != 1) { + fig.send_message("set_dpi_ratio", {'dpi_ratio': fig.ratio}); + } fig.send_message("refresh", {}); } From 0c1ea5af2c56fc929ca0224564b3a223d6af5a04 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 2 Nov 2015 21:16:51 -0500 Subject: [PATCH 5/8] PEP8 --- lib/matplotlib/backends/backend_webagg_core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 06875660f8d4..bf0800d9d943 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -459,7 +459,9 @@ def _get_toolbar(self, canvas): return toolbar def resize(self, w, h): - self._send_event('resize', size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio)) + self._send_event( + 'resize', + size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio)) def set_window_title(self, title): self._send_event('figure_label', label=title) From 17c725939154c1dd189d05535acbad059146ae8e Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 5 Nov 2015 20:01:40 -0500 Subject: [PATCH 6/8] Add what's new --- doc/users/whats_new/2015-11-05_hidpi.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/users/whats_new/2015-11-05_hidpi.rst diff --git a/doc/users/whats_new/2015-11-05_hidpi.rst b/doc/users/whats_new/2015-11-05_hidpi.rst new file mode 100644 index 000000000000..6a0ed6ea9fe7 --- /dev/null +++ b/doc/users/whats_new/2015-11-05_hidpi.rst @@ -0,0 +1,5 @@ +Support for HiDPI (Retina) displays in the NbAgg and WebAgg backends +-------------------------------------------------------------------- + +The NbAgg and WebAgg backends will now use the full resolution of your +high-pixel-density display. From c08e2510f21b76a6936e8269b586009ef35a9f45 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 17 Nov 2015 14:23:48 -0500 Subject: [PATCH 7/8] Fix scale of mouse events when in HiDPI mode --- lib/matplotlib/backends/web_backend/mpl.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index 7e1882629235..cecebd8e0201 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -60,8 +60,8 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); fig.send_message("send_image_mode", {}); - if (fig.ratio != 1) { - fig.send_message("set_dpi_ratio", {'dpi_ratio': fig.ratio}); + if (mpl.ratio != 1) { + fig.send_message("set_dpi_ratio", {'dpi_ratio': mpl.ratio}); } fig.send_message("refresh", {}); } @@ -139,7 +139,7 @@ mpl.figure.prototype._init_canvas = function() { this.context.oBackingStorePixelRatio || this.context.backingStorePixelRatio || 1; - this.ratio = (window.devicePixelRatio || 1) / backingStore; + mpl.ratio = (window.devicePixelRatio || 1) / backingStore; var rubberband = $(''); rubberband.attr('style', "position: absolute; left: 0; top: 0; z-index: 1;") @@ -197,8 +197,8 @@ mpl.figure.prototype._init_canvas = function() { canvas_div.css('width', width) canvas_div.css('height', height) - canvas.attr('width', width * this.ratio); - canvas.attr('height', height * this.ratio); + canvas.attr('width', width * mpl.ratio); + canvas.attr('height', height * mpl.ratio); canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;'); rubberband.attr('width', width); @@ -332,10 +332,10 @@ mpl.figure.prototype.handle_resize = function(fig, msg) { } mpl.figure.prototype.handle_rubberband = function(fig, msg) { - var x0 = msg['x0']; - var y0 = fig.canvas.height - msg['y0']; - var x1 = msg['x1']; - var y1 = fig.canvas.height - msg['y1']; + var x0 = msg['x0'] / mpl.ratio; + var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio; + var x1 = msg['x1'] / mpl.ratio; + var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio; x0 = Math.floor(x0) + 0.5; y0 = Math.floor(y0) + 0.5; x1 = Math.floor(x1) + 0.5; @@ -491,8 +491,8 @@ mpl.figure.prototype.mouse_event = function(event, name) { this.canvas_div.focus(); } - var x = canvas_pos.x; - var y = canvas_pos.y; + var x = canvas_pos.x * mpl.ratio; + var y = canvas_pos.y * mpl.ratio; this.send_message(name, {x: x, y: y, button: event.button, step: event.step, From e338687a4685b13d5425ae537f56d8945954de78 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Thu, 10 Dec 2015 09:15:51 +0000 Subject: [PATCH 8/8] Set witdh of non interactive figure in nbagg backend. This ensures that the figure size remains the same following a closure of the figure regardless of display dpi --- lib/matplotlib/backends/web_backend/nbagg_mpl.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/nbagg_mpl.js index 9747995725b8..9471f5340d51 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_mpl.js +++ b/lib/matplotlib/backends/web_backend/nbagg_mpl.js @@ -55,6 +55,7 @@ mpl.mpl_figure_comm = function(comm, msg) { }; mpl.figure.prototype.handle_close = function(fig, msg) { + var width = fig.canvas.width/mpl.ratio fig.root.unbind('remove') // Update the output cell to use the data from the current canvas. @@ -63,7 +64,7 @@ mpl.figure.prototype.handle_close = function(fig, msg) { // Re-enable the keyboard manager in IPython - without this line, in FF, // the notebook keyboard shortcuts fail. IPython.keyboard_manager.enable() - $(fig.parent_element).html(''); + $(fig.parent_element).html(''); fig.close_ws(fig, msg); } @@ -74,8 +75,9 @@ mpl.figure.prototype.close_ws = function(fig, msg){ mpl.figure.prototype.push_to_output = function(remove_interactive) { // Turn the data on the canvas into data in the output cell. + var width = this.canvas.width/mpl.ratio var dataURL = this.canvas.toDataURL(); - this.cell_info[1]['text/html'] = ''; + this.cell_info[1]['text/html'] = ''; } mpl.figure.prototype.updated_canvas_event = function() {