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. diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 1750fc53d48a..bf0800d9d943 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) * 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) @@ -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,9 @@ 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..cecebd8e0201 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; @@ -59,6 +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", {}); + if (mpl.ratio != 1) { + fig.send_message("set_dpi_ratio", {'dpi_ratio': mpl.ratio}); + } fig.send_message("refresh", {}); } @@ -128,6 +132,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; + + mpl.ratio = (window.devicePixelRatio || 1) / backingStore; + var rubberband = $(''); rubberband.attr('style', "position: absolute; left: 0; top: 0; z-index: 1;") @@ -184,8 +197,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 * mpl.ratio); + canvas.attr('height', height * mpl.ratio); + canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;'); rubberband.attr('width', width); rubberband.attr('height', height); @@ -318,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; @@ -477,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, 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() {