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() {