Skip to content
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

Drawn lines on plot get incorrect over a certain width #4054

Closed
MathieuDuponchelle opened this issue Jan 30, 2015 · 8 comments · Fixed by #4075
Closed

Drawn lines on plot get incorrect over a certain width #4054

MathieuDuponchelle opened this issue Jan 30, 2015 · 8 comments · Fixed by #4075

Comments

@MathieuDuponchelle
Copy link
Contributor

I wasn't sure how to word that best, basically :

http://paste.fedoraproject.org/178311/19358142

If the width request is changed to something more usual, the line is correctly plotted.

@tacaswell tacaswell added the status: needs clarification Issues that need more information to resolve. label Jan 30, 2015
@tacaswell tacaswell added this to the unassigned milestone Jan 30, 2015
@tacaswell
Copy link
Member

Can you make a simpler example? Is this a gtk specific thing or a cairo specific thing?

The string 'width_request' does not appear anywhere in the mpl code base and the only object that has a props attribute is mpl.patches.Shadow so I am really unclear what is even going on.

Please past code as links to external sites will rot over time.

@MathieuDuponchelle
Copy link
Contributor Author

!/usr/bin/python3

from gi.repository import Gtk
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
from matplotlib.patches import Rectangle

class DrawPoints:
    def init(self):
         self.xs = [0, 10]
         self.ys = [0, 5]

         self.fig = Figure(figsize=(0,0), dpi=80, facecolor='red')
         self.ax = self.fig.add_axes ([0, 0, 1, 1], axisbg='None')
         self.fig.patch.set_visible (False)
         self.canvas = FigureCanvas(self.fig)

    def draw(self):
        '''Draws the ax-subplot'''
        self.ax.cla()
        self.ax.set_xlim(0,10)
        self.ax.set_ylim(0,10)
        self.ax.plot (self.xs, self.ys)

window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
window.set_default_size(800, 500)

box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
window.add(box)

scrolled = Gtk.ScrolledWindow ()

points = DrawPoints()
points.draw()

scrolled.add (points.canvas)
points.canvas.props.width_request = 50000

box.pack_start(scrolled, True, True, 0)

window.show_all()
Gtk.main()

@MathieuDuponchelle
Copy link
Contributor Author

Thanks for the quick answer, sorry about the first paste I should have cleaned it up a little.

Basically, setting the width_request to 50000 tells the canvas it should be 50000 pixels wide, it's a gtkwidget property.

I suspect this is a cairo backend issue, I think the path drawing should somehow take into account the clip_extents, I don't understand the code in there (not familiar with transform aka Affine2D).

Hope that helps

@tacaswell tacaswell added backend: cairo and removed status: needs clarification Issues that need more information to resolve. labels Jan 30, 2015
@mdboom
Copy link
Member

mdboom commented Jan 30, 2015

I suspect this is a limitation of Cairo. Most rendering frameworks use fixed point arithmetic, so there is a limit on the number of pixels in any dimension that it can refer to. Cairo in fact has a hard limit of 32 pixels in either dimension when creating a buffer (and it raises an exception in the Gtk3Agg backend about this though strangely not in the Gtk3Cairo backend) but it appears that even at that size there are rounding errors in the renderer.

You can switch to the Gtk3Agg backend instead, which still limits you to 32k pixels, but at least the rendering isn't broken. Or if you can do without a gui, the Agg backend can handle even larger (though even there, not unlimited -- not sure off hand what its limit is).

TL;DR: Images of this size present a number of challenges and are not really supported directly by matplotlib. Rendering the image off screen and then tiling to a window may be your best option, or perhaps handling the scrollbar yourself and transforming/redrawing the graph dynamically.

@MathieuDuponchelle
Copy link
Contributor Author

Redrawing the graph dynamically is what I'm going for, but not ideal.

I am under the impression that matplotlib somehow does something wrong, because of the updated script I'll attach below.

@MathieuDuponchelle
Copy link
Contributor Author

#!/usr/bin/python3

from gi.repository import Gtk
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
from matplotlib.patches import Rectangle

class DrawPoints:
    def __init__(self):
        self.xs = [49000, 50000]
        self.ys = [0, 5]

        self.fig = Figure(figsize=(0,0), dpi=80, facecolor='red')
        self.ax = self.fig.add_axes ([0, 0, 1, 1], axisbg='None')
        self.fig.patch.set_visible (False)
        self.canvas = FigureCanvas(self.fig)

    def draw(self):
        '''Draws the ax-subplot'''
        self.ax.cla()
        self.ax.set_xlim(0,50000)
        self.ax.set_ylim(0,10)
        self.ax.plot (self.xs, self.ys)

window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
window.set_default_size(800, 500)

box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
window.add(box)

scrolled = Gtk.ScrolledWindow ()

points = DrawPoints()
points.draw()

scrolled.add (points.canvas)
points.canvas.props.width_request = 50000

box.pack_start(scrolled, True, True, 0)

window.show_all()
Gtk.main()

@MathieuDuponchelle
Copy link
Contributor Author

In that example, the line drawn at the end of the viewport is correct, which makes me assume that matplotlib could be clever and only plot the line relevant to the cairo context clip_extents. This is basically what I'm doing in my application, by moving the figure canvas around and interpolating y values to draw the line correctly. This is however not a very good solution, and I see a lot of nasty flickering. I think something could be done in the draw_path method of backend_cairo.py, because the context.clip_extents() are available there, however I don't understand the transformation logic enough.

Thaks again for the quick answers :)

@MathieuDuponchelle
Copy link
Contributor Author

I'll briefly explain our use case:

We develop an open source video editor, Pitivi, and we want to use matplotlib to plot "keyframes".
Keyframes are timed values that describe how a given property evolves over time. An audio clip may for example have a "volume" property, which the user can set keyframes on.

We are moving our timeline away from the "Clutter" toolkit, and our previous implementation of keyframes in the timeline was curves overlaid on the various clips, which one could directly control. The clips are scrollable and infinitely zoomable.

We now layout the timeline in plain Gtk, and while considering our options for plotting these curves, we thought about matplotlib and decided to experiment a bit with it instead of implementing everything in cairo.

matplotlib serves our simple real time use case very well, and if it were not for that issue it would make our code very elegant and future proof (we're thinking about implementing bezier curves, may want to draw a grid etc)

Solving this issue would mean a lot to us, and would let us provide a fun showcase for matplotlib in a real time application far more easily :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants