pythoneventsmouseclick-event

How to stop a python click event from firing again until the first one is done?


When using mouse click event on python, Multiple clicks happen at once. This makes loops to have problem working with a variable that is constantly changing.

I want to single thread it. Stop the click event until all the functionalities in that thread is done, then listen for more.

I am also not sure why I am getting multiple clicks at once. Every time I click, it fires 3 times. That's why I want to ask this question overall to solve this issue.

Here is my code

import matplotlib.pyplot as plt
from matplotlib.offsetbox import TextArea, DrawingArea, OffsetImage, AnnotationBbox
import matplotlib.image as mpimg
import numpy as np
from mpl_point_clicker import clicker


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def isAround(self, anotherPoint):
        if anotherPoint.x == self.x + 1 or anotherPoint.x == self.x - 1 or anotherPoint.x == self.y + 1 or anotherPoint.x == self.y - 1:
            return True
        return False

    def roundPoints(self):
        self.x = round(self.x, 2)
        self.y = round(self.y, 2)
            


mouseClicks = []
slopeAndLengths = []

count = 0


def on_pick(event):
    global count
    artist = event.artist
    xmouse, ymouse = event.mouseevent.xdata, event.mouseevent.ydata
    x, y = artist.get_xdata(), artist.get_ydata()
    ind = event.ind
    # print ('Artist picked:', event.artist)
    # print ('{} vertices picked'.format(len(ind)))
    print ('Pick between vertices {} and {}'.format(min(ind), max(ind)+1))
    # print ('x, y of mouse: {:.2f},{:.2f}'.format(xmouse, ymouse))
    # print ('Data point:', x[ind[0]], y[ind[0]])
    # print(" ------------------- ")
    thisPoint = Point(x[ind[0]], y[ind[0]])
    thisPoint.roundPoints()
    mouseClicks.append(thisPoint)
    for i in range(len(mouseClicks)):
        for j in list(range(len(mouseClicks))):
            if mouseClicks[i].isAround(mouseClicks[j]):
                if not mouseClicks[i] == mouseClicks[j]:
                    mouseClicks.pop(i)
    print(mouseClicks)
    # mouseClicks.append((round(x[ind[0]],2),round(y[ind[0]],2)))
    # count += 1
    # if count % 2 == 0:
    #     mouseClicks.pop()
    #     print(mouseClicks)
    

    if len(mouseClicks)==2:
        length = mouseClicks[1].x - mouseClicks[0].x
        slope = (mouseClicks[1].y - mouseClicks[0].y)/(mouseClicks[1].x - mouseClicks[0].x)

        slopeAndLengths.append((round(slope,2),round(length,2)))
        mouseClicks.pop(0)
        # print(slopeAndLengths)
        # print(mouseClicks)

    # addPoint(round(x[ind[0]],2),round(y[ind[0]],2))
    # ax.plot(round(x[ind[0]],2),round(y[ind[0]],2),'bo-', zorder=2)
    # plt.draw()
    

arr_lena = mpimg.imread('picture.png')
imageBox = OffsetImage(arr_lena, zoom=2)

ab = AnnotationBbox(imageBox, (7.4, 13.6), zorder=1)


fig, ax = plt.subplots()

ax.add_artist(ab)

for row in range(1,30):
    tolerance = 30 # points
    ax.plot(np.arange(0,15,0.5),[i*row/i for i in range(1,15*2+1)], 'ro-', picker=tolerance, zorder=0)


fig.canvas.callbacks.connect('pick_event', on_pick)
klicker = clicker(ax, ["event"], markers=["x"], **{"linestyle": "--"})


plt.draw()
plt.savefig('add_picture_matplotlib_figure.png',bbox_inches='tight')
plt.show()

I have tried this idea to remove points that are near the first received point, but because multiple threads happen at once, this doesn't help.

for i in range(len(mouseClicks)):
        for j in list(range(len(mouseClicks))):
            if mouseClicks[i].isAround(mouseClicks[j]):
                if not mouseClicks[i] == mouseClicks[j]:
                    mouseClicks.pop(i)

Solution

  • How I fix it was like this:

    before I go to the answer, we know that every time a click is received, on_pick function will run. So using time I could dismiss the event fires that were happening at the same time, leaving only one of them.

    Answer:

    Before = datetime.now()
    
    def on_pick(event):
        global Before
        now = datetime.now()
        if (now < Before + timedelta(seconds=1)):
            return
        Before = datetime.now()
        ...