Gnuplot Speedometer Gauge

Gnuplot is a great package that allows you to do charting from the command line.

Gnuplot has a wide range of plot types but unfortunately a speedometer gauge is not one of them.

This blog documents my notes in creating a dynamic gauge.

Some useful takeaways:

  • A gnuplot can be created with only graphic elements (no chart)
  • A named object can be re-positioned without redrawing the background
  • A gnuplot script can be called like a Bash or Python script with the first line being: #!/usr/bin/gnuplot

A Dynamic Gauge

The script gnuplot.gp is an example that refreshes a gauge chart every 5 seconds with the processor’s idle time. The idle time is obtained using the Linux top command (with some awk to get the 8th line item).

#!/usr/bin/gnuplot
#
# gnuplot.gp - speedometer dial with title as the description/value
#
set xrange [-1:1]
set yrange [0:1]
set angles degrees
set size ratio -1
# r1 = annulus outer radius, r2 = annulus inner radius
r1=1.0
r2=0.5
unset border; unset tics; unset key; unset raxis

set style fill solid noborder

# define a "needle" pointer as object 1
set object 1 circle at first 0,0 front size r1 arc [181:182]  fillcolor rgb 'black'

# define the gauge background
set object circle at first 0,0  size r1 arc [0:180]  fillcolor rgb 'green'
set object circle at first 0,0  size r1 arc [0:10]  fillcolor rgb 'red'
set object circle at first 0,0  size r1 arc [10:20]  fillcolor rgb 'yellow'
set object circle at first 0,0 front size r2 arc [0:180] fillcolor rgb 'black'

# plot the static background 
plot -10 notitle

# Define a partial heading  
variable = "Idle Time\n"
unit = "%"

# Refresh the plot with a new dial setting
while (1) {
  # Get the idle time using the top utility
  idle = system("top -n 1 | grep Cpu | awk '{print $8}'")
  # scale the value from 0-100 to 180-0 (Note: arc starts on the right)
  value = (100 - real(idle)) * 1.8
  # show the value in the title
  set title sprintf('%s %s %s', variable, idle, unit)  font "Ariel,28"
  # reposition the value in the gauge
  set object 1 circle at first 0,0 front size r1 arc [value:(value+2)]  fillcolor rgb 'black'
  replot
  pause 5 
}

The script can be run either from gnuplot or like a Bash script:

$ # run script from gnuplot
$ gnuplot -c gauge.gp
Use Control-C to exit ...
^C
$ # make script executable
$ chmod +x gauge.gp
$ # run script like a Bash script
$ ./gauge.gp
Use Control-C to exit ...
^C

Next Steps…

From here the next steps could be to add command line arguments to make the script more generic, such as:

  • pass the calculation to run (eg. pass the bash top command)
  • pass the scaling, title and units

Gauges in a Python Canvas

There are some nice Python packages like tk_tools, that can be used for IoT indicators and gauges.

My daughter and I had a project where we wanted to repurpose an old eReader to be a kitchen kiosk display. Unfortunately tk_tools doesn’t support Python 2.7, also and we needed to account for gray scale and larger text, so we needed to look at another solution.

This blog documents how we made some simple update-able gauges using Python Tkinter Canvas objects that are supported in both Python 2.7 and 3.x .

Getting Started

Unfortunately the Python 2 and 3 Tkinter libaries are named differently (Tkinter in 2.7 vs tkinter in 3.x). If you are coding for both Python 2.7 and 3.x this gets messy, a simple workaround in your code is:

# Manage Python 2.7 and 3.x
#
import sys
# Check the version of Python and use the correct library
if sys.version_info[0] == 2:
    import Tkinter
else:
    import tkinter as Tkinter

Analog Clock

A Tkinter canvas supports a number of basic objects such as rectangles, circles, arcs, text, etc. The basic objects are positioned within the canvas space.

I found that as a first example an analog clock was a good place start. The first pass code for a clock with just the second hand would be:

# A Clock Second Hand Example
#
import tkinter as Tkinter # Python 3.x
import datetime

def update_sec():
    #Reposition the second hand starting position
    thesec = datetime.datetime.now().second
    arcstart = 90 - thesec*6  #0 sec = 90deg
    C.itemconfig(asec,start=arcstart) #pass the new start position
    C.after(1000, update_sec)

# Create a canvas object with an oval face and a second hand
top = Tkinter.Tk()

C = Tkinter.Canvas(top, bg="silver", height=250, width=300)
C.pack()


coord = 10, 50, 240, 210 
C.create_oval(coord,  fill="white")
# Have the second hand start at the top (90 deg) with 1 deg arc
asec = C.create_arc(coord, start=90, extent=1, width=3)

C.after(1000, update_sec)
top.mainloop()

The key point is to get the id of the seconds hand arc (asec). The itemconfig method is then used to change the starting position of seconds hand arc (C.itemconfig(asec,start=arcstart) ).

The arc positioning is a little backwards, 0 degrees is at 3o’clock and then goes counter-clockwise.

The next step is to add narrow arcs for the minutes and hours. Also text could be used to digitally show the date and time. For the hour and minute hand I used different colours and thicknesses.

#
# A Clock Example
#
import tkinter as Tkinter # Python 3.x
from datetime import datetime

def update_sec():
    # Position the hands 
    C.itemconfig(asec,start= 90 - datetime.now().second*6)
    C.itemconfig(amin,start= 90 - datetime.now().minute*6)
    C.itemconfig(ahour,start= 90 - datetime.now().hour*360/12)
    C.itemconfig(dtime,text = datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
    C.after(1000, update_sec)

# Create a canvas object with an oval face and a second hand
top = Tkinter.Tk()

C = Tkinter.Canvas(top, bg="silver", height=250, width=300)
C.pack()

coord = 10, 50, 240, 210
C.create_oval(coord,  fill="white")
# Have the second hand start at the top (90 deg) with 1 deg arc
asec = C.create_arc(coord, start=90, extent=1, width=2)
amin = C.create_arc(coord, start=90, extent=1, width=4, outline='blue')
ahour = C.create_arc(coord, start=90, extent=1, width=6, outline='red')
dtime = C.create_text(120,20, font="Times 16 bold", text="00:00:00")

C.after(1000, update_sec)
top.mainloop()

Gauges

There are a number of different types of gauges. My first example was a speedometer graph, that used an arc for both the background and the gauge needle:

#
# Use Canvas to create a basic gauge
#
from tkinter import *
import random

def update_gauge():
    newvalue = random.randint(low_r,hi_r)
    cnvs.itemconfig(id_text,text = str(newvalue) + " %")
    # Rescale value to angle range (0%=120deg, 100%=30 deg)
    angle = 120 * (hi_r - newvalue)/(hi_r - low_r) + 30
    cnvs.itemconfig(id_needle,start = angle)
    root.after(3000, update_gauge)

    
# Create Canvas objects    

canvas_width = 400
canvas_height =300

root = Tk()

cnvs = Canvas(root, width=canvas_width, height=canvas_height)
cnvs.grid(row=2, column=1)

coord = 10, 50, 350, 350 #define the size of the gauge
low_r = 0 # chart low range
hi_r = 100 # chart hi range

# Create a background arc and a pointer (very narrow arc)
cnvs.create_arc(coord, start=30, extent=120, fill="white",  width=2) 
id_needle = cnvs.create_arc(coord, start= 119, extent=1, width=7)

# Add some labels
cnvs.create_text(180,20,font="Times 20 italic bold", text="Humidity")
cnvs.create_text(25,140,font="Times 12 bold", text=low_r)
cnvs.create_text(330,140,font="Times 12 bold", text=hi_r)
id_text = cnvs.create_text(170,210,font="Times 15 bold")

root.after(3000, update_gauge)

root.mainloop()

The basic gauge can be enhanced to have more value ranges and colour hihi/hi/low ranges:

#
# Use Canvas to create a basic gauge
#
from tkinter import *
import random

def update_gauge():
    newvalue = random.randint(low_r,hi_r)
    cnvs.itemconfig(id_text,text = str(newvalue) + " %")
    # Rescale value to angle range (0%=120deg, 100%=30 deg)
    angle = 120 * (hi_r - newvalue)/(hi_r - low_r) + 30
    cnvs.itemconfig(id_needle,start = angle)
    root.after(3000, update_gauge)

    
# Create Canvas objects    

canvas_width = 400
canvas_height =300

root = Tk()

cnvs = Canvas(root, width=canvas_width, height=canvas_height)
cnvs.grid(row=2, column=1)

coord = 10, 50, 350, 350 #define the size of the gauge
low_r = 0 # chart low range
hi_r = 100 # chart hi range

# Create a background arc with a number of range lines
numpies = 8
for i in range(numpies):
    cnvs.create_arc(coord, start=(i*(120/numpies) +30), extent=(120/numpies), fill="white",  width=1)    

# add hi/low bands
cnvs.create_arc(coord, start=30, extent=120, outline="green", style= "arc", width=40)
cnvs.create_arc(coord, start=30, extent=20, outline="red", style= "arc", width=40)
cnvs.create_arc(coord, start=50, extent=20, outline="yellow", style= "arc", width=40)
# add needle/value pointer
id_needle = cnvs.create_arc(coord, start= 119, extent=1, width=7)

# Add some labels
cnvs.create_text(180,15,font="Times 20 italic bold", text="Humidity")
cnvs.create_text(25,140,font="Times 12 bold", text=low_r)
cnvs.create_text(330,140,font="Times 12 bold", text=hi_r)
id_text = cnvs.create_text(170,210,font="Times 15 bold")

root.after(3000, update_gauge)

root.mainloop()

Our Final Project

Our final project had 4 gauges that were based on basic gauge code. Our Python app ran full screen on a Kobo eReader that we installed Debian Linux on. The app connected to our Home Assistant Pi and showed us our current weather conditions.

We had to tweek the basic code a little bit to account for the 800×600 screen size and grey scale graphics.

Summary

In this blog we only looked at some basic gauges, the Tkinter Canvas component can be used in a very variety of different applications such as: bar charts, real time charts, graphics etc.