0

I want to make two animations working simultanously.

Here is my code:

import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import tkinter as tk

f = plt.figure(figsize=(6, 2), dpi=100)
f1 = plt.figure(figsize=(4, 2), dpi=100)

def animate(i):
    a = f.add_subplot(111)
    x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    y = [3, 5, 2, 7, 3, 5, 4, 6, 2, 2]
    a.plot(x, y)

def animate1(j):
    a1 = f1.add_subplot(111)
    x1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    y1 = [6, 1, 4, 9, 4, 2, 5, 1, 2, 4]
    a1.plot(x1, y1)

class Figure(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.config(background='blue')

        canvas = FigureCanvasTkAgg(f, self)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

class Figure1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.config(background='blue')

        canvas = FigureCanvasTkAgg(f1, self)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)


class Calculator(tk.Tk):
        def __init__(self, *args, **kwargs):
            tk.Tk.__init__(self, *args, *kwargs)
            tk.Tk.wm_title(self, 'Beam calculator')
            container = tk.Frame(self)
            container.pack(side='top', fill='both', expand=True)
            bfig = Figure(container, controller=self)
            bfig.grid(row=0, column=0, sticky="nsew")
            bfig1 = Figure(container, controller=self)
            bfig1.grid(row=1, column=0, sticky="nsew")


if __name__ == '__main__':

    app = Calculator()
    app.geometry('1280x720')
    ani = animation.FuncAnimation(f, animate, interval=1000)
    ani1 = animation.FuncAnimation(f1, animate1, interval=1000)
    app.mainloop()

But only second figure appears.

I found following topic:

Animate two or more figures simultaneously with matplotlib

but I don't know how to make it properly.

1 Answers1

0

There are 3 little things which are preventing your code from working:

  1. 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)
    
  2. Change bfig1 = Figure(container, controller=self) to bfig1 = Figure1(container, controller=self). Otherwise, a canvas is never attached to figure f1.

  3. *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()
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677