Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Popover over plot is very slow #12010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
akhilman opened this issue Sep 4, 2018 · 5 comments · Fixed by #12030
Closed

Popover over plot is very slow #12010

akhilman opened this issue Sep 4, 2018 · 5 comments · Fixed by #12030
Labels
Milestone

Comments

@akhilman
Copy link
Contributor

akhilman commented Sep 4, 2018

Bug report

Bug summary

Gtk3 overlay widgets like popover is very slow when they placed over plot.

Code for reproduction

import gi  # isort:skip
gi.require_version('Gtk', '3.0')  # NOQA: E402
from gi.repository import Gtk  # isort:skip

import numpy as np
from matplotlib.backends.backend_gtk3agg import \
    FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.figure import Figure

win = Gtk.Window()

box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
button = Gtk.Button(label='Push me')
box.pack_start(button, False, True, 0)

popover = Gtk.Popover(relative_to=button)
popbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
pixbuf = Gtk.IconTheme.get_default().load_icon('gtk-about', 256, 0)
icon = Gtk.Image.new_from_pixbuf(pixbuf)
popbox.pack_start(icon, False, True, 0)
label = Gtk.Label('Popover should be slow')
popbox.pack_start(label, False, True, 0)
popover.add(popbox)
popbox.show_all()
popbox.set_margin_top(16)
popbox.set_margin_bottom(16)
popbox.set_margin_left(16)
popbox.set_margin_right(16)
button.connect('clicked', lambda w: popover.popup())

fig = Figure()
ax = fig.add_subplot(111)
canvas = FigureCanvas(fig)
canvas.set_size_request(640, 489)
box.pack_start(canvas, True, True, 0)

arr = np.random.random((2, 2000, 2000))
ax.scatter(arr[0][0], arr[0][1], arr[1].flatten() * 40)

win.add(box)
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()

Actual outcome

Click to "push me" button to show popover, animation will be slow.
cProfile shows that most of time python spends in redrawing entire plot during animation.

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)                                                                      
       43   30.353    0.706   30.359    0.706 collections.py:883(set_sizes)                                                                  
        1   11.329   11.329   50.730   50.730 Gtk.py:1608(main)                                                                              
       42    4.675    0.111    5.254    0.125 {method 'draw_path_collection' of 'matplotlib.backends._backend_agg.RendererAgg' objects}      
        1    0.245    0.245    0.245    0.245 {method 'random_sample' of 'mtrand.RandomState' objects}                                       
    90130    0.155    0.000    0.155    0.000 path.py:208(vertices)                                                                          
    87356    0.155    0.000    0.155    0.000 path.py:241(simplify_threshold)                                                                
35583/26326    0.155    0.000    0.295    0.000 artist.py:230(stale)                                                                         
    88197    0.150    0.000    0.150    0.000 path.py:222(codes)                                                                             
    88616    0.145    0.000    0.145    0.000 path.py:260(should_simplify)     

Expected outcome

Plot should be cached somehow and won't be redrawn in this case.

Matplotlib version

  • Operating system: Debian Sid
  • Matplotlib version: 2.2.3
  • Matplotlib backend: Gtk3Agg and Gtk3Cairo
  • Python version: 3.6.6
  • Gtk: 3.22,30

Matplotlib installed in virtual environment with pip.

I would like to fix this issue and make PR. Please point me any direction how this problem can be fixed.

@akhilman akhilman changed the title Popover over plot is very slow. Popover over plot is very slow Sep 4, 2018
@anntzer
Copy link
Contributor

anntzer commented Sep 4, 2018

Somewhat curiously, with mplcairo.gtk (https://github.com/anntzer/mplcairo), this does not happen. (The initial draw is much slower though, and I think I know why; also the example; also this exposes a bug in mplcairo's scatter rendering that I need to look into. But I think the issue above is something about event loop integration so that doesn't matter.)

(EDIT: actually that's not a mplcairo bug but an inconsistency in matplotlib's own behavior, see #12021 -- mplcairo was drawing 4e6 points as would matplotlib's vector backends, whereas agg draws only 2e3.)

Looking at this more in depth, I think this is because the gtk3agg backend always rerenders the full figure in on_draw_event:

        if not len(self._bbox_queue):
            self._render_figure(w, h)

Compare with mplcairo.gtk which only does so if the last rendered buffer had been invalidated

        buf = self.get_renderer(_draw_if_new=True)._get_buffer()

(you need to look into the caching provided by _draw_if_new).

I think the builtin qt backends also avoid forcing a re-rendering in paintEvent through the draw_idle mechanism (which jumps through a lot of hoops), so it may be worth comparing it too.

@akhilman
Copy link
Contributor Author

akhilman commented Sep 4, 2018

This small modification seems does trick, but I not tested it in all use cases.

    def _render_figure(self, width, height):
        backend_agg.FigureCanvasAgg.draw(self)

    def queue_draw(self):    # ADDED
        allocation = self.get_allocation()
        w, h = allocation.width, allocation.height
        self._render_figure(w, h)
        super().queue_draw()

    def on_draw_event(self, widget, ctx):
        """ GtkDrawable draw event, like expose_event in GTK 2.X
        """
        allocation = self.get_allocation()
        w, h = allocation.width, allocation.height

        if not len(self._bbox_queue):
            # self._render_figure(w, h)  # REMOVED
            bbox_queue = [transforms.Bbox([[0, 0], [w, h]])]
        else:
            bbox_queue = self._bbox_queue

        if HAS_CAIRO_CFFI and not isinstance(ctx, cairo.Context):
            ctx = cairo.Context._from_pointer(
                cairo.ffi.cast('cairo_t **',
                               id(ctx) + object.__basicsize__)[0],
                incref=True)

@fariza
Copy link
Member

fariza commented Sep 14, 2018

Is this happening with the gtk3cairo backend?

@akhilman
Copy link
Contributor Author

@fariza Yes GTK3Cairo backend also redraws canvas same way. But same solution causes errors and I have no clue how to fix that errors.

@anntzer
Copy link
Contributor

anntzer commented Sep 14, 2018

Almost certainly the redraw can't be avoided with gtk3cairo, given that the whole idea of the backend is to draw the figure directly onto the cairo canvas that gtk's windowing system sets up for you, so there's no "previous" draw that can be kept around.

@QuLogic QuLogic added this to the v3.1 milestone Sep 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants