There are 3 little things which are preventing your code from working:
When using grid
, you need to assign a positive weight to at least one row and one column:
class Calculator(tk.Tk):
def __init__(self, *args, **kwargs):
...
container.grid_columnconfigure(0, weight=1)
# give the rows equal weight so they are allotted equal size
container.grid_rowconfigure(0, weight=1)
container.grid_rowconfigure(1, weight=1)
Change bfig1 = Figure(container, controller=self)
to bfig1 = Figure1(container, controller=self)
. Otherwise, a canvas is never attached to figure f1
.
*kwargs
should be **kwargs
in the line tk.Tk.__init__(self, *args, *kwargs)
.
See this post for more on what *args
and **kwargs
mean.
These are the minimal changes necessary to get the code running.
There are a number of other changes you could use to improve the code.
One big one is that a = f.add_subplot(111)
should not be placed inside of the animate
function. This creates an Axes object, a
, inside the Figure, f
. Usually there is one Axes and one Figure per plot. (See matplotlib's hierarchy of objects.) Since animate
gets called once for each frame of the animation, you don't want to be creating a new Axes for each frame. The code below suggests how to define a
once, globally.
Another major programming principle goes by the acronym DRY: "Don't repeat youself".
While copying code may be easy at first, in the long run, it makes maintaining
and modifying code harder. Suppose, for instance, you decide you want to add
some code to the Figure
class. Since you have two almost identical Figure
classes, you may have to add the same code to both Figure
and Figure1
. And
if you later find there is a bug in that code, you'd have to fix the bug in both
places too. Your productivity is cut in half because every edit needs to be done
twice. And if you forget to do it one place, then another bug is introduced.
You can make your code DRYer by generalizing animate
and Figure
so that you
can remove animate1
and Figure1
. All you need to do is add an extra argument
to the functions (e.g. line
and f
) to generalize the code and allow for code
reuse. (See the code below to see what I mean.)
It is almost always a bad idea to use numbered variable names.
A list
or dict
should usually be used instead of numbered variables.
Since numbered variable names are often used in the same way,
(i.e. do this to x1
, do the same thing to x2
, do the same thing to x3
, etc...)
using numbered variables leads to code repetition which goes against the DRY principle.
If instead you use a list to collect all the number variables in one object, then you can handle them all at once with a loop:
for x in xs:
do_something(x)
A performance tip: Calling plt.plot
thousands of times as might occur in an animation can make your program noticeably sluggish. Each call generates new Line2D
objects which need to be re-rendered with each frame of the animation. For better performance, reuse the same Line2D
object over and over again, and merely change the ydata inside the Line2D
object to cause matplotlib to render a different line:
line.set_ydata(y)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import matplotlib.figure as mplfig
import tkinter as tk
def animate(i, line):
y = np.random.randint(10, size=len(x))
line.set_ydata(y)
return [line]
class Figure(tk.Frame):
def __init__(self, parent, controller, f):
tk.Frame.__init__(self, parent)
self.controller = controller
canvas = FigureCanvasTkAgg(f, self)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
canvas.draw()
class Calculator(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title('Beam calculator')
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=True)
for i, f in enumerate(figs):
bfig = Figure(container, controller=self, f=f)
bfig.grid(row=i, column=0, sticky="nsew")
# give the rows equal weight so they are allotted equal size
container.grid_rowconfigure(i, weight=1)
# you need to give at least one row and one column a positive weight
# https://stackoverflow.com/a/36507919/190597 (Bryan Oakley)
container.grid_columnconfigure(0, weight=1)
if __name__ == '__main__':
figs = [mplfig.Figure(figsize=(6, 2), dpi=100),
mplfig.Figure(figsize=(4, 2), dpi=100)]
axs = [f.add_subplot(111) for f in figs]
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ys = [[3, 5, 2, 7, 3, 5, 4, 6, 2, 2],
[6, 1, 4, 9, 4, 2, 5, 1, 2, 4]]
lines = [ax.plot(x, ys[i])[0] for i, ax in enumerate(axs)]
for ax in axs:
ax.set_ylim([0, 10])
app = Calculator()
app.geometry('1280x720')
anis = [animation.FuncAnimation(f, animate, interval=1000, fargs=[line])
for f, line in zip(figs,lines)]
app.mainloop()