Plot labels are outside the figure on tkinter canvas - python

I would like to fit my plot to size of the figure, which includes labels for the x- and y-axes. The problem is that the labels are pushed outside the figure by the scaling.
Cause i am working with multiple frames alongside each other, it's important that the plot has the predefined size. I tried to make the code as short as possible, to keep only the essential of the code.
In the code there are multiple live plots in tkinter, all with there own frame, but the same class.
import matplotlib
import matplotlib.animation as animation
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import matplotlib as plt
import tkinter as tk
from tkinter import ttk
from math import *
figure_1 = Figure(figsize=(4,2.5), dpi=100)
a1 = figure_1.add_subplot(111)
figure_2 = Figure(figsize=(4,2.5), dpi=100)
a2 = figure_2.add_subplot(111)
x_value_1 =[]
y_value_1 =[]
x_value_2 =[]
y_value_2 =[]
var =tk.Tk()
def plot_1_xy(i):
x_value_1.append(i)
z=sin(pi*x_value_1[i]/9)
y_value_1.append(z)
a1.clear()
a1.plot(x_value_1,y_value_1)
a1.set_xlabel('X Label')
a1.set_ylabel('Y Label')
def plot_2_xy(i):
x_value_2.append(i)
y_value_2. append(500*sin((1/14)*pi*i))
a2.clear()
a2.plot(x_value_2,y_value_2)
a2.set_xlabel('X Label')
a2.set_ylabel('Y Label')
class Question_online():
def __init__(self,master):
self.frame = tk.Frame(master)
plot_frame(self.frame,0)
plot_frame(self.frame,1)
self.frame.pack()
class plot_frame(tk.Frame):
def __init__(self,root,j=0):
self.j = j
self.figure_name = [figure_1,figure_2]
self.frame = tk.Frame(root)
self.canvas = FigureCanvasTkAgg(self.figure_name[j], self.frame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH,
expand=True)
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.frame.pack()
c = Question_online(var)
ani = animation.FuncAnimation(figure_1,plot_1_xy,interval=500)
ani1 = animation.FuncAnimation(figure_2,plot_2_xy,interval=500)
var.mainloop()

You can call Figure's tight_layout() method at each figure update to ensure that your labels are displayed:
def plot_1_xy(i):
x_value_1.append(i)
z=sin(pi*x_value_1[i]/9)
y_value_1.append(z)
a1.clear()
a1.plot(x_value_1,y_value_1)
a1.set_xlabel('X Label')
a1.set_ylabel('Y Label')
figure_1.tight_layout()
def plot_2_xy(i):
x_value_2.append(i)
y_value_2. append(500*sin((1/14)*pi*i))
a2.clear()
a2.plot(x_value_2,y_value_2)
a2.set_xlabel('X Label')
a2.set_ylabel('Y Label')
figure_2.tight_layout()

Related

tkinter figure (with colorbar) shrinks every time it's displayed

Here is a tkinter program, boiled down from a GUI I am working on:
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
class App(tk.Tk):
def __init__(self):
super(App, self).__init__()
self.main_frame = tk.Frame(self,)
self.main_frame.pack(fill=tk.BOTH, expand=1)
self.plot1_button = tk.Button(self.main_frame, text='Plot 1',
command=self.draw_plot1)
self.plot1_button.pack(fill=tk.X,expand=1)
self.plot2_button = tk.Button(self.main_frame, text='Plot 2',
command=self.draw_plot2)
self.plot2_button.pack(fill=tk.X,expand=1)
self.FIG, self.AX = plt.subplots()
self.canvas = FigureCanvasTkAgg(self.FIG, master=self.main_frame)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.toolbar = NavigationToolbar2Tk(self.canvas, self.main_frame)
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def draw_plot1(self):
self.clear_axes()
fig = self.AX.plot(np.random.rand(10),np.random.rand(10), color='red')
self.canvas.draw_idle()
self.toolbar.update()
def draw_plot2(self):
self.clear_axes()
im = self.AX.matshow(np.random.rand(100,100))
self.canvas.draw_idle()
self.toolbar.update()
cb = plt.colorbar(im, ax=self.AX)
def clear_axes(self):
for ax in self.FIG.axes:
ax.clear()
if ax != self.AX:
ax.remove()
root = App()
root.resizable(False, False)
root.mainloop()
The Plot 1 button draws a random line plot, while the Plot 2 button draws a random heatmap with a colorbar. The Plot 1 button can be clicked repeatedly, creating new random line plots as expected. After 10 clicks, the display looks fine:
:
But the Plot 2 button causes the figure to shrink each time it is clicked. After 10 clicks, the graph is uninterpretable:
Additionally, the figure size persists when clicking Plot 1 again:
These are the .png files saved from the application's toolbar, but the same can be seen in the GUI window. I have tried add updates to the GUI/canvas (e.g. self.update(), self.canvas.draw_idle()) at different locations but haven't found anything that affects the issue. I added the clear_axes() function because in the real GUI I have some figures with multiple axes and this removes them, but apparently it does not help here.
I have found that if the color bar is removed, the problem disappears (i.e. comment out cb = plt.colorbar(im, ax=self.AX)), but I would like to have this as part of the figure. Can anyone shed light on what is going on, or can anyone suggest a fix? I'm on matplotlib 3.2.1.
The problem is you are not clearing the colorbar when you clear the axes.
class App(tk.Tk):
def __init__(self):
super(App, self).__init__()
self.main_frame = tk.Frame(self,)
...
self.cb = None
...
def draw_plot2(self):
self.clear_axes()
im = self.AX.matshow(np.random.rand(100,100))
self.canvas.draw_idle()
self.toolbar.update()
self.cb = plt.colorbar(im, ax=self.AX)
def clear_axes(self):
if self.cb:
self.cb.remove()
self.cb = None
for ax in self.FIG.axes:
ax.clear()
if ax != self.AX:
ax.remove()
Also note that you should use matplotlib.figure.Figure instead of pyplot when working with tkinter. See this for the official sample.

How to set the x and y Scale of a matplotlib Plot in a tkinter canvas

I have the following code:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class tkPlot:
def __init__(self,master):
frame = Frame(master)
frame.pack()
canvasSubFrame=Frame(frame)
canvasSubFrame.grid(row=0, column=0)
f = Figure(figsize=(8,8), dpi=100)
subplotA = f.add_subplot(211)
subplotA.set_title("Plot A")
subplotB = f.add_subplot(212)
subplotB.set_title("Plot B")
subplotA.plot([1,2,3,4,5,6],[1,4,9,16,25,36])
subplotB.plot([1,2,3,4,5,6],[1,1/2,1/3,1/4,1/5,1/6])
canvas = FigureCanvasTkAgg(f, master=canvasSubFrame)
canvas.draw()
canvas.get_tk_widget().pack(expand=False)
if __name__ == "__main__":
root = Tk()
app = tkPlot(root)
root.mainloop()
Ideally, i would like to have a textbox or spin control besides every top and bottom of each subplot to set the y axes min and max scales (y limits)
What is the proper way to do this. It should work for any number of subplots.

Using basemap as a figure in a Python GUI

I have snippets of code to generate a basemap, as well as the (very rough) start of a GUI. However, I cannot find that "one-liner" will allow me to display the map as a figure in the GUI. Code snippets are as follows; ideally, I would like a couple of things to happen:
An image of the 'basemap' to relace the plot of sin(2*pi*t)
I would really like for the code to record the (pixel) location on the graphic, if I were to click on the plot shown (the hope is that you could click anywhere on the map, and the script would record the latitude and longitude of where you clicked).
Regarding the 1st step, I've tried things such as setting the figure variable, f, equal to the Basemap; this killed the GUI portion altogether, and simply showed an image of the map in another window.
I've tried to address #2 by trying to implement a couple routines I found on stackexchange, but never got it to work fully.
Basemap:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
## User-chosen datapoint
#lons = [10]; lats = [20];
# Define map projection
m = Basemap(projection='cyl',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c')
m.drawcoastlines()
Rough GUI:
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from Tkinter import *
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from mpl_toolkits.basemap import Basemap
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
root = Tk.Tk()
root.wm_title("Embedding in TK")
f = Figure(figsize=(5, 4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0, 3.0, 0.01)
s = sin(2*pi*t)
a.plot(t, s)
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def on_key_event(event):
print('you pressed %s' % event.key)
key_press_handler(event, canvas, toolbar)
canvas.mpl_connect('key_press_event', on_key_event)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = Tk.Button(master=root, text='Quit', command=_quit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
The question how to include a basemap plot into Tkinter has actually not been answered yet. So the idea is to create an axes ax in a figure and add the Basemap to the axes. This is done with the ax argument,
m = Basemap(..., ax=ax)
The figure is then added to the canvas in the usual way; below is a complete example.
from mpl_toolkits.basemap import Basemap
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot(111)
m = Basemap(projection='cyl',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c', ax=ax)
m.drawcoastlines()
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.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 = Tk.Button(master=root, text='Quit', command=_quit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.
One simple technique is to define an on_click function, show an image, then overwrite that image. The on_click function will still read the mouse click location, despite the image change.
A sample code follows, demonstrating this is as shown:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
def on_click(event):
if event.inaxes is not None:
print event.xdata, event.ydata
else:
print 'Clicked ouside axes bounds but inside plot window'
fig, ax = plt.subplots()
fig.canvas.callbacks.connect('button_press_event', on_click)
plt.show()
#Define map projection
m = Basemap(projection='cyl', llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180, resolution='c')
m.drawcoastlines()

How to replace previous plots in a matplotlib figure with new plots or grid of plots?

I am using matplotlib and create a figure adding a plot. How can I replace the plot by a new one or a new grid of plots?
In my present code I create an axes with the menu dosomething() then add other red lines with the menu dosomethingelse() over the same axes.
Everytime I dosomething(), a new figure is appended bellow the current one, but I actually want to replace the current axes by a new one in the same figure. How can I do that?
import numpy as np
from Tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class Test1:
def __init__(self, windows, data, axes):
self.windows = windows
self.data = data
self.figure = axes.figure
self.axes = axes
self.im = self.axes.plot(data)
def dosomething():
global test1
global fig
fig = Figure(figsize=(12, 4))
axes = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=windows)
canvas.get_tk_widget().pack()
data=np.arange(100)
test1=Test1(windows, data, axes)
def dosomethingelse():
global test1
test1.axes.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
test1.figure.canvas.show()
windows = Tk()
menubar = Menu(windows)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do something", command=dosomething)
filemenu.add_command(label="Do somethingelse", command=dosomethingelse)
menubar.add_cascade(label="Tool", menu=filemenu)
windows.config(menu=menubar)
windows.mainloop()
This doesn't looks like a very standard way of doing tkinter. At least it's not very clear to me what you're trying to achieve. You have the following line in your dosomething:
test1=Test1(windows, data, axes)
which is what is producing your new window every time you run it. Also, there's no need for global variables, when you're inside a class. Just use self.variable = ..., and the variable will be available throughout your class and to objects that you pass the class to.
I haven't tried this, but perhaps something like this:
def dosomething():
try:
self.canvas.get_tk_widget().destroy()
except:
pass
fig = Figure(figsize=(12, 4))
axes = fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(fig, master=windows)
self.canvas.get_tk_widget().pack()
data=np.arange(100) # not sure what this is for
def dosomethingelse():
try:
self.canvas.get_tk_widget().destroy()
except:
pass
fig = Figure(figsize=(12, 4))
fig.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
self.canvas = FigureCanvasTkAgg(fig, master=windows)
self.canvas.get_tk_widget().pack()
I am posting the full code which worked for me after the answer of #DrXorile
import numpy as np
from Tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class Test1:
def __init__(self, data):
self.data = data
def dosomething(self):
try:
self.canvas.get_tk_widget().destroy()
except:
pass
self.figure = Figure(figsize=(12, 4))
self.axes = self.figure.add_subplot(111)
self.im = self.axes.plot(data)
self.canvas = FigureCanvasTkAgg(self.figure, master=windows)
self.canvas.get_tk_widget().pack()
def dosomethingelse(self):
self.axes.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
self.figure.canvas.show()
data=np.arange(100) # data to plot
test1=Test1(data)
windows = Tk()
menubar = Menu(windows)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do something", command=test1.dosomething)
filemenu.add_command(label="Do somethingelse", command=test1.dosomethingelse)
menubar.add_cascade(label="Tool", menu=filemenu)
windows.config(menu=menubar)
windows.mainloop()

applying the matplotlib draw() freezes window solution to special case

Being new to python, I've come upon the matplotlib draw() freezes window problem myself and found the solution on this site:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
import numpy as np
import sys
import Tkinter as tk
import time
def function1(fig, ax):
ax.cla()
color_grade_classes = ['#80FF00','#FFFF00','#FF8000', '#FF0000']
varsi = random.randint(1, 100)
for colors, rows in zip(color_grade_classes, [3,2,1,0] ):
indexs = np.arange(5)
heights = [varsi,varsi/2,varsi/3,0,0]
ax.bar(indexs, heights, zs = rows, zdir='y', color=colors, alpha=0.8)
return fig
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, projection='3d')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.fig = function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
My problem is incorporating the following plotting code into it. It's not quite the same as the example given. Not sure how to split this up between the function definition and the class declaration. Can anyone help me on this?
t0 = time.time()
while time.time() - t0 <= 10:
data = np.random.random((32, 32))
plt.clf()
im = plt.imshow(data,cmap=cm.gist_gray, interpolation='none')
plt.ion()
cbar = plt.colorbar(im)
cbar.update_normal(im)
cbar.set_clim(0, np.amax(data))
plt.draw()
time.sleep(0.5)
plt.show(block=True)
this seems to work.
Basically the __init__ part initialises the plot and draws the first "frame". Then the function self.update_clockis called every 1000ms, and that function calls function1() which generates new data and redraws the plot.
I moved things around a bit because of the colorbar in your example, but the idea remains the same.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import random
import numpy as np
import sys
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
data = np.random.random((32, 32))
im = self.ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar = self.fig.colorbar(im)
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
self.fig = self.function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = self.function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
def function1(self, fig, ax):
ax.cla()
data = np.random.random((32, 32))
im = ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
return fig
app=App()

Categories

Resources