Animated SVG Graphics in Python

Home and small Python projects can use animated graphics without adding a lot of code.

In this blog I wanted to document my testing for animating SVG (Scalar Vector Graphics) in QT GUI’s.

Getting Started with SVG

There are two main types of graphics, raster based (GIF, PNG, JPEG) and vector based (SVG). Raster or bitmap based graphics are excellent for applications like photographs. Vector based graphics are good for re-scaling or application where individual items in the graphic need to be adjusted.

There are a number of open source SVG editing tools, such as Inkscape that works on Windows, Linux and Mac.

Creating SVG images from scratch can be a lot of work, luckily there are a number of sites that offer free SVG libraries. Two libraries that I used were https://www.svgrepo.com/ for smaller icon graphics and the Opto22 site for industrial images.

Python Libraries

There are a number of Python graphic libraries (Tkinter, xW, QT …). For SVG support I found that the documentation for QT to be probably the easiest to follow.

To get the Python QT and SVG libraries loaded:

sudo apt install python3-pyqt5
sudo apt install python3-pyqt5.qtsvg

To load a static SVG file:

# svg1.py - load an SVG file in QT
#
import sys
from PyQt5 import QtSvg
from PyQt5.QtWidgets import QApplication

app = QApplication(sys.argv)

svgWidget = QtSvg.QSvgWidget()

svgWidget.load('smeter.svg')
svgWidget.show()

sys.exit(app.exec_())

QT GUI apps need an instance of QApplication, which is passed the command line arguments or nothing ( [ ] ).

Next an SVG widget is created and it is loaded with a SVG file (or a string of SVG text).

The final step is to show the Widget and pass control to the QApplication object.

SVG ID’s and Viewbox’s

The SVG syntax is similar to XML or HTML. There are different types of objects, (text, rectangles, etc.) and objects are identified by ID’s.

A simple SVG example with a rectangular box and some text would be:

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 200 100" xml:space="preserve">
  <svg>
  <rect id="thebox" fill="#aaa" stroke="#000" stroke-width="2" x="2" y="2" width="150" height="46"></rect>
  <text id="thetext" fill="#333" font-size="20px"  style="font-weight: bold;" y="30.5" x="5.5">Some Text</text>
  </svg>
</svg>

For this example the default SVG window size is set by the viewBox to be: “0 0 200 100” in the opening svg tag.

The rectangular box is defined with the rect tag and it is given an id=”thebox”. Similarly the text is defined using a text tag and it is given an id=”thetext”.

To get or change an SVG element, the element type is required, such as text or rect and the id is needed.

Change SVG Text

For the next example an SVG flowmeter file from the Opto22 site is loaded into the Inkscape app and a text item is added with an id of flowval.

This example’s Python program uses the lxml library to load the SVG into an etree object that can be searched with the ETXPath method.

# svg_timer.py - update an SVG text/color on a timer
#
import sys
import random
from lxml import etree
from PyQt5 import QtCore, QtGui, QtSvg
from PyQt5.QtWidgets import QApplication

def timerEvent():
    # set the flowval object text to a number 1-10
    find_text(root)[0].text = str(random.randint(1,10))
    # reload the SVG widget with the text
    svgWidget.load(etree.tostring(root))
                                  
root = etree.parse(r'flow0.svg')
# create an object for the flowval 
find_text = etree.ETXPath("//{http://www.w3.org/2000/svg}text[@id='flowval']")

app = QApplication(sys.argv)

svgWidget = QtSvg.QSvgWidget()
svgWidget.load("flow0.svg")

# Setup a timer event
timer = QtCore.QTimer()
timer.timeout.connect(timerEvent)
timer.start(1000)

svgWidget.show()

sys.exit(app.exec_())

The command : etree.ETXPath(“//{http://www.w3.org/2000/svg}text[@id=’flowval’]“), needs the know that the format of the document (http://www.w3.org/2000/svg), the type of object (text) and the id (flowval).

A QtCore.QTimer() object is created and it calls the timerEvent function every second. The timerEvent function puts a random number into the text of the flowval SVG entity.

Change SVG Item Attributes

For the last example a QT GUI is created with 2 buttons. The buttons will change the color of some solar panels and update a power reading.

The panels are found by an SVG path object type and an id of panels. The attributes of theplanel object are read and modified using the attribute[“the_attribute”] method. For example to change the color of the panels to red:

# Define SVG root object
svg_root = etree.parse(r'solarpanels.svg')
# create a panel object
thepanel = etree.ETXPath("//{http://www.w3.org/2000/svg}path[@id='panels']")
# set the fill colors of the panel to red
thepanel(svg_root)[0].attrib['fill'] = "red"

The full 2-button PyQt code is below:

import sys
# svg_2buttons.py - change solar panel colors and power text
#
from lxml import etree
from PyQt5 import QtGui, QtSvg
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout


def turn_to(thecolor,theval):
    # change the color of the panels, and power text
    thepanel(svg_root)[0].attrib['fill'] = thecolor
    thevalue(svg_root)[0].text = theval
    svgWidget.load(etree.tostring(svg_root))
    
# Define SVG info
svg_root = etree.parse(r'solarpanels.svg')

thepanel = etree.ETXPath("//{http://www.w3.org/2000/svg}path[@id='panels']")
thevalue = etree.ETXPath("//{http://www.w3.org/2000/svg}text[@id='watts']")
svg_str = etree.tostring(svg_root)

app = QApplication([])
window = QWidget()
layout = QVBoxLayout()

# Create 2 buttons to change the panel color and power text
btn_on = QPushButton('TURN ON')
btn_on.clicked.connect(lambda: turn_to("red","200 W") )
layout.addWidget(btn_on)

btn_off = QPushButton('TURN OFF')
btn_off.clicked.connect(lambda: turn_to("silver", "0 W") )
layout.addWidget(btn_off)

svgWidget = QtSvg.QSvgWidget()
svgWidget.load(etree.tostring(svg_root))

layout.addWidget(svgWidget)
window.setLayout(layout)
window.setWindowTitle("Solar Panel")
window.show()

sys.exit(app.exec_())

Final Comments

Combining SVG graphics and Python can add some useful presentations for your next project.

SVG items within a QT GUI can also be made select-able.

There are also a number of SVG charting object that can be used (https://pypi.org/project/svg.charts/ and https://www.pygal.org/en/stable/).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s