User Selection of point using: fig.canvas.mpl_connect('pick_event',lambda event: on_pick

80 Views Asked by At

I am new to python! I am trying to write a program that reads multiple csv files, gets the data columns and plots them on a scatter plot. Then the user will interact with each plot and select a point. This data point will change colour and a message will be prompted to ask the user whether they are happy with this selection. If the user selects yes, the index of this point will be returned and used for analysis and the next data set will be plotted and the cycle restarts. If ths user selects no, then the data point is changed to the original colour, the index is not returned and the user is prompted to select another point.

This is what I have tried.

However a few things arent working

  1. The colour doesnt change to red when the data point is selected
  2. the index is not returned correctly. For example: It prints out that the 'returned' 'selected_index' is 14 when it shouldve been 1223 or 1273 etc.

Image of script output

import os
import tkinter as tk
from tkinter import filedialog, messagebox
import pandas as pd
from openpyxl import Workbook
import numpy as np
from statistics import mean
import math as M
from scipy import stats
import matplotlib.pyplot as plt
import mplcursors

def on_pick(event,disp_list,load_list,F_vs_D_plot):
    ind = event.ind[0]
    print(ind)

    F_vs_D_plot.set_facecolors(['red' if i == ind else 'blue' for i in range(len(disp_list))])
    # F_vs_D_plot.set_color(ind,'red')
    # plt.scatter(disp_list[ind],load_list[ind],color = 'red')
    plt.draw()

    confirmation = messagebox.askyesno("Confirmation", "Are you sure you want to select this point?")
    if confirmation:
        selected_index = ind
        # print(f"Selected point index: {selected_index}")

        #ask if user wants to close plot and move onto next file
        # if yes, close plot
        # if no, select again and overwrite previous selection for that file
        plt.close()

    return selected_index

def graph_method(disp_list, load_list):
    selectedPoint = None
    fig, ax = plt.subplots()
    mplcursors.cursor(hover=True)
    F_vs_D_plot = ax.scatter(disp_list, load_list, picker=True, color = 'blue')
    selected_index = fig.canvas.mpl_connect('pick_event',lambda event: on_pick(event,disp_list,load_list,F_vs_D_plot))
    print('This is the selected index',selected_index)


    ax.set_xlabel('Displacement (mm)')
    ax.set_ylabel('Load (g)')
    ax.set_title(file_name)

    plt.show()


def main()
    disp_list = random.randint(100, size=(1000))
    load_list = random.randint(100, size=(1000))
    graph_method(disp_list, load_list)
    

1

There are 1 best solutions below

0
pippo1980 On

OK, your code still not clear to me, kind of not exacty an MRE as per SO guidelines ( How to create a Minimal, Reproducible Example ) ,

ended up with :

import tkinter as tk

from tkinter import  messagebox

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import random

file_name = None


def on_pick(event , disp_list,load_list , F_vs_D_plot ):
    
    
    ind = event.ind
    
    F_vs_D_plot.set_facecolors(['red' if i == ind else 'blue' for i in range(len(disp_list))])
    
    plt.draw()

    confirmation = messagebox.askyesno("Confirmation", "Are you sure you want to select this point?")
    
    if confirmation :
        
        print('!!!!!!!!!!!!!!!!!!!!!!!!!!! ', ind , disp_list[ind[0]] , load_list[ind[0]])
        
        print('!!!!!!!!!!!!!!!!!!!!!!!!!!! ', ind , type(ind))
        
    
    pippo = event.artist
    
    print ('line : ' , pippo , type(pippo))
    
    
        
    print(event.artist.get_cursor_data(event))

   


def main() :
    
    disp_list = random.sample(range(1, 11), 10) # lista
    
    load_list = random.sample(range(1, 11), 10)
    
    
    print(disp_list)
    
    print(load_list)
    
    
    root = tk.Tk()
   

    
    fig, ax = plt.subplots()

    
    F_vs_D_plot = ax.scatter(disp_list, load_list, picker=True, color = 'blue')
    
    
    ax.set_xlabel('Displacement (mm)')
    ax.set_ylabel('Load (g)')
    ax.set_title(file_name)
    
    canvas = FigureCanvasTkAgg(fig, master=root)

    canvas.draw()
    
    canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    
    canvas.mpl_connect('pick_event',lambda event: on_pick(event,disp_list , load_list , F_vs_D_plot))

    
    tk.mainloop()
    
    
main()

Running the code I get this plot :

enter image description here

Selecting, clicking on it , one of the dot/points it gets red (see arrowhead) :

enter image description here

And clicking yes in messagebox.askyesno messagebox I get following

output :

[10, 4, 2, 6, 5, 7, 3, 1, 8, 9]
[6, 7, 1, 3, 2, 10, 9, 8, 4, 5]
!!!!!!!!!!!!!!!!!!!!!!!!!!!  [5] 7 10
!!!!!!!!!!!!!!!!!!!!!!!!!!!  [5] <class 'numpy.ndarray'>
line :  <matplotlib.collections.PathCollection object at 0x7f42fdd16940> <class 'matplotlib.collections.PathCollection'>
None

I guess you need to embed yur plot in Tkinter to get the messagebox working properly, see Matplotlib Event Handlers not being Called when Embedded in Tkinter for more about it.

Other interesting link are :

Picking on a scatter plot matplotlib example, showing the use of event.ind for picking on scatter plot (a scatter plot is backed by a PathCollection ) , wasnt able to find any reference about event.ind id matplotlib documentation though :

from numpy.random  import rand

from matplotlib import pyplot as plt

x, y, c, s = rand(4, 100)


def onpick3(event):
    ind = event.ind
    print('onpick3 scatter:', ind, x[ind], y[ind])
    
    print('onpick3 scatter:', ind, type(ind))


fig, ax = plt.subplots()
ax.scatter(x, y, 100*s, c, picker=True)
fig.canvas.mpl_connect('pick_event', onpick3)

plt.show()

and Event handling and picking in Matplotlib to learn more about it, in brief :

Matplotlib works with a number of user interface toolkits (wxpython, tkinter, qt, gtk, and macosx) and in order to support features like interactive panning and zooming of figures, it is helpful to the developers to have an API for interacting with the figure via key presses and mouse movements that is "GUI neutral" so we don't have to repeat a lot of code across the different user interfaces. Although the event handling API is GUI neutral, it is based on the GTK model, which was the first user interface Matplotlib supported. The events that are triggered are also a bit richer vis-a-vis Matplotlib than standard GUI events, including information like which Axes the event occurred in. The events also understand the Matplotlib coordinate system, and report event locations in both pixel and data coordinates.

Sorry about not being able to explain anything in more detail/clarity , just got your question in REVIEW QUEUES , not an expert of matplotlib/tkinter.

Just want to point out that problably PyQt5 is nowadays more fashionable and efficient than Tkinter even if Tkinter is the Python interface to the Tk GUI toolkit shipped with Python