How can I customize the Matplotlib Toolbar in tkinter using "toolmanager"? - python

I'm trying to implement matplotlib toolmanager custom tools from this Tool Manager Example in my tkinter app (which is based partly on this other MPL Example), but I'm running into issues when trying to access fig.canvas.manager.toolmanager.
Here's a pared-down version of my tkinter application
import matplotlib as mpl
import matplotlib.backends.backend_tkagg as mptk
import matplotlib.pytlot as plt
import numpy as np
import tkinter as tk
from matplotlib.backend_tools import ToolBase # for custom tools, as per linked TM example
from tkinter import ttk
mpl.use('TkAgg') # do I need this? What is this doing?
plt.rcParams['toolbar'] = 'toolmanager' # as per the linked TM example
class Root(tk.Tk):
def __init__(self):
super().__init__()
self.title('Plot App')
self.geometry('1920x1080')
self.main_frame = MainFrame(self)
self.main_frame.pack(expand=True, fill=tk.BOTH)
class MainFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.label_frame = ttk.LabelFrame(self, text='Data Viewer')
self.label_frame.pack(expand=True, fill=tk.BOTH)
self.plot() # just plot immediately at init
def plot(self):
# nonsense data to plot
t = np.arange(0, 10, 0.1)
a = np.sin(8 * np.pi * t)
b = np.cos(4 * np.pi * t)
self.fig, self.ax = plt.subplots()
self.ax.plot(t, a, t, b)
self.tk_canvas = mptk.FigureCanvasTkAgg(self.fig, master=self.label_frame)
self.tk_canvas.draw()
self.tk_canvas.get_tk_widget().pack(expand=True, fill=tk.BOTH)
# init custom toolbar
self.toolbar = Toolbar(self.fig, self.label_frame)
self.toolbar.update()
class Toolbar(mptk.NavigationToolbar2Tk): # custom toolbar inherits from NavigationToolbar2Tk
def __init__(self, fig, parent):
super().__init__(fig.canvas, parent)
self.fig = fig
self.tb = self.fig.canvas.manager.toolbar # here is where I run into trouble
self.tm = self.fig.canvas.manager.toolmanager # and likewise, here
if __name__ == '__main__':
app = Root()
app.mainloop()
The line assigning self.tb throws the following exception at run time:
AttributeError: 'NoneType' has no attribute 'toolbar'
I understand that this is because self.fig.canvas.manager is None - what I don't understand is why that's the case when the example does this without issues.
For reference, if I print(fig.canvas.manager.toolmanager) from the Tool Manager Example code, I get (as expected):
<matplotlib.backend_managers.ToolManager object at ~ID~>
It's worth pointing out (though perhaps not surprising) that the assignment to self.tm throws the same exception for much the same reason.
Informational Edit:
A little more digging led me to the method plt.get_current_fig_manager(), which in my case is unfortunately None; I assume this has something to do with using the Tk backend.
What am I doing wrong? How can I use toolmanager effectively with NavigationToolbar2Tk? I'd like to be able to use tm.add_tool() and tm.remove_tool(), but I can't since my toolmanager doesn't exist.
Any help is appreciated much appreciated.

Related

how to make checkbox works fine in notebook, tkinter, matplotlib

I have 3 sheets in a notebook that do the same thing as I show in the code below. (This code works fine here but not in my code)
All sheets have the function checkbox_O and g_checkbox with the same name and all use the global variable lines
When I press the graph button, the graphs are displayed but the checkboxes only work fine on sheet 1.
I did code tests and I realize that, in sheet 2 and 3, the g_checkbox function does not have access to the lines array (I don't know why)
This is what I tried to fix it and it didn't work:
tried to write g_checkbox function as a class method
I tried to place a global lines variable for each sheet
I changed the name of the function checkbox_O in each sheet, so that it is different in each one of them
I need help please to know how to make the checkboxes work in all three sheets.
I don't know what I'm doing wrong and I don't know how to fix it.
Thanks in advance
import numpy as np
import tkinter as tk
from tkinter import ttk
import matplotlib
from matplotlib import pyplot as plt
matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import(FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib import style
from matplotlib.widgets import CheckButtons, Button, TextBox, RadioButtons
x = np.arange(-15, 15, .01)
lines = []
class root(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('750x618')
self.my_book()
def my_book(self):
#I left it out so the code doesn't get too long
class myFrame(ttk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.my_widgets()
def my_widgets(self):
self.f = Figure(figsize=(7, 5.5), dpi=100)
self.plott = self.f.add_subplot()
self.canvas = FigureCanvasTkAgg(self.f, self)
self.canvas.draw()
self.canvas.get_tk_widget().pack()
self.button_ax = self.f.add_axes([0.76, 0.95, 0.11, 0.04])
self.button_graph = Button(self.button_ax, 'Graph', color='darkgrey')
self.button_graph.on_clicked(self.callback_button)
plt.show()
def callback_button(self, event):
self.graph_1()
self.graph_2()
def checkbox_O(self, lines, f):
self.check_ax = f.add_axes([0.76, 0.085, 0.21, 0.16])
labels = [str(line.get_label()) for line in lines]
labels_visual = ['graph1', 'graph2']
visibility = [line.get_visible() for line in lines]
self.check = CheckButtons(self.check_ax, labels_visual, visibility)
def g_checkbox(label):
selec_index = [ind for ind, lab in enumerate(self.check.labels) if label in lab._text]
for index, laabel in enumerate(labels):
if laabel==label:
lines[index].set_visible(not lines[index].get_visible())
if (lines[index].get_visible()):
self.check.rectangles[selec_index[0]].set_fill(True)
else:
self.check.rectangles[selec_index[0]].set_fill(False)
f.canvas.draw()
self.check.on_clicked(g_checkbox)
plt.show()
def graph_1(self):
global lines
for i in range(0, 15):
V = np.sin(i/10*x) + 2*i
l0, = self.plott.plot(x, V, label='graph1')
lines.append(l0)
self.checkbox_O(lines, self.f)
def graph_2(self):
global lines
for i in range(0, 15):
l0, = self.plott.plot([i, i], [0, 10], label='graph2')
lines.append(l0)
self.checkbox_O(lines, self.f)
if __name__ == "__main__":
app = root()
app.mainloop()
PS: By the way, I took the code from stackoverflow and modified it so that it works like this

Python matplotlib tkinter - button doesn't update graph

I am writing a small program, with the intention to update matplotlib graphs periodically throughout. For this I intend to use clear() and redraw the graph. The clear function does work when called from within the method that creates the graph, but it does not work, when called from a button, eventhough the graph is given as a Parameter.
Below is runnable code in it's most basic form to illustrate the problem.
In this case, clicking the "Update" button does nothing. How would I fix that button to clear the graph?
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import numpy as np
class MainWindow(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.add_graph()
def add_graph(self):
fig_sig = plt.figure(figsize=(4,2))
graph = fig_sig.add_subplot(111)
y_values = [0,1,2,3,4,5]
x_values = [1,2,3,4,5,6]
graph.plot(x_values, y_values)
canvas = FigureCanvasTkAgg(fig_sig, master=root)
canvas_widget=canvas.get_tk_widget()
canvas_widget.grid(row = 1, column = 0, columnspan = 3)
canvas.draw()
self.add_widgets(root, graph)
#graph.clear() # Calling graph.clear() here does clear the graph
def add_widgets(self, parent, graph):
update_btn = tk.Button(parent, text = "Update", command = lambda: self.update_graph(graph))
update_btn.grid(row = 8, column = 3)
def update_graph(self, graph):
graph.clear() # calling graph.clear() here does nothing
root = tk.Tk()
oberflaeche = MainWindow(master = root)
oberflaeche.mainloop()
you need to "update" canvas in that case.
define your canvas as: self.canvas = FigureCanvasTkAgg(fig_sig, master=root)
and "update" it:
def update_graph(self, graph):
graph.clear() # calling graph.clear() here does nothing
self.canvas.draw()

How to plot different graphs in different canvas in PyQt4 in a window?

I am trying to code with python3 a GUI that plots 4 different graph in 4 respective layout : speed, height, coordinates and the angle. Right now I am able to draw the figure in each respective layout. However, I have no idea how to plot different function into each graph. I used a method to randomly generate 10 points to plot. When the method is called, it plots the same graph into each 4 canvas.
So my question is **if there is anyway to plot different function respectively to a figure(one plot per graph)?**I am pretty new to python3 and would be grateful for any help provided.
If possible, I would like to avoid using many subplots in a figure since a layout for each figures exist already.
Here is what the current GUI looks like when I call the test method that generates random points, you can see that
the same graph are plotted in each canvas
I will also mention, if it adds any constraints, that this code will eventually be used to plots graph that will update with data coming from another thread.
And here's the code:
from PyQt4 import QtGui
from .flight_dataUI import Ui_Dialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import random
class FlightData(QtGui.QDialog, Ui_Dialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.figure = plt.figure() # FlightData.figure = matplotlib.pyplot.figure()
# Creates a figure widget self.name = FigureCanvas(self.figure)
self.speedGraph = FigureCanvas(self.figure)
self.heightGraph = FigureCanvas(self.figure)
self.mapGraph = FigureCanvas(self.figure)
self.angleGraph = FigureCanvas(self.figure)
# -------------------------------------------------------------
self.speedLayout.addWidget(self.speedGraph) # insert widget "speedGraph" in speedLayout
self.heightLayout.addWidget(self.heightGraph) # insert widget "heightGraph" in heightLayout
self.mapLayout.addWidget(self.mapGraph) # insert widget "mapGraph" in mapLayout
self.angleLayout.addWidget(self.angleGraph) # insert widget "angleGraph" in angleLayout
self.ax = self.figure.add_subplot(111)
self.ax.hold(False)
self.init_widgets()
def init_widgets(self):
self.analyseButton.clicked.connect(self.open_analysedata)
self.draw_plot()
def open_analysedata(self):
self.done(2) # Closes and delete Dialog window et and return the int 2 as a results in main_window.py
def draw_plot(self):
data = [random.random() for i in range(10)]
self.ax.plot(data, '-*')
If embedding do not import pyplot, it's global state will only cause you trouble. You are using the same figure to initialize all 4 FigureCanvas objects. You want to do something like:
from matplotlib.figure import Figure
class FlightData(QtGui.QDialog, Ui_Dialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.figs = {}
self.canvas = {}
self.axs = {}
plot_names = ['speed', 'height', 'map', 'angle']
for pn in plot_names:
fig = Figure()
self.canvas[pn] = FigureCanvas(fig)
ax = fig.add_subplot(1, 1, 1)
self.figs[pn] = fig
self.axs[pn] = ax
# -------------------------------------------------------------
self.speedLayout.addWidget(self.canvas['speed'])
self.heightLayout.addWidget(self.canvas['height'])
self.mapLayout.addWidget(self.canvas['map'])
self.angleLayout.addWidget(self.canvas['angle'])
def draw_plot(self, target, data):
self.axs[target].plot(data, '-*')
self.canvas[target].draw_idle()

Closing the window doesn't kill all processes

I have a very simple programme that displays a simple plot on press of a button. My problem is when I close the application window, the programme keeps running until I kill it from the terminal. Below is my code and my investigation showed the issue is caused by
matplotlib.use('TkAgg')
But I don't know how to fix it! If it helps, I'm running on OSX.
#!/usr/bin/python
from Tkinter import *
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
# ------ End of imports
class Ops:
def show_plot(self):
self.f, self.figarray = plt.subplots(1, sharex=True, sharey=True)
self.figarray.plot((1,2,3),(1,2,3))
plt.tight_layout()
self.canvas = FigureCanvasTkAgg(self.f, master=self.mainFrame)
self.canvas._tkcanvas.config(background='white', borderwidth=0, highlightthickness=0)
self.canvas._tkcanvas.pack(side=TOP, fill=BOTH)
class GUI(Ops):
def __init__(self, master):
self.master = master
self.width = self.master.winfo_screenwidth() # Width of the screen
self.height = self.master.winfo_screenheight() # Height of the screen
self.x = (self.width / 2)
self.y = (self.height / 2)
self.master.geometry("%dx%d+%d+%d" % (self.width, self.height, self.x, self.y))
self.mainFrame = Frame(self.master) # Generate the main container
self.mainFrame.pack()
# ---------- TOP FRAME ----------
self.topFrame = Frame(self.mainFrame)
self.topFrame.pack()
self.browse_button = Button(self.topFrame, text="Plot", command=self.show_plot)
self.browse_button.grid()
class App:
def __init__(self):
self.file_handler = Ops()
self.root = Tk()
self.gui_handler = GUI(self.root)
def run(self):
self.root.mainloop()
Application = App()
Application.run()
You need to call root.quit() to end the Tk.mainloop(). For example, see the answer here.
The solution is simple. Just use
from matplotlib.figure import Figure
instead of
import matplotlib.pyplot as plt
Use root.mainloop outside of a function, that should solve your problems.

Python: Tkinter and pyplot to replot data

I am trying to write a simple program that reads the values of three different widgets and plots a graph of a function depending on what the input values are. I run into the following problems:
1) On the button press I get the error "App_Window instance has no attribute 'refreshFigure'"
2)On exiting ipython (I use ipython-listener in conjunction with a gedit plugin) I get the error "Exception RuntimeError: 'main thread is not in main loop' in del of
I've based this mostly on this thread - Interactive plot based on Tkinter and matplotlib and this thread - How do I refresh a matplotlib plot in a Tkinter window?
#!/usr/bin/python
from Tkinter import Tk, Frame
from Tkinter import *
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#this bit I added just because the NVC13 module is in another directory
import sys
for i in range(len(sys.path)):
if sys.path[i]!='/home/captain-pants/NV_centri':
if i==len(sys.path)-1:
sys.path.append('/home/captain-pants/NV_centri')
continue
else:
break
import multiprocessing as mp
import numpy as np
import matplotlib.pyplot as plt
#This module contains all the code necessary to return a list of values that depend on the input parameters
import NVC13_1lev_advanced_ex_nocoh as C13
from matplotlib.figure import Figure
#this function takes a list of parameters and returns a list of values dependant on those parameters.
def plotfunc(c13,Eg,Bz):
Epam=[]
nv = C13.NVC13(c13,[2580,1423],[Eg,Eg],0,0,Bz,0)
evals = nv.intH()
for i in range(len(evals[0])):
Epam.append(np.real_if_close(evals[0][i]))
return Epam
class App_Window:
def __init__(self, parent):
frame = Tkinter.Frame(parent)
self.Egdata=DoubleVar()
self.c13input=IntVar()
self.Bzdata=DoubleVar()
parent.title("Simple")
Tkinter.Button(text='lulz',command=self.main1).grid(row=3,column=0)
Tkinter.Label(textvariable=self.Egdata).grid(row=5,column=0)
self.c13 = Tkinter.Entry(textvariable=self.c13input)
self.Eg = Tkinter.Scale(orient='horizontal')
self.Bz = Tkinter.Scale(orient='horizontal')
self.c13.grid(row=6,column=0)
self.Bz.grid(row=5,column=0)
self.Eg.grid(row=4,column=0)
Fig = Figure()
FigSubPlot = Fig.add_subplot(111)
self.line, = FigSubPlot.plot(range(10),'bo')
x=[]
y=[]
self.canvas = FigureCanvasTkAgg(Fig, master=parent)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=0,column=0,columnspan=2)
frame.grid(row=0,column=0)
def refreshFigure(self,y):
x=np.arange(len(y))
self.line.set_xdata(x)
self.line.set_ydata(y)
self.canvas.draw()
def main1(self):
self.c13input=int(self.c13.get())
self.Egdata=self.Eg.get()
self.Bzdata=self.Bz.get()
values = plotfunc(self.c13input,self.Egdata,self.Bzdata)
#Just to see whether I actually get the right thing in the input.
print self.c13input
self.refreshFigure(values)
root = Tkinter.Tk()
app = App_Window(root)
root.mainloop()

Categories

Resources