how to close a Figure when using tk backend - python

So I am saving a lot of plots.
Olde code:
import matplotlib.pyplot as plt
for args in lots_of_things_to_make:
fig = plt.figure()
do_the_fancy_graphing(fig, *args)
fig.savefig(out_path)
plt.close()
other parts of my code are using Tkinter so I can not use pyplot.
New code:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
for args in lots_of_things_to_make:
fig = Figure()
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=root)
fig.savefig(out_path)
this leads to _tkinter.TclError: not enough free memory for image buffer
How can I close a Figure when using tk backend?

FigureCanvasTkAgg does not have a destroy method. So I tried:
for args in lots_of_things_to_make:
fig = Figure()
frame = Frame(root)
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=frame)
fig.savefig(out_path)
frame.destroy()
but no luck, turns out FigureCanvasTkAgg.__init__ binds to the toplevel it is placed in, so:
for args in lots_of_things_to_make:
fig = Figure()
top = Toplevel(root)
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=top)
fig.savefig(out_path)
top.destroy()
Seems to be working for me.

Related

Unable to embed networkx graph using matplotlib in customtk

I'm trying to embed a networkx graph in CustomTk, but the graph doesn't appear. When I use plt.show() I can see the graph in matplotlib pop-up menu, but not in GUI.
import tkinter
import customtkinter
import networkx as nx
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
root = customtkinter.CTk()
root.title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
G = nx.random_geometric_graph(20, 0.125, seed=896803)
nx.draw_networkx(G, pos=nx.circular_layout(G), node_size=4000, with_labels=True, font_size=20)
plt.style.use('ggplot')
fig = Figure(figsize=(10, 10), dpi=100)
ax = fig.add_subplot(111)
ax = plt.gca()
ax.margins(0.11)
plt.tight_layout()
plt.axis("off")
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
plt.show()
def on_key_press(event):
print("you pressed {}".format(event.key))
key_press_handler(event, canvas, toolbar)
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = customtkinter.CTkButton(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
root.mainloop()
# If you put root.destroy() here, it will cause an error if the window is
# closed with the window manager.
plt.show() image:
figure on canvas:
I'm not sure what's the issue when drawing on the canvas.
Thanks in advance
Solution: https://github.com/TomSchimansky/CustomTkinter/issues/971#issuecomment-1371489896
import tkinter
import customtkinter
import networkx as nx
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
root = customtkinter.CTk()
root.title("Embedding in Tk")
#call fig ax exactly once
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot(111)
ax.margins(0.11)
#call the graphing module
G = nx.random_geometric_graph(20, 0.125, seed=896803)
#draw the graph, note the ax=ax at the end, this is the critical part, matplotlib needs to know onto which axis the graph is drawn
#otherwise it will just draw it in the spyder backend, without a care for your other elements
nx.draw(G, pos=nx.circular_layout(G), node_size=4000, with_labels=True, font_size=20,ax=ax)
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = customtkinter.CTkButton(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
root.mainloop()
# If you put root.destroy() here, it will cause an error if the window is
# closed with the window manager.

How to realise matplotlib line picker by using opencv?

I would like to pick lines that have been drawn on an image by using cv2.line and do something with them. To realise that I had a look on matplotlibs picker and even found a good example here. As I am going to use tkinter for GUI I added it to my MWE.
Code from example which works fine:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
root = Tk.Tk()
root.iconify()
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots()
for i in range(1, 10):
ax.plot(x, i * x + x, picker=5)
canvas = FigureCanvasTkAgg(fig, master=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)
def on_pick(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', on_pick)
root.mainloop()
Now my code using OpenCV which does not work:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import cv2
root = Tk.Tk()
root.iconify()
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255)
cv2.line(img,(10,10),(60,90),(100,149,237),2)
fig = Figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.plot()
ax.imshow(img)
canvas = FigureCanvasTkAgg(fig, master=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)
def onpick1(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', onpick1)
root.mainloop()
In both examples onpick1 should set the lines visibility on or off by a single mouse click but it doesn't. My assumption is that it has something to do with the way it is plotted or how I draw the lines (ax.plt vs cv2.line). I would be very happy about any help. Thanks!

Plot graph with pyplot using input from tkinter spinbox

I am working in a project which needs to plot a graph dynamically as the inputs in a tkinter spinbox is changed.
I have a sample code:
from tkinter import *
from tkinter import font
from tkinter.font import Font
from tkinter import messagebox
print("'Tkinter' module is found as tkinter.")
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
print("Importing matplotlib from libraries.")
master = Tk()
def ok(x_val=1000,y_val=20):
fig = Figure(figsize=(5,5),dpi=70)
ax = fig.subplots()
ax.set_title("Right Ear")
ax.set_ylabel("db HL")
ax.set_xlabel("Frequency")
ax.set_xlim(100,9000)
ax.set_ylim(130,-10)
ax.set_facecolor("#ffd2d2")
x = [125,250,500,1000,2000,4000,8000]
ticks = [125,250,500,"1K","2K","4K","8K"]
xm = [750,1500,3000,6000]
ax.set_xscale('log', basex=2)
ax.set_xticks(x)
ax.set_xticks(xm, minor=True)
ax.set_xticklabels(ticks)
ax.set_xticklabels([""]*len(xm), minor=True)
ax.yaxis.set_ticks([120,110,100,90,80,70,60,50,40,30,20,10,0,-10])
ax.plot([x_val],[y_val],'r+',markersize=15.0,mew=2)
ax.grid(color="grey")
ax.grid(axis="x", which='minor',color="grey", linestyle="--")
canvas = FigureCanvasTkAgg(fig, master=master)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=2,columnspan=3,rowspan=15)
def action():
print(spin.get())
canvas.draw()
ok(spin.get(),10)
spin = Spinbox(master, from_=125,to=8000,command=action)
spin.grid(column=5,row=2)
ok()
This code does not change the plot, I cannot understand how to change it, to be precise, how to use canvas.draw() here to do the work. The spinbox has value range from 125 to 8000, I could not figure out how to take the value of the spinbox every time it changes (can use command= but how to implement) and feed it to the x axis of ax.plot() and plot dynamically. As the value of spinbox changes the plot also changes to the new position and removes the previous plot from the previous position.
You need to make the variables you need available. A usual approach is to use a class and make those class variables. Those can then be accessed from within the class (self) or outside as attributes.
from Tkinter import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class PlotClass():
def __init__(self):
fig = Figure(figsize=(5,5),dpi=70)
ax = fig.subplots()
ax.set_title("Right Ear")
ax.set_ylabel("db HL")
ax.set_xlabel("Frequency")
ax.set_xlim(100,9000)
ax.set_ylim(130,-10)
ax.set_facecolor("#ffd2d2")
x = [125,250,500,1000,2000,4000,8000]
ticks = [125,250,500,"1K","2K","4K","8K"]
xm = [750,1500,3000,6000]
ax.set_xscale('log', basex=2)
ax.set_xticks(x)
ax.set_xticks(xm, minor=True)
ax.set_xticklabels(ticks)
ax.set_xticklabels([""]*len(xm), minor=True)
ax.yaxis.set_ticks([120,110,100,90,80,70,60,50,40,30,20,10,0,-10])
self.line, = ax.plot([],[],'r+',markersize=15.0,mew=2)
ax.grid(color="grey")
ax.grid(axis="x", which='minor',color="grey", linestyle="--")
self.canvas = canvas = FigureCanvasTkAgg(fig, master=master)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=2,columnspan=3,rowspan=15)
self.spin = Spinbox(master, from_=125,to=8000,command=self.action)
self.spin.grid(column=5,row=2)
def ok(self, x=1000,y=20):
self.line.set_data([x],[y])
self.canvas.draw_idle()
def action(self):
self.ok(float(self.spin.get()),10)
master = Tk()
plotter = PlotClass()
plotter.ok(125,10)
master.mainloop()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.

matplotlib axis redraw on zoom

I have a simple graph with a Navigation Tool Bar. When I zoom or pan, the graph is correctly updated, but the axis labels get messed up. Its as if it does not clear the old text out before drawing the new text. So you see the new text written over the old. If I resize the window, it seems to do a full re-draw and fixes the labels. Here is my simple example:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
from Tix import *
from Tkconstants import *
root = Tk()
f = Figure(figsize=(12,5), dpi=100, frameon=False)
s = f.add_subplot(111, title="test")
x = [0,1,2,3,4,5]
y = [5,2,6,7,3,6]
s.plot(x,y,label="Test")
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
NavigationToolbar2TkAgg(canvas, root)
root.mainloop()
Here is a before:
Here is after panning:
As suggested by Oblivion:
Removing the frameon=False option from f = Figure(figsize=(12,5), dpi=100, frameon=False) solved the issue.

animated NetworkX Graph in TkCanvas: background color

For a demonstration of a Graph algorithm i need to draw a networkx graph to a Tkinter Canvas and be able to modify that graph (and the plot) at runtime.
I have pieced together the following code (I hope it is the minimal code leading to my problem, but I'm new to this so I'm not sure):
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
root.wm_protocol('WM_DELETE_WINDOW', root.quit())
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
nx.draw_networkx(G,pos=nx.spring_layout(G),ax=a)
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw(G, pos=nx.circular_layout(G), ax=a)
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()
My problem now is this:
The first display of the graph is like I want it (backgroundcolor-wise), but after you first click 'Next' the backgroundcolor of the graph changes to white. I have tried changing the background color of the figure and the canvas.
I don't even know what brings that change about, I think it is simply drawing to the same canvas twice.
How can I modify the code to have the graph always have the same background color?
On an unrelated note: the root.quit() I added does not help in ending the application properly. This might be stupid on my side, but what did go wrong here?
I think you are very close. If you use nx.draw_networkx() in your event loop then it works (turn off the axis there too).
Here is your example with those modifications and also with a single layout computed at the beginning that is reused in the loop:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
# Quit when the window is done
root.wm_protocol('WM_DELETE_WINDOW', root.quit)
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
pos=nx.circular_layout(G)
nx.draw_networkx(G,pos=pos,ax=a)
xlim=a.get_xlim()
ylim=a.get_ylim()
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw_networkx(G, pos, ax=a)
a.set_xlim(xlim)
a.set_ylim(ylim)
plt.axis('off')
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()

Categories