Dear Community,
I am trying to make it so that it is possible to click on a plot to toggle specific text labels for points. Below is an example code. I took some help from chatGPT to construct this but I am stuck at the fact that apparently the indices of the 3D scatter point artists and of the text artists are not aligned and I can’t go further than that. So my question is: how to make it so that by clicking on a given point, the associated text label gets toggled? (if it is displayed, it is removed, and if it is not there, then it is displayed?)
Thanks in advance.
Below is the code.
import PySimpleGUI as sg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import mpl_toolkits.mplot3d as mpl3d
class IndexedText:
def __init__(self, text, index):
self.text = text
self.index = index
self.visible = True
# Sample data
d_a = 10
d_b = 20
d_d = 15
solute = 'Sample Solute'
d_a_solvent_eff = np.random.rand(5) * 30
d_b_solvent_eff = np.random.rand(5) * 30
d_d_solvent = np.random.rand(5) * 10 + 12
solvents = [f'Solvent {i}' for i in range(1, 6)]
fig, ax = plt.subplots(figsize=(4.5, 3), dpi=80, subplot_kw={'projection': '3d'})
ax.set_xlabel(r"$\delta$$_{{A}}$ (effective) (MPa$^{{1/2}}$)")
ax.set_ylabel(r"$\delta$$_{{B}}$ (effective) (MPa$^{{1/2}}$)")
ax.set_zlabel(r"$\delta$$_{{D}}$ (MPa$^{{1/2}}$)")
# plot molecules to figure
scatter_solute = ax.scatter([d_a], [d_b], [d_d], color='blue', picker=True)
text_solute = ax.text(d_a, d_b, d_d, 'SOLUTE: %s' % (solute), size=8, zorder=1, color='blue', picker=True)
scatter_solvents = ax.scatter(d_a_solvent_eff, d_b_solvent_eff, d_d_solvent, label=solvents, color='black', picker=True)
text_solvents = [IndexedText(text, i) for i, text in enumerate(solvents)]
for i in range(len(text_solvents)):
text = ax.text(d_a_solvent_eff[i], d_b_solvent_eff[i], d_d_solvent[i], '%s' % (text_solvents[i].text), size=5, zorder=1, color='black', picker=True)
text_solvents[i].text_artist = text
ax.set_xlim([0, 30])
ax.set_ylim([0, 30])
ax.set_zlim([12, 22])
ax.mouse_init() # make sure 3D plot is rotatable
# set up plot title
ax.set_title(solute)
# Embed figure in PySimpleGUI Canvas
layout = [[sg.Canvas(size=(450, 300), background_color='white', key='-CANVAS-', pad=(0, 0))]]
window = sg.Window('Matplotlib Plot in PySimpleGUI Canvas', layout, finalize=True)
# Embed figure in canvas and update interface to account for the change
canvas = FigureCanvasTkAgg(fig, master=window['-CANVAS-'].TKCanvas)
canvas.draw()
canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
def on_pick(event):
print(f'Type of the artist: {type(event.artist)}')
print(f'Indices of picked points: {event.ind}')
if isinstance(event.artist, mpl3d.art3d.Path3DCollection):
# Access the data associated with the picked point using the index
for index in event.ind:
# Access the corresponding Text object using the loop index
text_obj = text_solvents[index]
# Get the 3D coordinates of the point
x_points, y_points, z_points = event.artist._offsets3d
x_point = x_points[index]
y_point = y_points[index]
z_point = z_points[index]
# Convert 3D coordinates to screen space
point_screen = ax.transData.transform_point((x_point, y_point))
# Get the pixel coordinates
x_screen, y_screen = point_screen[:2]
# Toggle visibility of the text artist
text_artist = text_obj.text_artist
text_artist.set_visible(not text_artist.get_visible())
print(f'Solvent {index + 1} is {text_obj.text}')
print(f'2D Coordinates: ({x_point}, {y_point})')
print(f'Screen Coordinates: ({x_screen}, {y_screen})')
print("mouse coordinates", event.mouseevent.xdata, event.mouseevent.ydata, event.mouseevent.x, event.mouseevent.y)
# Redraw the canvas to reflect the changes
canvas.draw()
# Get the pixel coordinates of all text labels
labels_screen = [text_obj.text_artist.get_position()
for text_obj in text_solvents]
print('Screen Coordinates of Text Labels:')
for i, label_screen in enumerate(labels_screen):
print(f'{text_solvents[i].text}: {label_screen}')
# Redraw the canvas to reflect the changes
canvas.draw()
fig.canvas.mpl_connect('pick_event', on_pick)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
window.close()