Making a Tkinter Gauge#

As we found the output from the two LDRs (what those again) is relatively sedate we should have enough time to draw everything in tkinter and give the user a colour choice.

Create a class using Canvas as the parent, so we inherit all the canvas properties. Create a dictionary of the dependant properties, then all the initialised properties, then build up the gauge using the utility functions.

Tkinter Utilities#

The first utility is just a colour choice dictionary, from a colour selection the associated colours for highlight, background, surround and ghost are automatically selected:

def colour_choice(colour):
blued={'f':'#02fdf6','b':'#0031a7','g':'#0d4fdb','s':'#18a1ff'}
oranged={'f':'#fee737','b':'#7b0106','g':'#5E0B0B','s':'#fa0e20'}
greend={'f':'#00F23C','b':'#002504','g':'#005914','s':'#84aea7'}
purpled={'f':'#F9F9FD','b':'#1D1739','g':'#3F1E4B','s':'#9493A1'}

if colour=='purple':
    bdial=purpled['b']
    gdial=purpled['g']
    fdial=purpled['f']
    sdial=purpled['s']

elif colour=='blue':
    bdial=blued['b']
    gdial=blued['g']
    fdial=blued['f']
    sdial=blued['s']

elif colour=='green':
    bdial=greend['b']
    gdial=greend['g']
    fdial=greend['f']
    sdial=greend['s']

else:
    bdial=oranged['b']
    gdial=oranged['g']
    fdial=oranged['f']
    sdial=oranged['s']
return bdial,fdial,gdial,sdial

Next off is a utility to draw arcs based on the centre and radius:

# extent starts at 0 along x axis, goes anticlockwise, style PIESLICE
def tk_arc(canvas,c,r,style,start,extent,outline='#888888',fill='#888888'):
return canvas.create_arc([c[0]-r,c[1]-r,c[0]+r,c[1]+r],style=style,
    start=start,extent=extent,outline=outline,fill=fill)

We need a utility to draw the ticks around an arc, this means they have to be a line with its main axis lying along the radius. It is possible to make the ticks wedge shaped using pieslices, but then this leaves a central part that needs to be cleaned up. Our ticks can be tagged:

# create tick with centre, radius, length, angle (starting 3 o' clock
# increasing clockwise
def tk_tick(canvas,c,ri,l,angle,fill='#888888',width=1,tags=None):
    rangle = angle * pi / 180.0
    x0 = c[0] + ri * cos(rangle)
    y0 = c[1] + ri * sin(rangle)
    x1 = c[0] + (ri + l) * cos(rangle)
    y1 = c[1] + (ri + l) * sin(rangle)
    return canvas.create_line([x0,y0,x1,y1],fill=fill,width=width,tags=tags)

Associated with ticks are deltas that should also align with the radius:

def tk_delta(canvas,angle,c,ro,e,fill='#888888'):
    rangle = angle * pi / 180.0
    x0 = c[0] + ro * cos(rangle)
    y0 = c[1] + ro * sin(rangle)
    x1 = x0 + 2*e * sin(rangle)
    y1 = y0 - 2*e * cos(rangle)
    x2 = x0 - 2*e * sin(rangle)
    y2 = y0 + 2*e * cos(rangle)
    x3 = c[0] + (ro -3*e) * cos(rangle)
    y3 = c[1] + (ro -3*e) * sin(rangle)
    return canvas.create_polygon([x1,y1,x2,y2,x3,y3],fill=fill)

Lastly the scale range has numbers drawn on an arc adjacent to the deltas. The figures stay in the normal orientation:

# create text according to polar co-ordinates
def tk_text(canvas,text,c,ro,angle,font='arial',size=12,fill='#888888',tags=''):
    rangle = angle * pi / 180.0
    x0 = c[0] + ro * cos(rangle)
    y0 = c[1] + ro * sin(rangle)
    return canvas.create_text((x0,y0),text=text,font=(font,size,'italic'),
            fill=fill,tags=tags)

Tkinter Construction#

There are about 30 variables required to be initialised, then we can construct the guge. Starting from the outer parts and working inwards. Make the bezel using pieslices adding 20° to the scale extent, so start 10° less and add 10° at the end. Clean up the central area, then add the joining line to two ends.

Next the two rows of ticks are made, the small ticks in the highlight colour the larger ticks in the ghost colour. Then we can make the deltas and text in highlight colour.

Dependant on the range add the ghost figures '888' or '8888', and add the normal text.

Now we can start our pointer function. During the normal interreaction with the Arduino this function is called using the value and old value, we can draw the larger ticks and the reading in our highlight colour. We either do nothing, delete the large ticks or add new ticks. The display value is always redrawn, no matter what.

Show/Hide Code lcd_tk_dpi.py

from tkinter import Tk,Canvas, PIESLICE, Frame
from math import pi,sin,cos
from DialUtils import colour_choice,tk_arc,tk_tick,tk_text,tk_delta

'''
converting to class, based loosely on Ardiotech
'''
class LCD(Canvas):
    def __init__(self,parent,select,enlargement=1,colour='red',unit='Temp C'):
        super().__init__(parent,bd=0,highlightthickness=0)
        self.select=select
        self.enlargement=enlargement
        self.colour=colour
        self.unit=unit

        # lcd gauge type; min,max scale extent; st,end start end degrees;
        # upt units per tick; w width pixels; tw tick width
        self.lcds=lcds={
            1:{'lcd':'lcd70','min':-30,'max':70,'st':120,'end':60,'upt':1,
                    'w':201,'tw':3,'mess':'temperature'},
            2:{'lcd':'lcd100','min':0,'max':100,'st':120,'end':60,'upt':1,
                    'w':201,'tw':3,'mess':'general\npurpose'},
            3:{'lcd':'lcd255','min':0,'max':270,'st':135,'end':45,'upt':2,
                    'w':201,'tw':2,'mess':'analogue\n output'},
            4:{'lcd':'lcd1023','min':0,'max':1030,'st':117,'end':63,'upt':10,
                    'w':201,'tw':4,'mess':'analogue\n input'}
                    }

        #select=3
        self.e=e=enlargement
        we=lcds[select]['w']*e
        he=lcds[select]['w']*e
        self['width']=we
        self['height']=he
        self.ce=ce=we//2,he//2
        self.re=re=we//2
        lwe=re/25*e

        self.max_value=max_value=int(lcds[select]['lcd'].strip('lcd')) #70
        self.min_value=min_value=lcds[select]['min'] #-30
        val=(max_value+min_value)//2
        max_scale=lcds[select]['max']
        self.unitspertick=unitspertick=lcds[select]['upt']
        self.dial=lcds[select]['lcd']
        mess=lcds[select]['mess']
        # angular calculations
        # 300° scale extent, 100 divisions therefore 1 tick interval 3 degrees
        degree_extent=360-lcds[select]['st']+lcds[select]['end'] # 300
        scale_extent=max_scale-min_value # 100
        self.tick_extent=tick_extent=degree_extent/scale_extent
        self.tick_width=tick_width=lcds[select]['tw']
        # trig_start=240 # scale degrees corresponding to 0° trig

        self.start_scale=start_scale=lcds[select]['st'] # physical start scale in degrees
        # end_scale=60 # physical end scale in degrees

        bdial,fdial,gdial,sdial=colour_choice(colour)
        self.fdial=fdial
        self.dfont=dfont = 'Digital-7 Mono'
        background=bdial # '#090B0E'

        # create bezel, add 20 degrees to scale extent
        phi=-(start_scale-10)
        ext=-(degree_extent+20)
        tk_arc(self,ce,re,style=PIESLICE,start=phi,extent=ext,
            outline='',fill=sdial)
        tk_arc(self,ce,re-lwe,style=PIESLICE,start=phi,extent=ext,
            outline='',fill=background)
        theta=(-phi-90)
        rangle=theta*pi/180

        # clean up central area
        st=ce[0]-(re+lwe/2)*sin(rangle),ce[1]+(re-lwe/2)*cos(rangle)
        end=ce[0]+(re+lwe/2)*sin(rangle),ce[1]+(re-lwe/2)*cos(rangle)
        self.create_polygon(ce,st,end,fill=background)

        # join the 2 ends
        self.create_line([st,end],fill=sdial,width=lwe)

        # create outer 2 rows ticks
        for j in range(0,scale_extent//unitspertick+1): # degree_extent//tick_extent
            angle=j*tick_extent*unitspertick +start_scale # measured angle
            tk_tick(self,ce,re-8*e,2*e,angle,fill=fdial,width=tick_width*e)
            tk_tick(self,ce,re-18*e,8*e,angle,fill=gdial,width=tick_width*e)

        # inner ticks need to be made before any deltas
        for j in range(0,degree_extent+1): # range 300 scale_extent degree_extent//tick_extent
            angle=j + start_scale
            tk_tick(self,ce,re-20*e,2*e,angle,fill=sdial,width=1*e)

        if max_value in (70,100):
            x=29
        elif max_value==255:
            x=31
        else:
            x=34

        for j in range(0,scale_extent//unitspertick+1):
            angle=j*tick_extent*unitspertick + start_scale
            if j % 10==0:
                tangle=str((j+min_value)*unitspertick)
                tk_delta(self,angle,ce,re-19*e,e,fill=fdial)
                tk_text(self,tangle,ce,re-x*e,angle,fill=fdial,font=dfont,size=10)

        if max_value != 1023:
            tk_text(self,'888',ce,0,0,fill=gdial,font=dfont,size=60)
        else:
            tk_text(self,'8888',ce,0,0,fill=gdial,font=dfont,size=45)

        y=(re*4//10 if max_value in (255,1023) else re//2)
        tk_text(self,mess,(ce[0],ce[1]-y),0,0,
            fill=fdial,font=dfont,size=10)

        y=(re*7//10 if max_value==255 else re*4//5)
        tk_text(self,unit,(ce[0],ce[1]+y),0,0,
            fill=fdial,font=dfont,size=10)

        self.lcd_pointer(val,self.min_value)

    def lcd_pointer(self,val,oldval):
        re=self.re
        ce=self.ce
        dfont=self.dfont
        e=self.e
        fdial=self.fdial
        dial=self.dial
        min_value=self.min_value
        unitspertick=self.unitspertick
        start_scale=self.start_scale

        if val==oldval:
            pass
        elif val<oldval:
            for x in range((val-min_value)//unitspertick+1,
                (oldval-min_value)//unitspertick+1):
                angle=x*self.tick_extent*unitspertick + start_scale
                self.delete(dial+'_'+str(int(angle-start_scale)))

        else:
            for j in range(((oldval-min_value)//unitspertick),
                        ((val-min_value)//unitspertick+1)):
                angle=j*self.tick_extent*unitspertick + start_scale
                tag=(dial+'_'+str(int(angle-start_scale)))
                tk_tick(self,ce,re-18*e,8*e,angle,fill=fdial,
                    width=self.tick_width*self.e,tags=tag)

        # display draw_text needs 3 or 4 letters or spaces
        sval=str(val)
        self.delete(dial)
        if self.max_value != 1023:
            pinput=(3-len(sval))*' '+sval
            tk_text(self,pinput,ce,0,0,fill=fdial,font=dfont,
                size=60,tags=dial)

        else:
            pinput=(4-len(sval))*' '+sval
            tk_text(self,pinput,ce,0,0,fill=fdial,font=dfont,
                size=45,tags=dial)

if __name__ == '__main__':
    root = Tk()
    winsys = root.tk.call("tk", "windowingsystem")
    BASELINE = 1.33398982438864281 if winsys != 'aqua' else 1.000492368291482
    scaling = root.tk.call("tk", "scaling")
    enlargement = int(scaling / BASELINE + 0.5)
    fr=Frame(root)
    fr.grid(row=0,column=0,sticky='nsew')
    l1=LCD(fr,select=4,enlargement=enlargement,colour='red',unit='Luminosity')
    l1.grid(row=0,column=0,sticky='nsew')
    l2=LCD(fr,select=4,enlargement=enlargement,colour='blue',unit='Luminosity')
    l2.grid(row=0,column=1,sticky='nsew')
    oldval=1023//2
    val=100
    l1.lcd_pointer(val,oldval)
    l2.lcd_pointer(val,oldval)

    root.mainloop()

Finally import this into a python script that runs with our trusty 2 LDRs:-

Show/Hide Code run_lcd_tk.py

from tkinter import Tk,Frame

import serial
from lcd_tk_dpi import LCD

oldq0=511
oldq1=511

def Packet(oldq0,oldq1):
    # Read the newest output from the Arduino
    dataPacket=ser.readline()
    dataPacket=str(dataPacket,'utf-8')
    splitPacket=dataPacket.split(" ")
    q0=int(splitPacket[0])
    q1=int(splitPacket[1])
    l1.lcd_pointer(q0,oldq0)
    l2.lcd_pointer(q1,oldq1)
    return q0,q1

root = Tk()
fr=Frame(root)
fr.grid(row=0,column=0,sticky='nsew')

l1=LCD(fr,select=4,enlargement=2,colour='red',unit='Luminosity')
l1.grid(row=0,column=0,sticky='nsew')
l2=LCD(fr,select=4,enlargement=2,colour='green',unit='Luminosity')
l2.grid(row=0,column=1,sticky='nsew')


ser = serial.Serial('com3', 9600)

while 1:

    q0,q1=Packet(oldq0,oldq1)

    oldq0=q0
    oldq1=q1
    root.update() # no root.mainloop()

If all goes well we should see the following, change the lighting and see how the display and larger ticks change. The gauge colour should match the led colour.

2 digital gauges drawn in tkinter

Tkinter Digital Gauges matching the LED Colours#