Skip to content

Instantly share code, notes, and snippets.

@israel-dryer
Created May 2, 2021 20:54
Show Gist options
  • Save israel-dryer/5914a9fa6c20a5329b94a463d046c089 to your computer and use it in GitHub Desktop.
Save israel-dryer/5914a9fa6c20a5329b94a463d046c089 to your computer and use it in GitHub Desktop.
Meter with needle
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw
from tkinter import StringVar, IntVar
from tkinter import ttk
from tkinter.ttk import Frame
class NeedleMeter(Frame):
def __init__(self,
master=None,
amounttotal=100,
amountused=0,
metersize=200,
meterthickness=10,
**kw):
super().__init__(master=master, **kw)
self.box = ttk.Frame(self, width=metersize, height=metersize)
self.arcoffset = -180
self.arcrange = 180
self.amountusedvariable = IntVar(value=amountused)
self.amounttotalvariable = IntVar(value=amounttotal)
self.amountusedvariable.trace_add('write', self.draw_needle)
self.towardsmaximum = True
self.metersize = metersize
self.meterthickness = meterthickness
self.metercolorsuccess = '#22b24c'
self.metercolorwarning = '#f5e625'
self.metercolordanger = '#f57a00'
self.needlecolor = '#272525'
# meter image
self.meter = ttk.Label(self.box)
self.draw_base_image()
self.draw_needle()
self.meter.place(x=0, y=0)
self.box.pack()
@property
def amountused(self):
return self.amountusedvariable.get()
@amountused.setter
def amountused(self, value):
self.amountusedvariable.set(value)
@property
def amounttotal(self):
return self.amounttotalvariable.get()
@amounttotal.setter
def amounttotal(self, value):
self.amounttotalvariable.set(value)
def draw_base_image(self):
"""Draw the base image to be used for subsequent updates"""
self.base_image = Image.new('RGBA', (self.metersize * 5, self.metersize * 5))
draw = ImageDraw.Draw(self.base_image)
# success
draw.arc((0, 0, self.metersize * 5 - 20, self.metersize * 5 - 20),
-180, -90, self.metercolorsuccess, self.meterthickness * 5)
# warning
draw.arc((0, 0, self.metersize * 5 - 20, self.metersize * 5 - 20),
-90, -45, self.metercolorwarning, self.meterthickness * 5)
# warning
draw.arc((0, 0, self.metersize * 5 - 20, self.metersize * 5 - 20),
-45, 0, self.metercolordanger, self.meterthickness * 5)
def draw_needle(self, *args):
"""Draw a meter
Args:
*args: if triggered by a trace, will be `variable`, `index`, `mode`.
"""
im = self.base_image.copy()
draw = ImageDraw.Draw(im)
scaled_xy = self.metersize * 5
scaled_half = scaled_xy // 2
draw.arc((40, 40, scaled_xy - 40, scaled_xy - 40),
self.meter_value() - 1, self.meter_value() + 1, self.needlecolor, scaled_xy)
needle_base_xy = (scaled_half - 20, scaled_half - 20, scaled_half + 20, scaled_half + 20)
draw.ellipse(needle_base_xy, fill=self.needlecolor)
self.meterimage = ImageTk.PhotoImage(im.resize((self.metersize, self.metersize), Image.CUBIC))
self.meter.configure(image=self.meterimage)
def meter_value(self):
"""Calculate the meter value
Returns:
int: the value to be used to draw the arc length of the progress meter
"""
return int((self.amountused / self.amounttotal) * self.arcrange + self.arcoffset)
def step(self, delta=1):
"""Increase the indicator value by ``delta``.
The default increment is 1. The indicator will reverse direction and count down once it reaches the maximum
value.
Keyword Args:
delta (int): the amount to change the indicator.
"""
if self.amountused >= self.amounttotal:
self.towardsmaximum = True
self.amountused = self.amountused - delta
elif self.amountused <= 0:
self.towardsmaximum = False
self.amountused = self.amountused + delta
elif self.towardsmaximum:
self.amountused = self.amountused - delta
else:
self.amountused = self.amountused + delta
if __name__ == '__main__':
def test(meter):
meter.step(1)
meter.after(1, test, meter)
m = NeedleMeter(meterthickness=25, padding=20)
m.pack()
test(m)
m.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment