alejandroautalan / pygubu

A simple GUI builder for the python tkinter module
MIT License
2.02k stars 212 forks source link

How to put animated graph into a canvas? #220

Closed Doid-Reyewliz closed 3 years ago

Doid-Reyewliz commented 3 years ago

Hello. I'm trying to display an animated graph into a canvas and also trying to put this into new frame in application. I want this graph will displayed in application

from math import sin, cos, radians from matplotlib import pyplot as plt

class Cannon:

def __init__(self, x0, y0, v, angle):

    self.x = x0
    self.y = y0

    self.vx = v*cos(radians(angle))
    self.vy = v*sin(radians(angle))

    self.ax = 0
    self.ay = -9.8

    self.time = 0

    self.xarr = [self.x]
    self.yarr = [self.y]

def updateVx(self, dt):
    self.vx = self.vx + self.ax*dt
    return self.vx
def updateVy(self, dt):
    self.vy = self.vy + self.ay*dt
    return self.vy

def updateX(self, dt):
    self.x = self.x + 0.5*(self.vx + self.updateVx(dt))*dt
    return self.x
def updateY(self, dt):
    self.y = self.y + 0.5*(self.vy + self.updateVy(dt))*dt
    return self.y

def step(self, dt):
    self.xarr.append(self.updateX(dt))
    self.yarr.append(self.updateY(dt))
    self.time = self.time + dt

def makeShoot(x0, y0, velocity, angle):

cannon = Cannon(x0, y0, velocity, angle)
dt = 0.05 # time step
t = 0
cannon.step(dt)

while cannon.y >= 0:
    cannon.step(dt)
    t = t + dt

return (cannon.xarr, cannon.yarr)

def main(): x0 = 0 y0 = 60 velocity = 25

x45, y45 = makeShoot(x0, y0, velocity, 45)
x30, y30 = makeShoot(x0, y0, velocity, 30)
x60, y60 = makeShoot(x0, y0, velocity, 60)

plt.plot(x45, y45, 'bo-', x30, y30, 'ro-', x60, y60, 'ko-', [0, 12], [0, 0], 'k-')
plt.legend(['45 deg shoot', '30 deg shoot', '60 deg shoot'])
plt.xlabel('X coordinate (m)')
plt.ylabel('Y coordinate (m)')
plt.show()

if name == 'main': main()

alejandroautalan commented 3 years ago

Hi Doid-Reyewliz, thanks for trying pygubu.

I think the question refers to how to use pygubu, tkinter and matplotlib together.

I don't use the matplotlib library much but I had a little time to play with your code and this is what came up. It may serve as a starting point for you (You should investigate how to make better animations with matplotlib).

Greetings. Alejandro A.

File: issue220.py

import os
import tkinter as tk
import pygubu

import matplotlib
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

from math import sin, cos, radians

class Cannon:
    def __init__(self, x0, y0, v, angle):

        self.x = x0
        self.y = y0

        self.vx = v*cos(radians(angle))
        self.vy = v*sin(radians(angle))

        self.ax = 0
        self.ay = -9.8

        self.time = 0

        self.xarr = [self.x]
        self.yarr = [self.y]

    def updateVx(self, dt):
        self.vx = self.vx + self.ax*dt
        return self.vx

    def updateVy(self, dt):
        self.vy = self.vy + self.ay*dt
        return self.vy

    def updateX(self, dt):
        self.x = self.x + 0.5*(self.vx + self.updateVx(dt))*dt
        return self.x

    def updateY(self, dt):
        self.y = self.y + 0.5*(self.vy + self.updateVy(dt))*dt
        return self.y

    def step(self, dt):
        self.xarr.append(self.updateX(dt))
        self.yarr.append(self.updateY(dt))
        self.time = self.time + dt

def makeShoot(x0, y0, velocity, angle):

    cannon = Cannon(x0, y0, velocity, angle)
    dt = 0.05 # time step
    t = 0
    cannon.step(dt)

    while cannon.y >= 0:
        cannon.step(dt)
        t = t + dt

    return (cannon.xarr, cannon.yarr)

PROJECT_PATH = os.path.dirname(__file__)
PROJECT_UI = os.path.join(PROJECT_PATH, "issue220.ui")

class Issue220App:
    def __init__(self):
        self.builder = builder = pygubu.Builder()
        builder.add_resource_path(PROJECT_PATH)
        builder.add_from_file(PROJECT_UI)
        self.mainwindow = builder.get_object('mainwindow')

        # Container for the matplotlib canvas and toolbar classes
        fcontainer = builder.get_object('fplot')

        # Setup matplotlib canvas
        self.figure = fig = Figure(figsize=(5, 4), dpi=100)
        self.canvas = canvas = FigureCanvasTkAgg(fig, master=fcontainer)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        # Setup matplotlib toolbar (optional)
        #self.toolbar = NavigationToolbar2Tk(canvas, fcontainer, pack_toolbar=True)
        #self.toolbar.update()

        builder.connect_callbacks(self)

    def on_draw_click(self):
        self.figure.clear()

        x0 = 0
        y0 = 60
        velocity = 25

        x45, y45 = makeShoot(x0, y0, velocity, 45)
        x30, y30 = makeShoot(x0, y0, velocity, 30)
        x60, y60 = makeShoot(x0, y0, velocity, 60)

        sp = self.figure.add_subplot(111)

        sp.plot(x45, y45, 'bo-', x30, y30, 'ro-', x60, y60, 'ko-', [0, 12], [0, 0], 'k-')
        #print(x45)
        #sp.plot(x45, x45)
        sp.legend(['45 deg shoot', '30 deg shoot', '60 deg shoot'])
        sp.set_xlabel('X coordinate (m)')
        sp.set_ylabel('Y coordinate (m)')
        self.canvas.draw()

    def on_animate_click(self):
        self.anim_step = 5
        velocity = 25
        self.dt = 0.1
        self.time = 0
        self.cannons = [
            Cannon(0, 0, velocity, 45),
            Cannon(0, 0, velocity, 30),
            Cannon(0, 0, velocity, 60)
        ]

        self.mainwindow.after(self.anim_step, self.anim)

    def anim(self):
        self.figure.clear()
        ax = self.figure.add_subplot(111)

        continue_anim = False
        for c in self.cannons:
            if c.y >= 0:
                c.step(self.dt)
                print(c.x, c.y)
                continue_anim = True
            ax.plot(c.xarr, c.yarr)

        ax.legend(['45 deg shoot', '30 deg shoot', '60 deg shoot'])
        ax.set_xlabel('X coordinate (m)')
        ax.set_ylabel('Y coordinate (m)')        
        self.canvas.draw()

        if continue_anim:
            self.mainwindow.after(self.anim_step, self.anim)

    def run(self):
        self.mainwindow.mainloop()

if __name__ == '__main__':
    app = Issue220App()
    app.run()

File: issue220.ui

<?xml version='1.0' encoding='utf-8'?>
<interface version="1.0">
  <object class="tk.Toplevel" id="mainwindow">
    <property name="geometry">640x480</property>
    <property name="height">200</property>
    <property name="resizable">both</property>
    <property name="title" translatable="yes">My App</property>
    <property name="width">200</property>
    <child>
      <object class="ttk.Frame" id="fmain">
        <property name="height">200</property>
        <property name="padding">2</property>
        <property name="width">200</property>
        <layout manager="pack">
          <property name="expand">true</property>
          <property name="fill">both</property>
          <property name="propagate">True</property>
          <property name="side">top</property>
        </layout>
        <child>
          <object class="ttk.Frame" id="ftop">
            <property name="height">200</property>
            <property name="padding">2</property>
            <property name="width">200</property>
            <layout manager="pack">
              <property name="fill">x</property>
              <property name="propagate">True</property>
              <property name="side">top</property>
            </layout>
            <child>
              <object class="ttk.Label" id="label_1">
                <property name="font">{Helvetica} 16 {bold}</property>
                <property name="text" translatable="yes">Cannon graph</property>
                <layout manager="pack">
                  <property name="padx">0 20</property>
                  <property name="propagate">True</property>
                  <property name="side">left</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="ttk.Button" id="button_1">
                <property name="command">on_draw_click</property>
                <property name="text" translatable="yes">Draw</property>
                <layout manager="pack">
                  <property name="propagate">True</property>
                  <property name="side">left</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="ttk.Button" id="button_1_2">
                <property name="command">on_animate_click</property>
                <property name="text" translatable="yes">Animate</property>
                <layout manager="pack">
                  <property name="padx">10 0</property>
                  <property name="propagate">True</property>
                  <property name="side">left</property>
                </layout>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="ttk.Frame" id="fplot">
            <property name="height">200</property>
            <property name="width">200</property>
            <layout manager="pack">
              <property name="expand">true</property>
              <property name="fill">both</property>
              <property name="propagate">True</property>
              <property name="side">top</property>
            </layout>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>
Doid-Reyewliz commented 3 years ago

Thanks you very much

Doid-Reyewliz commented 3 years ago

@alejandroautalan I have a question. I need the y0 and velocity values to be taken from Entry. How to make the values come from Entry. I created a record, but how do I write a command now?

alejandroautalan commented 3 years ago

@alejandroautalan I have a question. I need the y0 and velocity values to be taken from Entry. How to make the values come from Entry. I created a record, but how do I write a command now?

See issue #221 with the response.

Regards! Alejandro A.