Search code examples
pythonmatplotlibtkinter-canvas

How to control the zorder of polygons


I have modified the Poly Editor (from the gallery of Matplotlib) to make it able to load several polygons at a time. This works fine except on one point: I don't know how to control the zorder of the polygons.

Here is my code (only interesting lines):

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import polygons # a list of matplotlib.patches.Polygon
import poly_editor

root = tk.Tk()

fig = Figure(figsize=(10, 8))
ax = fig.add_subplot(aspect='equal')

canvas = FigureCanvasTkAgg(fig, root)

poly_editors = []
for poly in polygons:
    ax.add_patch(poly)
    poly_editors.append(poly_editor.PolygonInteractor(ax, poly, canvas))

With this code, the first polygon is below, the last one above.enter image description here I tried to test the use of zorder at this time:

for i,poly in enumerate(polygons):
    poly.set_zorder(10 - i)
    ax.add_patch(poly)
    poly_editors.append(poly_editor.PolygonInteractor(ax, poly, canvas))

Then the result is the same, zorder is not taken into account.

My goal is to put a polygon above all others when pressing the letter 't' inside it. I have written this code in the poly_editor, which obviously does not work.

def on_key_press(self, event):
    if event.key == 't':
        if self.poly.contains_point((event.x, event.y)):
            self.poly.set(zorder=10)
        else:
            self.poly.set(zorder=0)
        self.ax.draw_artist(self.poly)
        self.canvas.draw_idle()

I am new to matplotlib, could somebody help me to understand how zorder works ? I saw many questions related to zorder but I don't find any answer to my question.


Solution

  • I finally solved my problem. Since zorder cannot be used, the only way available is to play on the ax.patches list, as the last polygon of this list is the one above all others.

    So if I write that to put the first polygon on top:

    ax.patches.remove(polygons[0])
    ax.add_patch(polygons[0])
    

    It is not sufficient. I have also to recreate the PolygonInteractor

    instance:ax.patches.remove(polygons[0])
    ax.add_patch(polygons[0])
    p = poly_editor.PolygonInteractor(ax, poly, canvas)
    canvas.draw_idle()
    

    That test by hand works as expected. But I have to do that from inside the PolygonInteractor instance.

    Here is the interesting code of the final solution that implements exactly what I asked for:

    ### main
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import polygons # a list of matplotlib.patches.Polygon
    import poly_editor
    
    root = tk.Tk()
    
    fig = Figure(figsize=(10, 8))
    ax = fig.add_subplot(aspect='equal')
    
    canvas = FigureCanvasTkAgg(fig, root)
    poly_editors = {}
    for poly in polygons:
        ax.add_patch(poly)
        poly_editors[poly] = poly_editor.PolygonInteractor(ax, poly, poly_editors)
    
    ### PolygonInteractor.__init__
        def __init__(self, ax, poly, container):
            self.ax = ax
            self.poly = poly
            self.canvas = ax.figure.canvas
            self.container = container
    
    
    ### inside PolygonInteractor
        def on_key_press(self, event):
            if event.key == 't':
                if self.poly.contains_point((event.x, event.y)):
                    self.ax.patches.remove(self.poly)
                    self.ax.add_patch(self.poly)
                    self = self.__class__(self.ax, self.poly, self.container)
                    self.container[self.poly] = self
    

    It is very simple to replace the current instance by a newly created one. It seems a bit hacky and magical... Python will always amaze me, despite my long experience!