Unable to call canvas.show() - python

Error:
/usr/bin/python3.5 /root/PycharmProjects/Capstone2/main.py
Traceback (most recent call last):
File "/root/PycharmProjects/Capstone2/main.py", line 62, in
canvas.show()
File "/usr/lib/python3/dist-packages/matplotlib/backends/backend_tkagg.py", line 353, in draw
self._master.update_idletasks()
AttributeError: 'NoneType' object has no attribute 'update_idletasks'
Process finished with exit code 1
Relevant Code:
# GUI imports
from tkinter import *
# Graph imports
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
# Time imports
import time
def animate(i):
a.clear()
a.plot(data)
data = []
# GUI Set-Up
ROOT = Tk()
ROOT.attributes("-fullscreen", True)
matplotlib.use("TkAgg")
style.use('ggplot')
f = Frame(ROOT)
fig = Figure(figsize=(12, 7), dpi=100)
a = fig.add_subplot(221)
a.set_title("Data")
canvas = FigureCanvasTkAgg(fig)
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
canvas.show()
Frame.pack(f)
LOOP_ACTIVE = True
ROOT.config(cursor="none")
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
for x in range(0, 10):
data.append(x)
ROOT.update()
time.sleep(1)
My Understanding:
Every second one more data point should be added. Every second the plot should update to reflect this.
My Thoughts:
From some google searches hint that AttributeError pops up because I am trying to call canvas.show and canvas is nothing but I think my line "canvas = FigureCanvasTkAgg(fig)" is correct and earlier lines like "canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)" do not cause errors
If I omit "canvas.show()" then the error occurs later "Exception in Tkinter callback "
If I modify my code so canvas = FigureCanvasTkAgg(fig, master=ROOT) and match some examples I've found, no errors occur but the animation never does anything. I couldn't find any documentation on FigureCanvasTkAgg, only examples so I'm not too sure what I'm doing by modifying this line
Full code can be seen here (warning: it's pretty gross): https://github.com/AdamVen/Capstone/blob/fasterGraph/main.py
The code I've based mine on is here: https://pythonprogramming.net/how-to-embed-matplotlib-graph-tkinter-gui/
I'm not sure where I'm going wrong. Any advice would be appreciated

Some parts of the code are unclear to me: The use of canvas.show() (which shouldn't even exist) and the use of plt.show() (which doesn't makes sense, given that you haven't created any pyplot figure at all)
Given that canvas = FigureCanvasTkAgg(fig, master=ROOT) is used in most examples, I wouldn't know why not to use it.
The loop at the end of the file doesn't seem to be useful. Instead you have the animation loop to change your data.
Finally, you forgot to actually start the program, i.e. ROOT.mainloop().
from Tkinter import *
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
# Data simulation imports
import time
def animate(i):
a.clear()
data.append(i)
a.plot(data)
data = []
# GUI Set-Up
ROOT = Tk()
f = Frame(ROOT)
plt.style.use('ggplot')
fig = Figure(figsize=(12, 7), dpi=100)
a = fig.add_subplot(221)
a.set_title("Data")
canvas = FigureCanvasTkAgg(fig, master=ROOT)
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
Frame.pack(f)
ani = animation.FuncAnimation(fig, animate, interval=1000)
ROOT.mainloop()

Related

Python live scatterplot in WSL with background image

I have a ROS node that subscribes to a certain topic 'first' and this returns (x,y) values, which I would like to plot on a graph with a background image. I want to be able to show only the last tuple.
I am running this python script in WSL-Ubuntu-18.04 and have installed VcXsrv in Windows to visualize the graph.
This is the code I am using but I don't know how to plot only the last (x,y) output. At the moment, all the values are plotted and after a while the plotting slows terribly down because of all the points (I guess).
from Tkinter import Canvas
import Tkinter as Tk
import rospy
from std_msgs.msg import Int32MultiArray, String
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from time import sleep
from threading import Thread
import numpy as np
from PIL import ImageTk, Image
def callback(data):
received = data.data.split(',')
x = int(float(received[0]))
y = int(float(received[1]))
plt.scatter(x,y)
def listener():
rospy.Subscriber('first', String, callback)
rospy.spin()
def animate(i):
pass
def visual():
root = Tk.Tk()
label = Tk.Label(root, text="Realtime plot ")
root.geometry("750x720")
img = plt.imread("back.jpg")
fig, ax = plt.subplots()
#mng = plt.get_current_fig_manager()
#mng.full_screen_toggle()
ax.imshow(img,extent=[0, 17947, -200, 5330], aspect='equal')
fig.canvas.draw()
plt.show()
plotcanvas = FigureCanvasTkAgg(fig, root)
plotcanvas.get_tk_widget().grid(column=0, row=0)
ani = FuncAnimation(fig, animate, interval=100, blit=False)
Tk.mainloop()
t1 = Thread(target=visual)
t1.start()
listener()
t1.join()
I'm also struggling with scaling the image (hence the whole graph) to full screen. I've tried something but only the frame gets bigger

How to import a matplotlib 3D animation in a tkinter frame in python?

I'm trying to import a matplotlib 3D animation inside a Tkinter window but I got stuck on an error. My Tkinter window has many frames and I try to plot the animation on one of this frame. Finally, I create all elements inside a class init function. So here is a simplified version of my code:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as anim
import random
class UI:
def __init__(self):
self.back = Frame(master=self.window, width=1200, height=800, bg='white')
self.rightBack = Frame(master=self.back, width=300, height=800)
self.rightBack.pack(side='right',padx=2,pady=2)
self.fig = Figure()
self.axes = Axes3D(self.fig)
self.ani = anim.FuncAnimation(self.fig, self.updateDisplay, interval=100, blit=False) # Creating animation
self.canvas = FigureCanvasTkAgg(self.fig, master=self.displayBack)
self.canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=True)
# Starting application
self.window.mainloop()
def updateDisplay(self,num):
self.axes.clear()
self.axes.plot([0,1,random.random()],[0,1,random.random()],[0,1,random.random()],'o',color='k',markersize='5')
But when I run the following in python
self.ani = anim.FuncAnimation(self.fig, self.updateDisplay, interval=100, blit=False)
I get this error
File "matplotlib/animation.py", line 1703, in __init__
TimedAnimation.__init__(self, fig, **kwargs)
File "matplotlib/animation.py", line 1465, in __init__
event_source = fig.canvas.new_timer()
AttributeError: 'NoneType' object has no attribute 'new_timer'
So I think I understood the error as animation needs a timer but I don't know how to fix it. Any help? Thank's in advance
The animation needs to reside in a figure that has a canvas. In your code you define the canvas only after the animation. I suppose it would work as expected if you reverse the order of the animation and the canvas.
# First define the canvas
self.canvas = FigureCanvasTkAgg(self.fig, ...)
# only then define the animation
self.ani = anim.FuncAnimation(self.fig, ...)

Xtick label Rotation resets after FigureCanvasTkAgg.draw()

I'm creating a GUI in Tkinter that has an embedded matplotlib graph. I change the contents of the graph based on a couple of Radiobuttons. These Radiobuttons are linked to a function which redefines the data in the graph, and ends with a call to self.canvas.draw(). I used Unable to update Tkinter matplotlib graph with buttons and custom data substantially, and I got it to work perfectly: the graph is updated with the desired new data.
The problem I run into is that (I'm using dates for the x-tick labels) after the redraw function is called, the x-tick labels get rotated back to horizontal, causing them to overlap. Below is a simplified form of my application, which has no dependencies so can be run directly.
import tkinter
from tkinter import *
from tkinter.ttk import *
import csv
import datetime as dt
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class MainApp:
def __init__(self,parent):
self.RightFrame = LabelFrame(parent,text='RightFrame')
self.RightFrame.pack(side='right')
self.plot_fig=plt.figure(figsize=(4,4),dpi=100)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.autofmt_xdate(rotation=30) ### Sets tick rotation Initially
self.ax.fmt_xdata=mdates.DateFormatter('%Y-%m-%d')
self.ax.hold(False)
self.canvas = FigureCanvasTkAgg(self.fig,master=self.RightFrame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=TOP)
self.plot_radio_frame = LabelFrame(self.RightFrame)
self.plot_radio_frame.pack(side=BOTTOM)
self.plot_time_radio_var = IntVar()
self.plot_time_radio_var.set(7)
modes = [('1 Week',3),('1 Month',4),('1 Year',5),('5 Years',6),('All',0)]
for text,value in modes:
b= Radiobutton(self.plot_radio_frame,text=text,variable=self.plot_time_radio_var,value=value)
b.pack(side=LEFT)
self.plot_time_radio_var.trace('w',self.plotting_function)
def plotting_function(self, varname, elementname, mode):
plot_time = self.plot_time_radio_var.get()
try:
data_hist = [['2016-12-16',116.4700],
['2016-12-19',115.8000],
['2016-12-20',116.7400],
['2016-12-21',116.8000],
['2016-12-22',116.3500],
['2016-12-23',115.5900],
['2016-12-27',116.5200],
['2016-12-28',117.5200],
['2016-12-29',116.4500],
['2016-12-30',116.6500]
]
days = int(plot_time)
spacing = 1
dates = [line[0] for line in data_hist[-days::spacing]]
new_dates = [dt.datetime.strptime(d,'%Y-%m-%d').date() for d in dates]
Y_DATA = [line[1] for line in data_hist[-days::spacing]]
#print(adj_close)
self.curve = self.ax.plot(new_dates,Y_DATA)
self.canvas.draw()
except Exception as e:
print(str(e))
root = Tk()
MainApp(root)
root.mainloop()

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()

Close/Unpack Object "matplotlib.backends.backend_tkagg.FigureCanvasTkAgg"

This is code that takes a figure and displays it on a Tkinter window. How do I unpack/delete "canvas" from the GUI?
from Tkinter import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def plot(x, y):
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax1.plot(x,y)
return fig
plt = plot(1,1)
root = Tk()
canvas = FigureCanvasTkAgg(plt, master=root)
canvas.get_tk_widget().pack()
If you'd like to remove the plot and free up the parent frame/window, call canvas.get_tk_widget().destroy().
For example:
import Tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
def main():
fig = plot(range(10), range(10))
root = tk.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack()
root.bind_all('<Button-1>', lambda event: remove_plot(canvas))
root.mainloop()
def plot(x, y):
fig = Figure()
ax1 = fig.add_subplot(1,1,1)
ax1.plot(x,y)
return fig
def remove_plot(canvas):
canvas.get_tk_widget().destroy()
main()
This only destroys the figure's tk widget. The figure still exists and could be added again, assuming it hasn't gone out of scope and been garbage collected. (Also, the figure will be garbage collected just like any other object as we're not using the pyplot state machine.)

Categories

Resources