How to Create Canvas Animation Using Tkinter

Tkinter is one of the most popular GUI toolkits available for python. It is available as part of the standard python installation. Hence you can use this package without installing any additional modules. Tkinter is a powerful GUI library and you can use it to build full fledged user interfaces or build simple desktop games.

This article contains a step by step guide on building an animation using the python tkinter package. We will be using canvas object of the tkinter package to draw our animation. Note that the following program requires python 3.6 or above.

The following python program creates a simple animation showing the movement of a ball across the screen. Following are the steps in the program,

  • Create and display main window of the application
  • Create and attach the canvas to the main window. We will be drawing to this canvas
  • Draw and animate a circle (our ball) inside the canvas

Let us start by importing tkinter and time modules. We will use time module to introduce a delay for drawing successive screens of the animation,

import tkinter
import time

Let us now create a set of variables for our animation demo. This defines the size of our gaming canvas, speed of the ball, size of the ball and where we want our ball to start moving from.

# width of the animation window
animation_window_width=800
# height of the animation window
animation_window_height=600
# initial x position of the ball
animation_ball_start_xpos = 50
# initial y position of the ball
animation_ball_start_ypos = 50
# radius of the ball
animation_ball_radius = 30
# the pixel movement of ball for each iteration
animation_ball_min_movement = 5
# delay between successive frames in seconds
animation_refresh_seconds = 0.01

The following function creates the application window with the specified title and dimensions,

# The main window of the animation
def create_animation_window():
  window = tkinter.Tk()
  window.title("Tkinter Animation Demo")
  # Uses python 3.6+ string interpolation
  window.geometry(f'{animation_window_width}x{animation_window_height}')
  return window

We then create a function to create the animation canvas and attach it to the main window,

# Create a canvas for animation and add it to main window
def create_animation_canvas(window):
  canvas = tkinter.Canvas(window)
  canvas.configure(bg="black")
  canvas.pack(fill="both", expand=True)
  return canvas

Let us now create a function to create and animate a circle in an infinite loop. We use the create_oval method to create a circle(ball) with specified radius and draw it on canvas. We move the ball until it reaches a boundary and then we reverse the direction of the ball. Note the use of move method of canvas which lets us move a drawing in the canvas to another location. Also note the use of python array unpacking to get the location of the ball.

# Create and animate ball in an infinite loop
def animate_ball(window, canvas,xinc,yinc):
  ball = canvas.create_oval(animation_ball_start_xpos-animation_ball_radius,
            animation_ball_start_ypos-animation_ball_radius,
            animation_ball_start_xpos+animation_ball_radius,
            animation_ball_start_ypos+animation_ball_radius,
            fill="blue", outline="white", width=4)
  while True:
    canvas.move(ball,xinc,yinc)
    window.update()
    time.sleep(animation_refresh_seconds)
    ball_pos = canvas.coords(ball)
    # unpack array to variables
    xl,yl,xr,yr = ball_pos
    if xl < abs(xinc) or xr > animation_window_width-abs(xinc):
      xinc = -xinc
    if yl < abs(yinc) or yr > animation_window_height-abs(yinc):
      yinc = -yinc

Finally let us call all the functions in order to run our animation,

# The actual execution starts here
animation_window = create_animation_window()
animation_canvas = create_animation_canvas(animation_window)
animate_ball(animation_window,animation_canvas, animation_ball_min_movement, animation_ball_min_movement)

Full code of the python animation demo using tkinter is given below. This can be easily extended to a bat and ball game if you know event handling in tkinter. Save the following code in "tkinter-draw-demo.py" file and then run the command "python3 tkinter-draw-demo.py" to see the animation.

import tkinter
import time

# width of the animation window
animation_window_width=800
# height of the animation window
animation_window_height=600
# initial x position of the ball
animation_ball_start_xpos = 50
# initial y position of the ball
animation_ball_start_ypos = 50
# radius of the ball
animation_ball_radius = 30
# the pixel movement of ball for each iteration
animation_ball_min_movement = 5
# delay between successive frames in seconds
animation_refresh_seconds = 0.01

# The main window of the animation
def create_animation_window():
  window = tkinter.Tk()
  window.title("Tkinter Animation Demo")
  # Uses python 3.6+ string interpolation
  window.geometry(f'{animation_window_width}x{animation_window_height}')
  return window

# Create a canvas for animation and add it to main window
def create_animation_canvas(window):
  canvas = tkinter.Canvas(window)
  canvas.configure(bg="black")
  canvas.pack(fill="both", expand=True)
  return canvas

# Create and animate ball in an infinite loop
def animate_ball(window, canvas,xinc,yinc):
  ball = canvas.create_oval(animation_ball_start_xpos-animation_ball_radius,
            animation_ball_start_ypos-animation_ball_radius,
            animation_ball_start_xpos+animation_ball_radius,
            animation_ball_start_ypos+animation_ball_radius,
            fill="blue", outline="white", width=4)
  while True:
    canvas.move(ball,xinc,yinc)
    window.update()
    time.sleep(animation_refresh_seconds)
    ball_pos = canvas.coords(ball)
    # unpack array to variables
    xl,yl,xr,yr = ball_pos
    if xl < abs(xinc) or xr > animation_window_width-abs(xinc):
      xinc = -xinc
    if yl < abs(yinc) or yr > animation_window_height-abs(yinc):
      yinc = -yinc

# The actual execution starts here
animation_window = create_animation_window()
animation_canvas = create_animation_canvas(animation_window)
animate_ball(animation_window,animation_canvas, animation_ball_min_movement, animation_ball_min_movement)

How to Download Multiple Files Concurrently in Python

Python has a very powerful library called requests for initiating http requests programmatically. You can use requests for downloading files hosted over http protocol. Run the following command to install requests python library. This assumes that you already have python 3 installed on your system.

pip3 install requests

You may need to prefix the above command with sudo if you get permission error in your linux system.

The following python 3 program downloads a given url to a local file. The following example assumes that the url contains the name of the file at the end and uses it as the name for the locally saved file.

import requests

def download_url(url):
  # assumes that the last segment after the / represents the file name
  # if the url is http://abc.com/xyz/file.txt, the file name will be file.txt
  file_name_start_pos = url.rfind("/") + 1
  file_name = url[file_name_start_pos:]

  r = requests.get(url, stream=True)
  if r.status_code == requests.codes.ok:
    with open(file_name, 'wb') as f:
      for data in r:
        f.write(data)

# download a sngle url
# the file name at the end is used as the local file name
download_url("https://jsonplaceholder.typicode.com/posts")

After running the above program, you will find a file named "posts" in the same folder where you have the script saved.

The following python 3 program downloads a list of urls to a list of local files. However the download may take sometime since it is executed sequentially.

import requests

def download_url(url):
  print("downloading: ",url)
  # assumes that the last segment after the / represents the file name
  # if url is abc/xyz/file.txt, the file name will be file.txt
  file_name_start_pos = url.rfind("/") + 1
  file_name = url[file_name_start_pos:]

  r = requests.get(url, stream=True)
  if r.status_code == requests.codes.ok:
    with open(file_name, 'wb') as f:
      for data in r:
        f.write(data)

# download a sngle url
# the file name at the end is used as the local file name
download_url("https://jsonplaceholder.typicode.com/posts")
download_url("https://jsonplaceholder.typicode.com/comments")
download_url("https://jsonplaceholder.typicode.com/photos")
download_url("https://jsonplaceholder.typicode.com/todos")
download_url("https://jsonplaceholder.typicode.com/albums")

The download program above can be substantially speeded up by running them in parallel. The following python program shows how to download multiple files concurrently by using multiprocessing library which has support for thread pools. Note the use of results list which forces python to continue execution until all the threads are complete. Without the iteration of the results list, the program will terminate even before the threads are started. Also note that we are running 5 threads concurrently in the script below and you may want to increase it if you have a large number of files to download. However, this puts substantial load on the server and you need to be sure that the server can handle such concurrent loads.

import requests
from multiprocessing.pool import ThreadPool

def download_url(url):
  print("downloading: ",url)
  # assumes that the last segment after the / represents the file name
  # if url is abc/xyz/file.txt, the file name will be file.txt
  file_name_start_pos = url.rfind("/") + 1
  file_name = url[file_name_start_pos:]

  r = requests.get(url, stream=True)
  if r.status_code == requests.codes.ok:
    with open(file_name, 'wb') as f:
      for data in r:
        f.write(data)
  return url


urls = ["https://jsonplaceholder.typicode.com/posts",
        "https://jsonplaceholder.typicode.com/comments",
        "https://jsonplaceholder.typicode.com/photos",
        "https://jsonplaceholder.typicode.com/todos",
        "https://jsonplaceholder.typicode.com/albums"
        ]

# Run 5 multiple threads. Each call will take the next element in urls list
results = ThreadPool(5).imap_unordered(download_url, urls)
for r in results:
    print(r)

How to Run Like Queries in MongoDB

When working with textual data in mongodb, you may want to fetch documents with partial match on a field. You can use the $regex operator in mongodb for running like queries. By default, the like query using $regex is case sensitive.

Assume that you have collection in your mongodb containing names of the countries. A sample document from the countries collection is given below,

{
  "countryName" : "United States",
  "isoCode" : "US",
  "countryCode" : "1"
}

The following mongodb query fetches all documents where countryName contains "rab" anywhere. This query is the SQL equivalent of "%rab%".

db.getCollection('countries').find({'countryName': {'$regex': 'rab'}})

The above query returns documents for "United Arab Emirates" and "Saudi Arabia". But if you replace "rab" with "RAB", not document is returned. For doing a case-insensitive search, you need to use the $options for $regex as given below.

db.getCollection('countries').find({'countryName': {'$regex': 'RAB', '$options':'i'}})

The above query returns documents for "United Arab Emirates" and "Saudi Arabia" by doing a case-insensitive search.

Run the following query if you want to retrieve all the countries where the name starts with "In" by doing a case-insensitive search.

db.getCollection('countries').find({'countryName': {'$regex': '^In', '$options':'i'}})

Run the following query to retrieve all the countries where the name ends with "tes" by doing a case-insensitive search.

db.getCollection('countries').find({'countryName': {'$regex': "tes$"
, '$options':'i'}})

How to Search in XML File Using Python and Lxml Library

Python is a powerful language when it comes to quick textual analysis of XML documents. Due to its simplicity and expressive power, it is possible to write short python scripts to do analysis on XML documents. In this article I provide a number of python scripts for processing and analysing content in XML documents. I will be using lxml library for processing xml documents in python.

For demonstrating the python script processing of xml, I will be using the following sample xml document. This represents customer information obtained from a typical CRM system. Save the following in a file named sample-customer-xml.xml in the same folder where python scripts are stored.



  
    John
    40
    9100000000
    NY
  
  
    Jack
    20
    9100000008
    NJ
  
  
    Pete
    56
    9100000001
    MD
  
  
    Mark
    11
    9100000003
    LA
  

The following python scripts assume that you have python3 and lxml library installed on your machine. If you don't have lxml, run the following command to install it. In linux, you may need to prefix the command with sudo (if you get permission errors),

pip3 install lxml

The following python script prints all the customer ids present in the sample XML. We open the xml file in binary mode and then read the entire contents. This is then passed to etree for parsing it into an xml tree. We then use an xpath expression to extract all the ids in a list data structure. Finally we iterate the list and then print all the ids on console. Note that the xml attribute id needs to be prefixed with @ when used in xpath.

from lxml import etree

with open("sample-customer-xml.xml",'rb') as f:
  file_content = f.read()
  tree = etree.fromstring(file_content)
  customer_ids = tree.xpath('//customer/@id')
  for id in customer_ids:
    print(id)

Following python program prints all the customer names who are in LA. We are using an xpath which returns all the customer names who are in LA.

from lxml import etree

with open("sample-customer-xml.xml",'rb') as f:
  file_content = f.read()
  tree = etree.fromstring(file_content)
  # get customer names for customers in LA
  customers_in_la = tree.xpath('//customer[state/text()="LA"]/name/text()')
  for name in customers_in_la:
    print(name)

Following python program converts the customer xml into a CSV file. We create one row for each customer with name, age, mobile and state separated by commas. This program requires python 3.6 or above since it uses literal string interpolation.

from lxml import etree

with open("sample-customer-csv.csv",'wt') as output:
  with open("sample-customer-xml.xml",'rb') as input:
    file_content = input.read()
    tree = etree.fromstring(file_content)
    # get all customer records
    customers = tree.xpath('//customer')
    for customer in customers:
      # note that xpath on text() returns a list
      name = customer.xpath('name/text()')[0]
      age = customer.xpath('age/text()')[0]
      mobile = customer.xpath('mobile/text()')[0]
      state = customer.xpath('state/text()')[0]
      # uses python 3.6 string interpolation
      # save the customer attributes in csv form
      output.write(f"{name},{age},{mobile},{state}\n")

How to Connect to MySQL Database and Run Queries from Python

MySQL now provides support for pure python based access to mysql database. Use the python mysql connector in your program for accessing the mysql database. Run the following command in your machine's command prompt to install the driver (this assumes you already have python3 installed),

pip3 install mysql-connector-python

If you get access denied error for the above, you may need to add sudo prefix to the above command.

Once you have the mysql connector installed, obtain the mysql server url, username, password and database name. Replace the variables in the following script with the values of the above. You may also want to replace the query written for wordpress database. Note that the following script is written for python3 and above.

The following python script connects to a mysql database configured for wordpress and then prints all the published posts along with the category under which it is published. You can easily customize this script for your queries.

# pip3 install mysql-connector-python
import mysql.connector

# Need to give access to your machine if using dreamhost MySQL instance
mysql_server = "replace_this_server_name"
mysql_user = "replace_this_user"
mysql_password = "replace_this_password"
mysql_db_name = "replace_this_db_name"

connection = mysql.connector.connect(user=mysql_user,
                  password=mysql_password,
                  host=mysql_server,
                  database=mysql_db_name)
try:
  cursor = connection.cursor()
  cursor.execute("""
    SELECT wp_posts.post_title AS ‘Title’, wp_terms.name AS ‘Cateogry’
    FROM wp_posts
    INNER JOIN wp_term_relationships
    ON wp_posts.ID = wp_term_relationships.object_id
    INNER JOIN wp_terms
    ON wp_term_relationships.term_taxonomy_id = wp_terms.term_id
    INNER JOIN wp_term_taxonomy
    ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id
    WHERE wp_posts.post_status = 'publish'
    AND wp_posts.post_type = 'post'
    AND wp_term_taxonomy.taxonomy = 'category'
    ORDER BY wp_terms.name;
  """)
  for row in cursor:
    print(row[0]+","+row[1])
finally:
  connection.close()

If you are using a mysql database provisioned by a cloud service provider such as Dreamhost, you may have to enable direct access to the database from your machine. This option is usually available on the cloud service provider portal. For example, if you are using Dreamhost, click on the mysql username from the Dreamhost panel and then add "%" to the list of "Allowable hosts". This gives user permission to connect from any IP. Alternatively you can give the specific external IP of the machine as well. Without this option, you will get "_mysql_connector.MySQLInterfaceError: Access denied for user and mysql.connector.errors.ProgrammingError: 1045 (28000): Access denied for user" message.