mp-007 / kivy_matplotlib_widget

A fast matplotlib rendering for Kivy based on Kivy_matplotlib project and kivy scatter. Matplotlib used 'agg' backend
MIT License
35 stars 7 forks source link

Accessing mouse coordinates using mpl_connect to add addional plots based on where the click was #20

Closed prodat1 closed 11 months ago

prodat1 commented 11 months ago

I am using the graph_widget.py in my project. The MatplotFigure is integrated in a KV-String

 BoxLayout:
            orientation: 'vertical'
            height: self.minimum_height 
            MatplotFigureCustom:
                id:myPlot

self.ids["myPlot"].figcanvas.mpl_connect('motion_notify_event', on_motion)

The event is never called. Is there another way of accessing the events, is this a bug or am I missing something?

mp-007 commented 11 months ago

You can bind a mouse_pos event with kivy and then used the motion_notify_event from figure canvas. See example above:


from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

import numpy as np
import matplotlib.pyplot as plt

from kivy_matplotlib_widget.uix.graph_widget import MatplotFigure
from kivy.core.window import Window

def enter_axes(event):
    print('enter_axes', str(event.inaxes) + 'event' + \
          event.inaxes.figure.texts[0].get_text()[-1])
    #+ 'event' + event.inaxes.figure.texts[0].figure.texts[0].get_text()[-1]
    event.inaxes.patch.set_facecolor('yellow')
    event.canvas.draw()

def leave_axes(event):
    print('leave_axes', str(event.inaxes) + 'event' + \
          event.inaxes.figure.texts[0].get_text()[-1])
    event.inaxes.patch.set_facecolor('white')
    event.canvas.draw()

def enter_figure(event):
    print('enter_figure', str(event.canvas.figure) + 'event' + \
          event.canvas.figure.texts[0].get_text()[-1])
    event.canvas.figure.patch.set_facecolor('red')
    event.canvas.draw()

def leave_figure(event):
    print('leave_figure', str(event.canvas.figure) + 'event' + \
          event.canvas.figure.texts[0].get_text()[-1])
    event.canvas.figure.patch.set_facecolor('grey')
    event.canvas.draw()

def motion_notify(event):
    print(f"event.x: {event.x}, event.y: {event.y}")

kv = """
<Test>:
    orientation: 'vertical'
    Button:
        size_hint_y: None
        height: 40
"""

Builder.load_string(kv)

class Test(BoxLayout):
    def __init__(self, *args, **kwargs):
        super(Test, self).__init__(*args, **kwargs)
        self.add_plot()

    def get_fc(self, i):
        fig1 = plt.figure()
        fig1.suptitle('mouse hover over figure or axes to trigger events' +
                      str(i))
        ax1 = fig1.add_subplot(111)
        # ax1 = fig1.add_subplot(211)
        # ax2 = fig1.add_subplot(212)
        wid = MatplotFigure()
        # wid.

        wid.figure = fig1
        wid.axes = ax1
        wid.xmin,wid.xmax=ax1.get_xlim()
        wid.ymin,wid.ymax=ax1.get_ylim()
        wid.figure.canvas.mpl_connect('figure_enter_event', enter_figure)
        wid.figure.canvas.mpl_connect('figure_leave_event', leave_figure)
        wid.figure.canvas.mpl_connect('axes_enter_event', enter_axes)
        wid.figure.canvas.mpl_connect('axes_leave_event', leave_axes)
        wid.figure.canvas.mpl_connect('motion_notify_event', motion_notify)
        wid.figure.canvas.entered_figure = True
        def _on_mouse_pos(*args):
            '''Kivy Event to trigger the following matplotlib events:
               `motion_notify_event`, `leave_notify_event` and
               `enter_notify_event`.
            '''
            pos = args[1]
            newcoord = wid.to_widget(pos[0], pos[1], relative=True)
            x = newcoord[0]
            y = newcoord[1]
            inside = wid.collide_point(*pos)
            if inside:
                wid.figure.canvas.motion_notify_event(x, y, guiEvent=None)
            if not inside and not wid.figure.canvas.entered_figure:
                wid.figure.canvas.leave_notify_event(guiEvent=None)
                wid.figure.canvas.entered_figure = True
            elif inside and wid.figure.canvas.entered_figure:
                wid.figure.canvas.enter_notify_event(guiEvent=None, xy=(pos[0], pos[1]))
                wid.figure.canvas.entered_figure = False

        Window.bind(mouse_pos=_on_mouse_pos)        
        return wid

    def add_plot(self):
        self.add_widget(self.get_fc(1))
        self.add_widget(BoxLayout(size_hint_y=None,height=40))
        self.add_widget(self.get_fc(2))

class TestApp(App):
    def build(self):
        return Test()

if __name__ == '__main__':
    TestApp().run()```
mp-007 commented 11 months ago

This is an other example with a pick_event


from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backend_bases import MouseEvent
from kivy_matplotlib_widget.uix.graph_widget import MatplotFigure
from kivy.core.window import Window

def onpick(event):
    thisline = event.artist
    print(event)
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.artist.contains(event.mouseevent)[1]['ind']
    if len(ind)!=0:
        points = tuple([xdata[ind][0], ydata[ind][0]])
        print('onpick points:', points)

kv = """
<Test>:
    orientation: 'vertical'
    Button:
        size_hint_y: None
        height: 40
"""

Builder.load_string(kv)

class Test(BoxLayout):
    def __init__(self, *args, **kwargs):
        super(Test, self).__init__(*args, **kwargs)
        self.add_plot()

    def get_fc(self, i):
        fig1 = plt.figure()
        fig1.suptitle('mouse hover over figure or axes to trigger events' +
                      str(i))
        ax1 = fig1.add_subplot(111)
        line, = ax1.plot(np.random.rand(100), 'o',
                        picker=True, pickradius=5)

        wid = MatplotFigure()

        wid.figure = fig1
        wid.axes = ax1
        wid.xmin,wid.xmax=ax1.get_xlim()
        wid.ymin,wid.ymax=ax1.get_ylim()

        wid.figure.canvas.mpl_connect('pick_event', onpick)

        def onPressed(instance,event):
            pos = [event.x,event.y]
            newcoord = wid.to_widget(pos[0], pos[1], relative=True)
            x = newcoord[0]
            y = newcoord[1]
            inside = wid.collide_point(*pos)
            if inside:

                if event.button == 'left':
                    s = 'pick_event'
                    mouseevent = MouseEvent(s, wid.figure.canvas, x, y, wid.figure.canvas._button, wid.figure.canvas._key,
                                       guiEvent=None)
                    wid.figure.canvas.pick_event(mouseevent,line)
                    print("left mouse clicked")            

        Window.bind(on_touch_down = onPressed)   
        return wid

    def add_plot(self):
        self.add_widget(self.get_fc(1))
        self.add_widget(BoxLayout(size_hint_y=None,height=40))
        self.add_widget(self.get_fc(2))

class TestApp(App):
    def build(self):
        return Test()

if __name__ == '__main__':
    TestApp().run()```
mp-007 commented 11 months ago

@prodat1 feel free to close this issue if I correctly answer your purpose.

mp-007 commented 11 months ago

@prodat1 I have read again your question and maybe this example will be better for your purpose (I still don't know what you're trying to do). https://github.com/mp-007/kivy_matplotlib_widget/blob/main/examples/example_custom_touch_widget/line_data.py It used kivy on_touch_down/on_touch_move/on_touch_up to manage the touch/mouse press and move event.