How to enable function while matplotlibs zoom to rectangle is activated? - python

On a tkinter canvas I added the matplotlib navigation toolbar and plotted several lines which I want to pick and modify using a function. I also want to be able to zoom into the canvas and select the lines without activating and deactivating the 'zoom to rectangle' everytime I want to excecute the defined function. Is there a way to simultaneously use matplotlib's zoom function and my defined function?
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
root = Tk.Tk()
fig = Figure()
ax = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=root)
nav = NavigationToolbar2Tk(canvas, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
x = np.arange(10)
ax.plot(x, x, picker=True)
ax.plot(x, 2 * x, picker=True)
ax.plot(x, 3 * x, picker=True)
ax.plot(x, 4 * x, picker=True)
def _onPick(event):
thisline = event.artist
thisline.set_linewidth(5)
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', _onPick)
root.mainloop()

You can connect a "button_press_event" and find out yourself if the click occured at a place that is covered by a line. That is slightly more complicated than using the inbuilt picker.
x = np.arange(10)
ax.plot(x, x, picker=6)
ax.plot(x, 2 * x, picker=6)
ax.plot(x, 3 * x, picker=15)
ax.plot(x, 4 * x, picker=1)
def _onPick(event):
update = False
if event.inaxes == ax:
for line in ax.lines:
if line.get_picker():
cont, ind = line.contains(event)
if cont:
line.set_linewidth(5)
update=True
if update:
fig.canvas.draw_idle()
fig.canvas.callbacks.connect('button_press_event', _onPick)
Note that I redefined the picker to state a radius here, which might be useful to make sure one actually hits a line.

Related

How can i add an ellipse in an animated tkinter gui figure?

I have a tkinter GUI where I'm plotting live data from a sensor using the matplotlib animation. When enough data are collected, a fitting ellipse is calculated. And this ellipse should then be plotted in the same figure as the recorded sensor data.
The original code is a bit lengthy, so here is a piece of working code that shows the problem. Currently the ellipse is drawn in a new window when the animation is stopped by button. Can someone explain me how to add this ellipse to the live plot figure in tkinter GUI?
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt, animation
from matplotlib.patches import Ellipse
import numpy as np
plt.rcParams["figure.figsize"] = [7.00, 3.50]
plt.rcParams["figure.autolayout"] = True
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = plt.Figure(dpi=100)
ax = fig.add_subplot(xlim=(0, 2), ylim=(-1, 1))
line, = ax.plot([], [], lw=2)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
def StopAnimation():
anim.pause()
plotEllipse()
def StartAnimation():
anim.resume()
buttonStop = tkinter.Button(master=root, text="Stop Animation", command=StopAnimation)
buttonStop.pack(side=tkinter.BOTTOM)
buttonStart = tkinter.Button(master=root, text="Start Animation", command=StartAnimation)
buttonStart.pack(side=tkinter.BOTTOM)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def plotEllipse():
ell= Ellipse((1,0), 1, 0.75, angle=45, edgecolor = 'red', facecolor='none', lw = 2)
fig1, ax1 = plt.subplots(1, 1, figsize=(10, 6))
ax1.add_artist(ell)
ax1.set_xlim(0, 2)
ax1.set_ylim(-1, 1)
plt.show()
def init():
line.set_data([], [])
return line,
def animate(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,frames=200, interval=20, blit=True)
tkinter.mainloop()
i would have expected to plot into the tkinter gui figure with ax.add_artist(ell)

Plots in Tkinter

I need the program to display 2 graphs of mathematical functions on the same coordinate line, which the user enters manually into the input window. How can I make this graphs show up in a tkinter window? I heard about using Figure, but then 2 graphs will not be on the same coordinate line
replacements = {
'sin' : 'np.sin',
'cos' : 'np.cos',
'exp': 'np.exp',
'sqrt': 'np.sqrt',
'^': '**',
}
allowed_words = [
'x',
'sin',
'cos',
'sqrt',
'exp',
]
def string2func(string):
for word in re.findall('[a-zA-Z_]+', string):
if word not in allowed_words:
raise ValueError(
'"{}" is forbidden to use in math expression'.format(word)
)
for old, new in replacements.items():
string = string.replace(old, new)
def func(x):
return eval(string)
return func
def create_plot():
a = int(value1.get())
b = int(value2.get())
x = np.linspace(a, b, 1000)
func1 = str(value3.get())
func2 = str(value4.get())
if func1.isnumeric():
func1 = int(value3.get())
func1 = [func1] * 1000
plt.plot(x, func1 , label = 'F1(X)')
else:
func1 = str(value3.get())
strfunc1 = string2func(func1)
plt.plot(x, strfunc1(x), label ='F1(X)')
if func2.isnumeric():
func2 = int(value4.get())
func2 = [func2] * 1000
plt.plot(x, func2, label = 'F2(X)')
else:
func2 = str(value4.get())
strfunc2 = string2func(func2)
plt.plot(x, strfunc2(x), label ='F2(X)')
plt.grid(True)
plt.xlabel('x')
plt.ylabel('y')
plt.title("Simple Plot")
plt.ylim(-50, 50)
plt.show()
You can get axis and use it to draw another line in the same plot - ax.plot().
Matplotlib has special classes to display in different GUIs (tkinter, PyQt, etc.)
Embedding in Tk — Matplotlib 3.6.0 documentation
Example which draws two lines on one plot (in Tkinter) when you press button.
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
# --- functions ---
def draw_plots():
x = np.arange(0, 3.01, .01)
scale = np.random.randint(1, 10)
y1 = scale * np.sin(scale * np.pi * x)
scale = np.random.randint(1, 10)
y2 = scale * np.sin(scale * np.pi * x)
ax.clear() # remove previous plots
#ax.grid(True)
ax.plot(x, y1)
ax.plot(x, y2)
canvas.draw() # refresh window
# --- main ---
root = tk.Tk()
fig = Figure() # Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(fill='both', expand=True) # resize plot when window is resized
ax = fig.add_subplot(111)
#ax.grid(True)
button = tk.Button(root, text='Draw', command=draw_plots)
button.pack()
tk.mainloop()
EDIT:
Example which draws on separated plots
# author: Bartlomiej "furas" Burek (https://blog.furas.pl)
# date: 2022.10.09
# [python - Plots in Tkinter - Stack Overflow](https://stackoverflow.com/questions/73997593/plots-in-tkinter/)
# [Embedding in Tk — Matplotlib 3.6.0 documentation](https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html)
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
# --- functions ---
def draw_plots():
x = np.arange(0, 3.01, .01)
scale = np.random.randint(1, 10)
y1 = scale * np.sin(scale * np.pi * x)
scale = np.random.randint(1, 10)
y2 = scale * np.sin(scale * np.pi * x)
ax1.clear() # remove previous plots
ax2.clear() # remove previous plots
#ax1.grid(True)
#ax2.grid(True)
ax1.plot(x, y1)
ax2.plot(x, y2)
canvas.draw() # refresh window
# --- main ---
root = tk.Tk()
fig = Figure() # Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(fill='both', expand=True) # resize plot when window is resized
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
#ax1.grid(True)
#ax2.grid(True)
button = tk.Button(root, text='Draw', command=draw_plots)
button.pack()
tk.mainloop()

Matplotlib Event Handlers not being Called when Embedded in Tkinter

Overview
I am in the process of embedding a Matplotlib plot in a Tkinter window. I need to use the Matplotlib event handler functions (described here). When run as a standalone Matplotlib figure, I get the correct behavior: the event handlers perform their correct function on the correct user action. But when embedding in a Tkinter window, the Matplotlib event handlers are no longer being called.
Expected Behavior
The Matplotlib event handler should be called when the figure is embedded in a Tkinter window.
Current Behavior
The Matplotlib event handler is not being called.
Minimal Code Snippet
Without Tkinter
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
When this code is run, you can select the line on the plot and the onpick method is called, printing the text to stdout. This is the desired output.
Embedded in Tkinter
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
fig.canvas.mpl_connect('pick_event', onpick)
### TKINTER SETUP ###
root = tkinter.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
tkinter.mainloop()
When this code is run and you try to click on the line, the text is never printed meaning the onpick method is never being called.
Versions
python : 3.6.1
matplotlib : 3.3.4
tkinter : 8.6.6
The event listeners of the Matplotlib Figure object cannot be called when embedded in Tkinter. Instead, you have to add the listeners to the FigureCanvasTkAgg (or similar) object. So a working example built on the previous minimal example would be:
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
#plt.show()
### TKINTER SETUP ###
root = tkinter.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
canvas.mpl_connect("pick_event", onpick) # Add the listener using this command
tkinter.mainloop()

How to make Matplotlib cursor display without mouse in python with Tkinter

I have one, two, or three plots in a canvas of Tkinter. Let's assume I don't have any Mouse. There only is a keyboard. I want a cursor on these plots that moves at the same horizontal axis on all graphs in the canvas and of course when the Tk window shows up the yellow cursors (mplcursors) on all graphs be at the first point of graphs without any mouse movement and clicks(if you run the following code there are two cursors yellow and green, yellow appears by clicking on the line). The yellow cursors must move together (the same x-axes point) by right/left arrow keys on the keyboard.
from matplotlib.widgets import MultiCursor
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import mplcursors
root = Tk()
fig = Figure(figsize=(8,6))
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
y = np.sin(x)
z = np.cos(x)
ax1.plot(x, y, label="sin function")
ax1.legend(loc="upper right")
ax2.plot(x, z, label="cos function")
canvas = FigureCanvasTkAgg(fig,root)
c1 = mplcursors.cursor(ax1, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
c2 = mplcursors.cursor(ax2, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
multi = MultiCursor(fig.canvas, (ax1, ax2), color='g', lw=0.5, horizOn=True, vertOn=True)
ax2.legend(loc="upper left")
canvas.draw()
#pack canavs
canvas.get_tk_widget().pack(side = BOTTOM, fill=BOTH, expand=True)
#create Navigation Toolbar
toolbar = NavigationToolbar2Tk(canvas, root) #add to canvas
canvas._tkcanvas.pack(side = TOP, fill=BOTH, expand=True)
root.mainloop()
Do you have any suggestions? Can I also make the Multi-Cursor (green lines) to move with mplcursors on plots and stick to them too?
I have tried multi-threading from this link, used mouse controlling from this link and getting pixels of first plot points by this question but I couldn't handle the first display event and it needed the mouse to be clicked on both plots separately to show the cursors.
My plots are more complicated, they are signals and I have multiple pages in my application.
The following code does what I wanted But couldn't solve to keep the green Multi-Cursor on the yellow ones! (Exploring yet)
the following code uses MouseEvent and _process_event which clicks on the plot by default, when calling it you should declare when this process happens?, on which plot, in which points!
from matplotlib.widgets import MultiCursor
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import mplcursors
import subprocess
from matplotlib.backend_bases import KeyEvent, MouseEvent
import copy
def _process_event(name, ax, coords, *args):
ax.viewLim # unstale viewLim.
if name == "__mouse_click__":
# So that the dragging callbacks don't go crazy.
_process_event("button_press_event", ax, coords, *args)
_process_event("button_release_event", ax, coords, *args)
return
display_coords = ax.transData.transform(coords)
if name in ["button_press_event", "button_release_event",
"motion_notify_event", "scroll_event"]:
event = MouseEvent(name, ax.figure.canvas, *display_coords, *args)
elif name in ["key_press_event", "key_release_event"]:
event = KeyEvent(name, ax.figure.canvas, *args, *display_coords)
else:
raise ValueError(f"Unknown event name {name!r}")
ax.figure.canvas.callbacks.process(name, event)
root = Tk()
fig = Figure(figsize=(8,6))
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
y = np.sin(x)
z = np.cos(x)
ax1.plot(x, y, label="sin function")
ax1.legend(loc="upper right")
ax2.plot(x, z, label="cos function")
canvas = FigureCanvasTkAgg(fig,root)
c1 = mplcursors.cursor(ax1, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
c2 = mplcursors.cursor(ax2, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
multi = MultiCursor(fig.canvas, (ax1, ax2), color='g', lw=0.5, horizOn=True, vertOn=True)
ax2.legend(loc="upper left")
_process_event("__mouse_click__", ax1, (x[0], y[0]), 1)
sel, = c1.selections
c1.add_selection(copy.copy(sel))
_process_event("__mouse_click__", ax2, (x[0], z[0]), 1)
sel2, = c2.selections
c2.add_selection(copy.copy(sel2))
canvas.draw()
#pack canavs
canvas.get_tk_widget().pack(side = BOTTOM, fill=BOTH, expand=True)
#create Navigation Toolbar
toolbar = NavigationToolbar2Tk(canvas, root) #add to canvas
canvas._tkcanvas.pack(side = TOP, fill=BOTH, expand=True)
root.mainloop()

Toolbar in matplotlib not working in tkinter canvas

I am trying to create a interactive 3d plot that can be rotated, I was able to do this using the following, however I am unable to reset the orientation when I click on the home button. I tried all the answers posted before me none seem to work.
matplolib version - 3.2.2
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
np.random.seed(1968001)
def randrange(n, vmin, vmax):
'''
Helper function to make an array of random numbers having shape (n, )
with each number distributed Uniform(vmin, vmax).
'''
return (vmax - vmin) * np.random.rand(n) + vmin
fig = Figure()
# Unable to have rotation otherwise so defined this function
def func():
ax = fig.add_subplot(111, projection='3d')
n = 100
# For each set of style and range settings, plot n random points in the box
# defined by x in [23, 32], y in [0, 100], z in [zlow, zhigh].
for m, zlow, zhigh in [('o', -50, -25), ('^', -30, -5)]:
xs = randrange(n, 23, 32)
ys = randrange(n, 0, 100)
zs = randrange(n, zlow, zhigh)
ax.scatter(xs, ys, zs, marker=m)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
top = tk.Tk()
top_frame = ttk.Frame(top)
top_frame.pack()
canv = FigureCanvasTkAgg(fig, master=top_frame)
canv.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
func()
canv.draw()
toolbar = NavigationToolbar2Tk(canv, top_frame)
toolbar.update()
top.mainloop()
By home button this is what I mean
click here
Edit 1: I've changed the plt.figure to figure.Figure as pointed out by Henry Yik, but still, it's not working

Categories

Resources