Search code examples
pythontkintertrigonometry

Drawing and Updating a Speedometer Needle


I am attempting to draw a speedometer using a Tkinter Canvas in Python and am having a few problems with my code that I can't seem to figure out. First off, here is what I have written:

import tkinter as tk
from tkinter import ttk
import math

class DrawMeter(tk.Canvas):

    def __init__(self, parent, *args, **kwargs):
        tk.Canvas.__init__(self, parent, *args, **kwargs)
        self.config(bg = "grey")
        if (int(self['height']) * 2 > int(self['width'])):
            boxSide = int(self['width'])
        else:
            boxSide = int(self['height']) * 2
        self.boxX = boxSide / 2
        self.boxY = boxSide / 2
        self.boxRadius = int(0.40 * float(boxSide))
        self.start = 0
        self.end = 1

        self.drawBackground()
        self.drawTicks()
        self.drawNeedle()

    def drawBackground(self):
        bgColour = "black"
        self.create_arc((self.boxX - self.boxRadius,
                         self.boxY - self.boxRadius,
                         self.boxX * 4,
                         self.boxY * 4),
                        fill = bgColour, start = 90)

    def drawTicks(self):
        length = self.boxRadius / 8
        for deg in range(5, 85, 6):
            rad = math.radians(deg)
            self.Tick(rad, length)
        for deg in range(5, 91, 18):
            rad = math.radians(deg)
            self.Tick(rad, length * 2)

    def Tick(self, angle, length):
        cos = math.cos(angle)
        sin = math.sin(angle)
        radius = self.boxRadius * 2
        X = self.boxX * 2
        Y = self.boxY * 2
        self.create_line((X - radius * cos,
                          Y - radius * sin,
                          X - (radius - length) * cos,
                          Y - (radius - length) * sin),
                         fill = "white", width = 2)

    def drawText(self, start = 0, end = 100):
        interval = end / 5
        value = start
        length = self.boxRadius / 2
        for deg in range(5, 91, 18):
            rad = math.radians(deg)
            cos = math.cos(rad)
            sin = math.sin(rad)
            radius = self.boxRadius * 2
            self.create_text(self.boxX * 2 - (radius - length - 1) * cos,
                             self.boxY * 2 - (radius - length - 1) * sin,
                             text = str("{0:.1f}".format(value)),
                             fill = "white",
                             font = ("Arial", 12, "bold"))
            value = value + interval

    def setRange(self, start, end):
        self.start = start
        self.end = end
        self.drawText(start, end)

    def drawNeedle(self):
        X = self.boxX * 2
        Y = self.boxY * 2
        length = self.boxRadius - (self.boxRadius / 4)
        self.meterHand = self.create_line(X / 2, Y / 2, X + length, Y + length,
                                          fill = "red", width = 4)
        self.create_arc(X - 30, Y - 30, X + 30, Y + 30,
                        fill = "#c0c0c0", outline = "#c0c0c0", start = 90)

    def updateNeedle(self, value):
        length = self.boxRadius - (self.boxRadius / 4)
        deg = 80 * (value - self.start) / self.end - 180
        rad = math.radians(deg)
        self.coords(self.meterHand, self.boxX * 2, self.boxY * 2,
                    self.boxX + length * math.cos(rad),
                    self.boxY + length * math.sin(rad))

value = 0

def update_frame():
    global value
    if value < 1:
        value = value + 0.01
    print(value)
    meter.updateNeedle(value)
    container.after(200, update_frame)

root = tk.Tk()
container = tk.Frame(root)
container.pack()
meter = DrawMeter(container, height = 200, width = 200, bg = "red")
meter.setRange(0, 1)
meter.pack()
update_frame()
root.mainloop()

So the problem is I am having is that my needle is drawing and updating properly on the screen. When I start the program, the needle starts at around 0.2ish, and goes until about 0.6 and then stop. I feel like my formula for calculating the needle's position is wrong, but I am not sure what about it is wrong.

The way I have it set up, is it takes the the percentage of the total the value is (value = self.start) / self.end) and multiplies it by 80 degrees (because my speedometer starts at the 5 degree marks and ends at 85) and them subtracts 180 so that the number makes the number go clockwise, not counter clockwise.

My placement of the canvas objects could also be off. My attempt was to set up a Speedometer that is a quarter circle at the bottom of the Canvas. However when you use the Canvas create_arc function, it draws the bottom right corner of your arc, in the center of the bounding box, meaning you make it the bottom right corner, I need to make by bounding box double the width and height of the canvas. I'm thinking maybe that threw me off a bit as well.


Solution

  • My problem was that I needed to create an offset value and add it to all four points in the Canvas.coords call.