3

I am trying to use Tkinter to visualise some time-series data. My data is in the form of a 2D matrix, with rows referring to blocks and columns referring to a certain period in time. All of the values are between 0 and 1.

I want to create a python script using Tkinter that creates a window, displays the blocks in a square matrix with the first column determining the luminance of each block, then after a predetermined amount of time, change the luminance of the blocks according to successive columns in the data.

I have created a cut-down version of what i have so far:

#!/usr/bin/python

import sys
import os.path
import Tkinter as tk
import math

# program constants
WINDOW_WIDTH = 900

# function to change the colours of the blocks based on current period
def redrawPeriod(t):
    for b in xrange(len(blocks)):
        luminance = int(blocks[b][t] * 255)
        colour = "#%x%x%x" % (luminance,luminance,luminance)
        canvas.itemconfig(block_ids[b],fill=colour)

# sample data (4 blocks 4 periods)
blocks = [
        [0.0, 0.2, 0.5, 0.8],
        [0.3, 0.0, 0.4, 0.0],
        [0.5, 0.7, 0.0, 1.0],
        [0.0, 0.0, 0.3, 0.6],
        [1.0, 0.5, 0.2, 0.9]
      ]

# get number of blocks and periods
nB = len(blocks)
nT = len(blocks[0])

n_cols = int(math.sqrt(nB))
n_rows = n_cols

# if not perfect square number of blocks, add extra row
if (nB % n_rows != 0):
    n_rows = n_rows + 1

# calculate block size
BLOCK_SIZE = WINDOW_WIDTH / n_cols
WINDOW_HEIGHT = BLOCK_SIZE * n_rows

# initialise Tkinter
root = tk.Tk()

# initialise canvas
canvas = tk.Canvas(root, width=WINDOW_WIDTH, height=WINDOW_HEIGHT, background="#002080")

# open canvas
canvas.pack()

# container for block objects
block_ids = []

x = 0
y = -1*BLOCK_SIZE

# initialise block objects
for b in xrange(nB):
    if (b % n_cols == 0):
        x = 0
        y = y + BLOCK_SIZE

    luminance = int(blocks[b][0] * 255)
    colour = "#%x%x%x" % (luminance,luminance,luminance)
    id = canvas.create_rectangle(x, y, x+BLOCK_SIZE, y+BLOCK_SIZE, outline="",fill = colour)
    block_ids.append(id)
    x = x + BLOCK_SIZE

for t in xrange(nT):
    root.after(1000, redrawPeriod,t)

root.mainloop()

This seems to do what i want it to, however it skips straight to the final frame every time - ie. it doesn't draw one frame, pause, draw another frame, pause again, etc.

Can anyone please help me work out what I am doing wrong?

guskenny83
  • 1,321
  • 14
  • 27

1 Answers1

1

Your problem is that when you call:

for t in xrange(nT):
    root.after(1000, redrawPeriod,t)

root.after() doesn't block the execution, so the for loop is executed very fast, and all your redraw events are called simultaneously after 1000 ms.

The usual way to run an animation in Tkinter is to write an animation method which calls itself after a delay (see Method for having animated movement for canvas objects python and Tkinter, executing functions over time for more info).

In your case, you could do something like this:

# Create all widgets
...

# Store the period indices to be redrawn ([0, 1, 2, ..., nT-1])
periods = range(nT)

# Define the animation method
def animation_loop(delay):
    # Check if there are still periods to be drawn
    if len(periods) > 0:
        t = periods[0]
        redrawPeriod(t)
        # Remove the period that was just drawn from the list of periods
        del periods[0]
        # Call the animation_loop again after a delay
        root.after(delay, animation_loop, delay)

# Start the animation loop
animation_loop(1000)

# Launch the app
root.mainloop()
Community
  • 1
  • 1
Josselin
  • 2,593
  • 2
  • 22
  • 35
  • 1
    Thanks, that worked perfectly. I had assumed that the 'root.after()' call paused the execution. But apparently not so! Thanks again.. – guskenny83 May 18 '17 at 17:09