Wednesday, August 29, 2012

Tkinter Animated Wave Demo

This code is based on the Tcl aniwave.tcl demo. A wave frequency line is drawn and animated, moving the frequency peak from right to left and back along the line.



# File: aniwave.py
#    http://infohost.nmt.edu/tcc/help/pubs/tkinter//canvas.html#create_line

from tkinter import *
from tkinter import ttk
from demopanels import MsgPanel, SeeDismissPanel

class AnimatedWaveDemo(ttk.Frame):
    
    def __init__(self, isapp=True, name='aniwavedemo'):
        ttk.Frame.__init__(self, name=name)
        self.pack(expand=Y, fill=BOTH)
        self.master.title('Animated Wave Demo')
        self.isapp = isapp
        self._create_widgets()
        
    def _create_widgets(self):
        if self.isapp:
            MsgPanel(self, 
                     ["This demonstration contains a canvas widget with a line item ",
                      "inside it. The animation routines work by adjusting the ",
                      "coordinates list of the line; which, in turn, changes the ",
                      "position of the frequency zig-zag on the line."])
            
            SeeDismissPanel(self)
        
        self._create_demo_panel()
        
    def _create_demo_panel(self):
        demoPanel = Frame(self)
        demoPanel.pack(side=TOP, fill=BOTH, expand=Y)
        
        # Create a canvas large enough to hold the wave. In fact, the wave
        # sticks off both sides of the canvas to prevent visual glitches.
        self.__canvas = Canvas(demoPanel, width=300, height=200, background='black')
        self.__canvas.pack(padx=10, pady=10, expand=Y)
        
        # Create a coordinates list for the wave. This code does a very sketchy
        # job and relies on Tk's line smoothing to make things look better.
        self.__waveCoords = []
        for x in range(-10, 305, 5):
            self.__waveCoords.extend((x, 100))      # straight line
           
        self.__waveCoords.extend((305,0, 310, 200)) # frequency zig-zag
        
        # Create a smoothed line using the wave coordinates we've just set
        self.__canvas.create_line(self.__waveCoords, tags=('wave',), 
                                  width=1, fill='green', smooth=True)

        self.__direction = 'left'   # set the direction of wave        
        self._move()                # start the animation
                
    def _move(self):
        # start the animation
        self._basic_motion()
        self._reverse_wave()
        
        # repeat the animation every 10 milliseconds
        self.after(10, self._move)

    def _basic_motion(self):
        # Basic motion handler.
        # Advances the 'y' coordinates of the wave line
        # one step in the currently active direction.
        
        # get a copy of the current wave line coordinates
        oc = list(self.__waveCoords)    
        oclen = len(oc)
        
        for i in range(1, oclen, 2):
            if self.__direction == 'left':
                idx = i + 2
                # at a boundary?
                if idx > oclen: idx = 1 
                self.__waveCoords[i] = oc[idx]
            else:   # moving 'right'
                idx = i - 2
                # at a boundary?
                if idx < 0: idx = oclen-1   
                self.__waveCoords[i] = oc[idx]

            # redraw the wave line
            self.__canvas.coords('wave', *self.__waveCoords)
                        
    def _reverse_wave(self):
        # Oscillation handler. This detects whether to reverse the direction
        # of the wave by checking to see if the peak of the wave (whose size
        # we already know) has moved off the screen.
        
        # get current wave line coordinates
        wave = self.__canvas.coords('wave')
        if wave[1] < 10:    # first 'y' coord
            self.__direction = 'right'
        elif wave[-1] < 10: # last 'y' coord
            self.__direction = 'left'

if __name__ == '__main__':
    AnimatedWaveDemo().mainloop()