I am trying to show a few different sets of data on one plot. The user can then select a specific data set to view alone, and in that case, I want to offer a hover cursor to show information about that data.
My hover cursors never appear, though. Why not?
It's taken me a while to produce an MRE, but here it is:
import customtkinter as ctk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import mplcursors
def plot_data(x_data, y_data, plot_var):
x_plot = [] # data to draw on the plot
y_plot = []
if plot_var in [1,2]:
x_plot.append(x_data[0])
y_plot.append(y_data[0])
if plot_var in [1,3]:
x_plot.append(x_data[1])
y_plot.append(y_data[1])
if plot_var in [1,4]:
x_plot.append(x_data[2])
y_plot.append(y_data[2])
if plot_var in [1,5]:
x_plot.append(x_data[3])
y_plot.append(y_data[3])
fig = Figure(figsize=(5, 5))
ax = fig.add_subplot(111)
for i in range(len(x_plot)):
ax.plot(x_plot[i], y_plot[i], marker='o', label=f"Data {i+1}")
if plot_var != 1:
cursor = mplcursors.cursor(ax, hover=True)
cursor.connect("add", lambda sel: sel.annotation.set_text(f"Data {plot_var-1}"))
return fig
def draw_plot_from_rb(canvas, x_data, y_data, plot_var):
canvas.figure = plot_data(x_data, y_data, plot_var)
canvas.draw()
canvas.get_tk_widget().pack(side=ctk.TOP, fill=ctk.BOTH, expand=True)
return canvas
# Create the main window
window = ctk.CTk()
window.title("Custom Window")
# Create the options frame
options_frame = ctk.CTkFrame(window)
options_frame.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True)
# Create the data frame
data_frame = ctk.CTkFrame(window)
data_frame.pack(side=ctk.RIGHT, fill=ctk.BOTH, expand=True)
# Add widgets to the options frame
options_label = ctk.CTkLabel(options_frame, text="Options", font=("Arial", 16))
options_label.pack()
# Add widgets to the data frame
data_label = ctk.CTkLabel(data_frame, text="Data", font=("Arial", 16))
data_label.pack()
# Create four separate x, y datasets
x1 = [1, 2, 3, 4, 5]
y1 = [1, 4, 9, 16, 25]
x2 = [1, 2, 3, 4, 5]
y2 = [36, 49, 64, 81, 100]
x3 = [1, 2, 3, 4, 5]
y3 = [121, 144, 169, 196, 225]
x4 = [1, 2, 3, 4, 5]
y4 = [256, 289, 324, 361, 400]
x = [x1, x2, x3, x4]
y = [y1, y2, y3, y4]
# Create four radio button options
plot_var = ctk.IntVar()
plot_var.set(1)
radio1 = ctk.CTkRadioButton(
options_frame, text="View All", variable=plot_var, value=1,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio1.pack()
radio2 = ctk.CTkRadioButton(
options_frame, text="Data 1", variable=plot_var, value=2,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio2.pack()
radio3 = ctk.CTkRadioButton(
options_frame, text="Data 2", variable=plot_var, value=3,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio3.pack()
radio4 = ctk.CTkRadioButton(
options_frame, text="Data 3", variable=plot_var, value=4,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio4.pack()
radio5 = ctk.CTkRadioButton(
options_frame, text="Data 4", variable=plot_var, value=5,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio5.pack()
fig = plot_data(x, y, plot_var.get())
# Create the canvas for the plot
canvas = FigureCanvasTkAgg(fig, master=data_frame)
canvas.draw()
canvas.get_tk_widget().pack(side=ctk.TOP, fill=ctk.BOTH, expand=True)
# Start the main loop
window.mainloop()
It works for me if I create fig
and ax
only once and later I use ax.clear()
to remove previous plots.
Maybe it works because this way cursor
is created after adding fig
to FigureCanvasTkAgg
like suggests answer for
mplcursors not getting generated when used with Tkinter
I think new fig
would need to be added again to FigureCanvasTkAgg
before creating new cursor
but your original code first creates cursor
and later it assigns new fig
to FigureCanvasTkAgg
. But I didn't test if changing order could resolve problem - because there is no need to create new fig
.
import customtkinter as ctk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import mplcursors
def plot_data(x_data, y_data, plot_var):
ax.clear() # <--- clear plot instead of creating new one
x_plot = [] # data to draw on the plot
y_plot = []
if plot_var in [1,2]:
x_plot.append(x_data[0])
y_plot.append(y_data[0])
if plot_var in [1,3]:
x_plot.append(x_data[1])
y_plot.append(y_data[1])
if plot_var in [1,4]:
x_plot.append(x_data[2])
y_plot.append(y_data[2])
if plot_var in [1,5]:
x_plot.append(x_data[3])
y_plot.append(y_data[3])
for i in range(len(x_plot)):
ax.plot(x_plot[i], y_plot[i], marker='o', label=f"Data {i+1}")
if plot_var != 1:
print('add cursor:', plot_var)
cursor = mplcursors.cursor(ax, hover=True)
cursor.connect("add", lambda sel: sel.annotation.set_text(f"Data {plot_var-1}\nx: {sel.target[0]}\ny: {sel.target[1]}"))
def draw_plot_from_rb(canvas, x_data, y_data, plot_var):
plot_data(x_data, y_data, plot_var)
canvas.draw()
#canvas.get_tk_widget().pack(side=ctk.TOP, fill=ctk.BOTH, expand=True) # there is no need to put new widget
#return canvas # button can't get this value - is using `return` is useless
# Create the main window
window = ctk.CTk()
window.title("Custom Window")
# Create the options frame
options_frame = ctk.CTkFrame(window)
options_frame.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True)
# Create the data frame
data_frame = ctk.CTkFrame(window)
data_frame.pack(side=ctk.RIGHT, fill=ctk.BOTH, expand=True)
# Add widgets to the options frame
options_label = ctk.CTkLabel(options_frame, text="Options", font=("Arial", 16))
options_label.pack()
# Add widgets to the data frame
data_label = ctk.CTkLabel(data_frame, text="Data", font=("Arial", 16))
data_label.pack()
# Create four separate x, y datasets
x1 = [1, 2, 3, 4, 5]
y1 = [1, 4, 9, 16, 25]
x2 = [1, 2, 3, 4, 5]
y2 = [36, 49, 64, 81, 100]
x3 = [1, 2, 3, 4, 5]
y3 = [121, 144, 169, 196, 225]
x4 = [1, 2, 3, 4, 5]
y4 = [256, 289, 324, 361, 400]
x = [x1, x2, x3, x4]
y = [y1, y2, y3, y4]
# Create four radio button options
plot_var = ctk.IntVar()
plot_var.set(1)
radio1 = ctk.CTkRadioButton(
options_frame, text="View All", variable=plot_var, value=1,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio1.pack()
radio2 = ctk.CTkRadioButton(
options_frame, text="Data 1", variable=plot_var, value=2,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio2.pack()
radio3 = ctk.CTkRadioButton(
options_frame, text="Data 2", variable=plot_var, value=3,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio3.pack()
radio4 = ctk.CTkRadioButton(
options_frame, text="Data 3", variable=plot_var, value=4,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio4.pack()
radio5 = ctk.CTkRadioButton(
options_frame, text="Data 4", variable=plot_var, value=5,
command=lambda: draw_plot_from_rb(canvas, x, y, plot_var.get())
)
radio5.pack()
# <--- create `fig` and `ax` only once
fig = Figure(figsize=(5, 5))
ax = fig.add_subplot(111)
plot_data(x, y, plot_var.get())
# Create the canvas for the plot
canvas = FigureCanvasTkAgg(fig, master=data_frame)
canvas.draw()
canvas.get_tk_widget().pack(side=ctk.TOP, fill=ctk.BOTH, expand=True)
# Start the main loop
window.mainloop()
BTW: I added details sel.target
in
sel.annotation.set_text(f"Data {plot_var-1}\nx: {sel.target[0]}\ny: {sel.target[1]}")