From 78c182d450085b1b9632c184e4905f6cf4f2c9af Mon Sep 17 00:00:00 2001 From: Ian Hunt-Isaak Date: Wed, 2 Dec 2020 22:35:41 -0500 Subject: [PATCH 1/2] support blitting in webagg backend Replaced the second Agg renderer by storing the previous buffer in a private attribute. Removing the second renderer eliminates the the flickering issues noted in: https://github.com/matplotlib/matplotlib/pull/9240#issuecomment-528170704 Also, did not need to implement copy_from_bbox as that is inherited from backend_agg.FigureCanvasAgg --- .../backends/backend_webagg_core.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index d8016e537563..9d498c830b51 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -118,7 +118,7 @@ def _handle_key(key): class FigureCanvasWebAggCore(backend_agg.FigureCanvasAgg): - supports_blit = False + supports_blit = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -153,6 +153,10 @@ def draw(self): finally: self.manager.refresh_all() # Swap the frames. + def blit(self, bbox=None): + self._png_is_old = True + self.manager.refresh_all() + def draw_idle(self): self.send_event("draw") @@ -189,18 +193,14 @@ def get_diff_image(self): output = buff else: self.set_image_mode('diff') - last_buffer = (np.frombuffer(self._last_renderer.buffer_rgba(), - dtype=np.uint32) - .reshape((renderer.height, renderer.width))) - diff = buff != last_buffer + diff = buff != self._last_buff output = np.where(diff, buff, 0) buf = BytesIO() data = output.view(dtype=np.uint8).reshape((*output.shape, 4)) Image.fromarray(data).save(buf, format="png") - # Swap the renderer frames - self._renderer, self._last_renderer = ( - self._last_renderer, renderer) + # store the current buffer so we can compute the next diff + np.copyto(self._last_buff, buff) self._force_full = False self._png_is_old = False return buf.getvalue() @@ -220,9 +220,10 @@ def get_renderer(self, cleared=None): if need_new_renderer: self._renderer = backend_agg.RendererAgg( w, h, self.figure.dpi) - self._last_renderer = backend_agg.RendererAgg( - w, h, self.figure.dpi) self._lastKey = key + self._last_buff = np.copy(np.frombuffer( + self._renderer.buffer_rgba(), dtype=np.uint32 + ).reshape((self._renderer.height, self._renderer.width))) elif cleared: self._renderer.clear() From 2fbbec983792118e5b6fc389241f651c4784ef3b Mon Sep 17 00:00:00 2001 From: Ian Hunt-Isaak Date: Wed, 2 Dec 2020 23:08:05 -0500 Subject: [PATCH 2/2] add blitting to nbagg UAT notebook + correct the numbering of stopping figure taking the example from https://github.com/matplotlib/matplotlib/pull/9240 Co-Authored-By: Thomas A Caswell --- .../backends/web_backend/nbagg_uat.ipynb | 156 +++++++++--------- 1 file changed, 74 insertions(+), 82 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb index 8ac7434e53e1..a914e7190664 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -3,9 +3,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from imp import reload" @@ -23,9 +21,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib\n", @@ -49,9 +45,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.backends.backend_webagg_core\n", @@ -78,9 +72,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.plot([3, 2, 1])\n", @@ -99,9 +91,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(matplotlib.backends.backend_nbagg.connection_info())" @@ -119,12 +109,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plt.close(fig1)" + "plt.close(fig1)\n", + "plt.close('all')" ] }, { @@ -140,9 +129,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.plot(range(10))" @@ -160,9 +147,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(matplotlib.backends.backend_nbagg.connection_info())" @@ -180,9 +165,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.show()\n", @@ -203,9 +186,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.interactive(True)\n", @@ -223,9 +204,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.plot(range(3))" @@ -241,9 +220,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(matplotlib.backends.backend_nbagg.connection_info())" @@ -259,9 +236,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.interactive(False)" @@ -279,9 +254,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.gcf().canvas.manager.reshow()" @@ -310,9 +283,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig = plt.figure()\n", @@ -335,9 +306,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from matplotlib.backends.backend_nbagg import new_figure_manager,show\n", @@ -361,9 +330,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.animation as animation\n", @@ -384,7 +351,7 @@ " return line,\n", "\n", "ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,\n", - " interval=32., blit=True)\n", + " interval=100., blit=True)\n", "plt.show()" ] }, @@ -404,9 +371,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib\n", @@ -430,9 +395,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import itertools\n", @@ -477,9 +440,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import time\n", @@ -506,9 +467,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -531,9 +490,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -549,9 +506,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -569,7 +524,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### UAT17 - stopping figure when removed from DOM\n", + "### UAT 18 - stopping figure when removed from DOM\n", "\n", "When the div that contains from the figure is removed from the DOM the figure should shut down it's comm, and if the python-side figure has no more active comms, it should destroy the figure. Repeatedly running the cell below should always have the same figure number" ] @@ -577,9 +532,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -597,20 +550,59 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "fig.canvas.manager.reshow()" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, + "source": [ + "### UAT 19 - Blitting\n", + "\n", + "Clicking on the figure should plot a green horizontal line moving up the axes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "\n", + "cnt = itertools.count()\n", + "bg = None\n", + "\n", + "def onclick_handle(event):\n", + " \"\"\"Should draw elevating green line on each mouse click\"\"\"\n", + " global bg\n", + " if bg is None:\n", + " bg = ax.figure.canvas.copy_from_bbox(ax.bbox) \n", + " ax.figure.canvas.restore_region(bg)\n", + "\n", + " cur_y = (next(cnt) % 10) * 0.1\n", + " ln.set_ydata([cur_y, cur_y])\n", + " ax.draw_artist(ln)\n", + " ax.figure.canvas.blit(ax.bbox)\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.plot([0, 1], [0, 1], 'r')\n", + "ln, = ax.plot([0, 1], [0, 0], 'g', animated=True)\n", + "plt.show()\n", + "ax.figure.canvas.draw()\n", + "\n", + "ax.figure.canvas.mpl_connect('button_press_event', onclick_handle)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [] } @@ -631,9 +623,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.8.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 }