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
Related
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.
I am plotting a simple scatter plot with 3 groups of data, labeled A, B and C.
How do I write the code to allow clicking on legend entry A and highlighting points associated with A, while dimming points associated with labels B and C?
Is it possible to select (by click and drag, or click on multiple legend entries with ctrl key) and highlighting the associated points, while dimming points having other labels (labels not selected on the legend)?
Following the example here https://matplotlib.org/3.1.1/gallery/event_handling/legend_picking.html (which uses plot instead of scatter), I have made some progress, but can't quite get the code to work the way I want it. In the example, they set the picker property for each legend entry via leg.get_lines(), and I tried a similar thing (leg.get_patches()) which gave an empty dict. I can't make further progress, hopefully someone can help. Thanks in advance.
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MainApplication(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.frame = tk.Frame(self.root)
self.frame.grid(row=0, column=0)
self.plot_button = tk.Button(self.frame, text='Plot XY', command=self.xy_plot)
self.plot_button.grid(row=0, column=0)
self.figure = plt.figure(figsize=(5,5))
self.canvas = FigureCanvasTkAgg(self.figure, master=self.frame)
self.canvas.get_tk_widget().grid(row=1, column=0)
self.X = [np.random.rand(5), np.random.rand(5), np.random.rand(5)]
self.Y = [np.random.rand(5), np.random.rand(5), np.random.rand(5)]
self.labels = ['A','B','C']
self.figure.canvas.mpl_connect('pick_event', self.onpick)
def xy_plot(self):
ax = self.figure.add_subplot(111)
self.pts = []
for x, y, grp in zip(self.X, self.Y, self.labels):
self.pts.append(ax.scatter(x, y, label=grp))
leg = ax.legend(self.pts, self.labels, fontsize=12)
leg.set_picker(5) #should be set for individual entries? If so, how?
self.canvas.draw()
def onpick(self, event):
#obviously this function needs to be modified
self.pts[0].set_alpha(0.9)
self.pts[1].set_alpha(0.1
self.canvas.draw()
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root)
root.mainloop()
All the examples that I came across involve looping over leg.get_lines() and setting the set_picker(value) for each element in leg.get_lines(). However, leg.get_lines() is empty for scatter plot. I tried looking at leg.dict['legendHandles'] and I see that it consists of 3 (in my case) collections.PathCollection objects. Do these objects have set_picker() methods? It seems to me that legend for a scatter is very different from legend for a plot. Can someone shed light on this?
By analogy to the example shown in the link in my question, I looped over the legendHandles and set the set_picker(5). This allows clicking on different legend entries and allows me to get the label via event.artist.get_label(). The modification to the onpick function seems like a hack, but it does what I want. Is there a more elegant and proper way to loop over the legendHandles than what I am doing? Any suggestions to improve the "solution" is welcome.
def xy_plot(self):
ax = self.figure.add_axes([0,0,1,1])
self.pts = []
for x, y, grp in zip(self.X, self.Y, self.labels):
self.pts.append(ax.scatter(x, y, label=grp))
self.leg = ax.legend(self.pts, self.labels)
for obj in self.leg.__dict__['legendHandles']:
obj.set_picker(5)
self.canvas.draw()
def onpick(self, event):
groups = {key: val for key, val in zip(self.labels, self.pts)}
label = event.artist.get_label()
for key in groups:
if key == label:
groups[key].set_alpha(1.0)
else:
groups[key].set_alpha(0.2)
self.canvas.draw()
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()
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()
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()