Matplotlib Text draggable

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Hallo,

ich habe mir eine Klasse geschrieben, mit der ich Text in der Matplotlib verschieben kann. Das funktioniert auch einwandfrei. Nachfolgen der Code dazu:

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from matplotlib         import pylab as p
from matplotlib.text    import Text

class DragHandler(object):

    def __init__(self, figure=None) :
        self.figure = figure
        if self.figure is None : self.figure = p.gcf()
        self.dragged = None

        self.figure.canvas.mpl_connect("pick_event", self.on_pick_event)
        #self.figure.canvas.mpl_connect("button_press_event", self.on_press_event)
        self.figure.canvas.mpl_connect("button_release_event", self.on_release_event)
        self.figure.canvas.mpl_connect("motion_notify_event", self.on_motion)

    def on_pick_event(self, event):
        if isinstance(event.artist, Text):
            self.dragged = event.artist
            x0, y0 = self.dragged.get_position()
            self.pick_pos = (x0, y0, event.mouseevent.xdata,
                                    event.mouseevent.ydata)
        return True

    def on_motion(self, event):
        if self.dragged is None: return
        x0, y0 , xpress, ypress = self.pick_pos
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        tmppos = (x0+dx, y0+dy)
        self.dragged.set_position(tmppos)
        self.figure.canvas.draw()

    def on_release_event(self, event):
        if self.dragged is not None :
            self.dragged = None
            self.figure.canvas.draw()
        return True

fig = p.figure()
ax = fig.add_subplot(111)
p.grid()

col = 'black'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
p.text(0.05, 0.95, "text", picker= True, color = col, 
                            transform=ax.transAxes, 
                            fontsize=14, verticalalignment='top', 
                            bbox=props)

TextDragH = DragHandler(fig)

p.show()
Wenn ich das Anzeigen des Textes aber in einer separaten Klasse aufrufe, dann funktioniert das nicht mehr. Ich habe mir eine Hilfsanzeige in die on_pick-Funktion geschrieben. Das Programm springt noch nicht einmal in diese Funktion hinein, dass heisst ich sehe die print-Anweisung überhaupt nicht. Als wenn "mpl_connect("pick_event", self.on_pick_event)" schon nicht funktionieren würde. Damit ich hier nicht zuviel Code reinsetzen muss und das zu unübersichtlich wird, habe ich die Klasse auf die wesentlichen Funktionen reduziert. Der nachfolgende Code ist lauffähig in Python 3, aber das Verschieben der Textbox funktioniert nicht. Es ist auch egal, ob ich p.show() in die Klasse schreibe oder ganz am Ende (so wie im Beispiel). Nichts von dem funktioniert.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from matplotlib         import pylab as p
from matplotlib.text    import Text

class DragHandler(object):

    def __init__(self, figure=None) :
        self.figure = figure
        if self.figure is None : self.figure = p.gcf()
        self.dragged = None

        self.figure.canvas.mpl_connect("pick_event", self.on_pick_event)
        #self.figure.canvas.mpl_connect("button_press_event", self.on_press_event)
        self.figure.canvas.mpl_connect("button_release_event", self.on_release_event)
        self.figure.canvas.mpl_connect("motion_notify_event", self.on_motion)

    def on_pick_event(self, event):
        print("on pick")
        if isinstance(event.artist, Text):
            self.dragged = event.artist
            x0, y0 = self.dragged.get_position()
            self.pick_pos = (x0, y0, event.mouseevent.xdata,
                                    event.mouseevent.ydata)
        return True

    def on_motion(self, event):
        if self.dragged is None: return
        x0, y0 , xpress, ypress = self.pick_pos
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        tmppos = (x0+dx, y0+dy)
        self.dragged.set_position(tmppos)
        self.figure.canvas.draw()

    def on_release_event(self, event):
        if self.dragged is not None :
            self.dragged = None
            self.figure.canvas.draw()
        return True

class PlotBaseClass(object):

    def __init__(self, fig):
        '''
        Constructor
        '''
        self.figure             = fig
        self.axes               = {}
        self.axes['left']       = self.figure.add_subplot(111)
        TextDragH               = DragHandler(self.figure)
  
    def showText(self,textlist):
        self.text = {}
        props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
        for col, text in textlist:
            self.text[col] = self.axes['left'].text(0.05, 0.95, 
                            text, picker= True, color = col, 
                            transform=self.axes['left'].transAxes, 
                            fontsize=14, verticalalignment='top', 
                            bbox=props)
        

fig = p.figure()
plots = PlotBaseClass(fig) 
tlist = [('black', 'Text1')]
plots.showText(tlist)
p.show()
Der gesamte Code ist in Python 3. Kann mir jemand sagen was ich falsch mache und warum das zweite Beispiel nicht funktioniert?

Danke im Voraus
Zuletzt geändert von Anonymous am Dienstag 1. November 2016, 13:19, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@rmanske: Ein Problem welches man ab und zu mit in C/C++/… geschriebenen Bibliotheken hat ist die Speicherverwaltung. Pythons automatische Speicherverwaltung weiss nichts von Speicher/Objekten aus solchen Bibliotheken und die Bibliotheken wissen nichts von Python-Objekten und deren Lebensdauer.

Im ersten Beispiel erstellst Du ein `DragHandler`-Exemplar und bindest das an einen Namen auf Modulebene. Dadurch bleibt es bestehen und die Grafikbibliothek kann die Rückrufmethoden aufrufen.

Im zweiten Beispiel erstellst Du das Exemplar, bindest es aber nur in der `PlotBaseClass.__init__()` (die übrigens *kein* Konstruktor ist, das wäre `__new__()`) an einen lokalen Namen. Der nachdem die `__init__()` abgearbeitet ist verschwindet und damit ist dann auch das `DragHandler`-Exemplar zum Abschuss frei gegeben und kann von der Speicherverwaltung beseitigt werden. Damit sind dann auch die Rückrufmethoden weg.

In reinem Python wäre das kein Problem weil in dem Fall die Methoden, die bei den `mpl_connect()`-Aufrufen übergeben wurden, noch Referenzen auf das `DragHandler`-Exemplar haben und das deswegen bestehen bleiben würde.
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

@BlackJack: Danke. Ich musste zwar dreimal überlegen, was Du meintest. :D

Aber das

Code: Alles auswählen

self.TextDragH               = DragHandler(self.figure)
statt

Code: Alles auswählen

TextDragH               = DragHandler(self.figure)
hat es gelöst.
Antworten