Button mit Funktion zu Matplotlib Toolbar hinzufügen

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

Wie kann ich ein Button mit Funktion zum Matplotlib hinzufügen?

Zum Verständnis:

Bild

Meine bisheriger Code sieht so aus:

Imports:

Code: Alles auswählen

import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
from matplotlib.backend_tools import ToolBase
import mplcursors
Hinzufügen des Tooltips:

Code: Alles auswählen

cursor = mplcursors.cursor(hover=True)
Hinzufügen des Buttons:

Code: Alles auswählen

tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_tool(tm.get_tool("newtool"), "toolgroup")
Hinzufügen des Icons:

Code: Alles auswählen

class NewTool(ToolBase):
	image = r"D:\path\toggle_tooltip.png"
Code den ich durch den Button umsetzen möchte:

Code: Alles auswählen

# wenn ich auf den Button klicken soll das Tooltip disabled werden
# wenn man nochmal drauf klickt soll es wieder angezeigt werden
# z.B. war meine Idee so:
num = int(0)
num += 1
if (num % 2) == 0:
	cursor = mplcursors.cursor(hover=True)
else:
	curser = mplcursors.cursor(hover=False)
Ich weiß nicht wo ich die Funktionalität/die Funktion hinzufügen muss bzw. bin mir nicht ganz sicher, ob der unterer Codeschnipsel so funktionierten könnte. Bei Stackoverflow und anderen Webseiten finde ich leider kaum Beispiele dazu.
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei Matplotlib fängt man meist mit der Beispielseite an:
https://matplotlib.org/3.3.0/gallery/us ... gskip.html
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

Danke für den Link, ich habe den Code mal angepasst:

Neues Tool:

Code: Alles auswählen

class NewTool(ToolToggleBase):
    # overwrites the image which is None on default
    image = r"D:\path\toggle_tooltip.png"
    description = "toggle Tooltip"
    default_toggle = True

    def __init__(self, *args, cursor, **kwargs):
        self.cursor = cursor
        super().__init__(*args, **kwargs)

    def enable(self, *args):
        self.set_tooltip_visibility(False)

    def disable(self, *args):
        self.set_tooltip_visibility(True)

    def set_tooltip_visibility(self, state):
        self.cursor = mplcursors.cursor(hover=state)
Hinzufügen des neuen Buttons:

Code: Alles auswählen

            tm = fig.canvas.manager.toolmanager
            tm.add_tool("newtool", NewTool.__init__(cursor))
Tooltip:

Code: Alles auswählen

            cursor = mplcursors.cursor(hover=True)
            cursor.connect("add", self.show_annotation)
Fehlermeldung:
File "D:/path/filename.py", line 150, in matplotCanvas
tm.add_tool("newtool", NewTool.__init__(cursor))
TypeError: __init__() missing 1 required keyword-only argument: 'cursor'
=> Der button funktioniert jetzt insoweit, dass es ein zusätzliches curserfenster (das standard fenster) angezeigt wird. Das bedeutet es werden jetzt dann zwei angezeigt. Das Ausblenden funktioniert jedoch noch nicht und ich muss es irgendwie schaffen, dass das bestimmte cursor objekt übergebe, aber ich weiß noch nicht wie das geht. Wisst ihr wie das geht?
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

Zeig den gesamten Code. Aus den Fragmente kann man nichts herauslesen.
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

ich darf den gesamten Code glaube ich nicht hier reinschreiben, ich kann dir höchstens als private Nachricht was schicken
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum solltest Du den Teil, über den Du Hilfe brauchst, nicht reinschreiben dürfen? Es soll ja nur dazu dienen, nachvollziehen zu können, was Du denn wirklich tust. Bei Deinem anderen Thread war die Lösung ja auch in der Zeile, die Du NICHT gezeigt hast, hier ist es wohl so ähnlich, und ich habe keine Lust, immer raten zu müssen. Über Private Nachrichten beantworte ich keine Programmierfragen, weil das Forum soll ja dazu dienen, anderen, die ein ähnliches Problem haben, auch zu helfen.
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

Habe es jetzt hinbekommen (Das der der Button mein Tooltip hinzufügt und beim nochmal klicken wird es ausgeblendet). @Sirius3 der Teil, bei dem ich Hilfe brauche steht doch da schon.
Ich würde jetzt nur noch gern die anderen Funktionen (wie z.B. Zoom und Pan disablen, wenn man auf den Button drückt)

Code sieht jetzt so aus:

Code: Alles auswählen

matplotlib.rcParams["toolbar"] = "toolmanager"
from matplotlib.backend_tools import ToolBase, ToolToggleBase

class Tooltip(ToolToggleBase):
    image = r"D:\path\toggle_tooltip.png"
    description = "Show Tooltip"
    default_toggle = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def enable(self, *args):
        self.set_tooltip_visibility(True)
        self.set_tooltip_connection("add")
        self.release_rest()

    def disable(self, *args):
        self.cursor.remove()

    def set_tooltip_visibility(self, state):
        self.cursor = mplcursors.cursor(hover=state)

    def set_tooltip_connection(self, state):
        self.cursor.connect(state, self.show_annotation)

    def release_rest(self):

        self.ToolPan(self._cancel_action())
        self.ToolZoom(self._cancel_action())
	
	#Code der nicht klappt
        #if 'Pan' in self.wx_ids.keys():
        #    self.ToolToggleBase(self.wx_ids['Pan'], False)
        #if 'Zoom' in self.wx_ids.keys():
        #    self.ToggleTool(self.wx_ids['Zoom'], False)
Im Moment stürtzt mein IDE ab, sobald ich auf den Button drücke, was an der Funktion release_rest liegt bzw. an den zwei Testzeilen, die noch nicht stimmen. Ich habe mal in die backend_tools.py reingeschaut, kriege es aber grad noch nicht richtig zusammengebastelt.

Beispiel aus backend_tools.py:

Code: Alles auswählen

class ToolPan(ZoomPanBase):
    """Pan axes with left mouse, zoom with right"""

    default_keymap = rcParams['keymap.pan']
    description = 'Pan axes with left mouse, zoom with right'
    image = 'move'
    cursor = cursors.MOVE
    radio_group = 'default'

    def __init__(self, *args):
        ZoomPanBase.__init__(self, *args)
        self._idDrag = None

    def _cancel_action(self):
        self._button_pressed = None
        self._xypress = []
        self.figure.canvas.mpl_disconnect(self._idDrag)
        self.toolmanager.messagelock.release(self)
        self.toolmanager.get_tool(_views_positions).refresh_locators()

    def _press(self, event):
        if event.button == 1:
            self._button_pressed = 1
        elif event.button == 3:
            self._button_pressed = 3
        else:
            self._cancel_action()
            return

        x, y = event.x, event.y

        self._xypress = []
        for i, a in enumerate(self.figure.get_axes()):
            if (x is not None and y is not None and a.in_axes(event) and
                    a.get_navigate() and a.can_pan()):
                a.start_pan(x, y, event.button)
                self._xypress.append((a, i))
                self.toolmanager.messagelock(self)
                self._idDrag = self.figure.canvas.mpl_connect(
                    'motion_notify_event', self._mouse_move)

    def _release(self, event):
        if self._button_pressed is None:
            self._cancel_action()
            return

        self.figure.canvas.mpl_disconnect(self._idDrag)
        self.toolmanager.messagelock.release(self)

        for a, _ind in self._xypress:
            a.end_pan()
        if not self._xypress:
            self._cancel_action()
            return

        self.toolmanager.get_tool(_views_positions).push_current()
        self._cancel_action()

    def _mouse_move(self, event):
        for a, _ind in self._xypress:
            # safer to use the recorded button at the _press than current
            # button: # multiple button can get pressed during motion...
            a.drag_pan(self._button_pressed, event.key, event.x, event.y)
        self.toolmanager.canvas.draw_idle()
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

@maGGTech: Dein Code funktioniert gar nicht, weil matplotlib benutzt wird, bevor irgendetwas importiert wird. Dann macht der Code einfach gar nichts, außer zwei Klassen zu definieren, wobei die zweite einfach nur irgendein kopiertes Beispiel ist. Benutzt wird das ganze gar nicht.
Wie soll man da helfen können?
Dazu gehört nunmal ein lauffähiges Beispielprogramm, das den Fehler zeigt, inklusive komplettem Traceback.
Ein "steht doch schon da" stößt die Leute, die hier helfen wollen, vor den Kopf.
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

@Sirus3: Ich habe nie behauptet, dass der Code ein funktionierendes Program wäre, sondern das der Code in meinem Programm jetzt funktioniert hat. Ich kann dir mein Program nicht funkionsfähig hier reinschreiben. Selbst wenn ich alles reinschreiben würde, würde der Code dennoch nicht komplett funktionieren, weil du auch noch Messdaten bräuchtest.
Wenn ich in diesen Forum nur Hilfe bekomme, in dem ich mein Programm extra umschreiben muss (was viel Aufwand wäre), um sensible Daten nicht zu veröffentlichen und es für euch laufbar zu machen, dann hilft mir das nicht.
Ich habe auch häufiger im Java Forum Fragen gestellt, und dort konnte mir auch geholfen werden, selbst wenn ich nicht den gesamten Code reingeschrieben habe, den braucht man häufig auch gar nicht.
Dieses "kopierte Beispiel" ist die backend_tools.py von matplotlib, wie ich schon geschrieben habe, und an der man sich langhangeln kann, um den richtigen Code zu finden.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist absoluter Standard, das man reproduzierbare Beispiele erstellt. Und die haben oft den positiven Nebeneffekt, dass man sein Problem selbst besser versteht und manchmal schon lösen kann.

Aber wenn’s dir hier nicht gefällt, wie du schon mehrfach bemerkt hast, schlage ich vor, du arbeitest mit Java und dem da so viel besseren Forum. Dir gehts damit besser, und hier vermisst so ein anmaßendes genöle auch keiner.
Benutzeravatar
sparrow
User
Beiträge: 4506
Registriert: Freitag 17. April 2009, 10:28

Also ich könnte schwören, dass ich den Ausdruck "minimal reproducible example" während meiner Java-Zeit gelernt habe.
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

Sensible Daten sollten nicht im Quelltext drin stehen und da man eh für Tests kleine Test-Programme schreibt, fällt so ein Beispiel quasi nebenher ab.
Es ist Deine Entscheidung, ob Du hier Hilfe erhalten willst, oder nicht.
Antworten