hexagram/py/hexagram/dial.py
2024-01-09 12:57:16 -05:00

150 lines
4.7 KiB
Python

import math
import cairo
from hexagram.gauge import Gauge
RAD = math.pi / 180.0
class Dial(Gauge):
__slots = 'x', 'y', 'radius', 'min_angle', 'max_angle',
ANGLE_OFFSET = math.pi / 2
BEZEL_WIDTH = 16
BEZEL_GRADIENT_STOPS = (
(0, 1.0, 0.4, 1.0, 1.0),
(1, 0.4, 0.0, 0.4, 0.0)
)
def __init__(self, x: float, y: float, radius: float, min_angle: float, max_angle: float, min_value: float, max_value: float):
super().__init__(min_value, max_value)
self.x = x
self.y = y
self.radius = radius
self.min_angle = min_angle
self.max_angle = max_angle
def _gradient(self):
gradient = cairo.LinearGradient(self.x - self.radius,
self.y - self.radius,
self.x + self.radius,
self.y + self.radius)
for stop in self.BEZEL_GRADIENT_STOPS:
gradient.add_color_stop_rgba(*stop)
return gradient
def angle(self, value: float):
adj_max = self.max_value - self.min_value
adj = min(adj_max, value - self.min_value)
return self.min_angle + \
(self.max_angle - self.min_angle) * (adj / adj_max) \
- self.ANGLE_OFFSET
def draw_legend(self, cr: cairo.Context, radius: float, value: float, text: str):
angle = self.angle(value)
extents = cr.text_extents(text)
width = extents[2] - extents[0]
height = extents[3] - extents[1]
cr.move_to(self.x + radius * self.radius * math.cos(angle) - width / 1.5,
self.y + radius * self.radius * math.sin(angle) + height / 4)
cr.show_text(text)
cr.stroke()
def draw_mark(self, cr: cairo.Context, min_radius: float, max_radius: float, value: float):
angle = self.angle(value)
cr.move_to(self.x + (min_radius * self.radius) * math.cos(angle),
self.y + (min_radius * self.radius) * math.sin(angle))
cr.line_to(self.x + (max_radius * self.radius) * math.cos(angle),
self.y + (max_radius * self.radius) * math.sin(angle))
cr.stroke()
def draw_needle(self, cr: cairo.Context, min_radius: float, max_radius: float, angle: float):
cr.set_source_rgba(1, 0.4, 1, 0.75)
cr.set_line_width(8)
cr.move_to(self.x + (min_radius * self.radius) * math.cos(angle),
self.y + (min_radius * self.radius) * math.sin(angle))
cr.line_to(self.x + (max_radius * self.radius) * math.cos(angle),
self.y + (max_radius * self.radius) * math.sin(angle))
cr.stroke()
def draw_bg(self, cr: cairo.Context):
arc = (self.x,
self.y,
self.radius - self.BEZEL_WIDTH,
0,
2.0 * math.pi)
# Gauge face
cr.set_source_rgba(0, 0, 0, 1)
cr.arc(*arc)
cr.fill()
# Gauge bezel
cr.set_source(self._gradient())
cr.set_line_width(self.BEZEL_WIDTH)
cr.arc(*arc)
cr.stroke()
# Gauge center bezel
cr.set_source_rgb(1, 1, 1)
cr.set_line_width(self.BEZEL_WIDTH / 3)
cr.arc(self.x, self.y, self.radius / 2, 0, 2.0 * math.pi)
cr.stroke()
def draw_fg(self, cr: cairo.Context):
angle = self.angle(self.value)
cr.set_line_width(self.radius * 0.375)
cr.set_source_rgba(1, 0.4, 1, 0.25)
cr.arc(self.x, self.y, self.radius * 0.713,
self.min_angle - self.ANGLE_OFFSET - RAD * 1.5,
angle)
cr.stroke()
self.draw_needle(cr, 0.5, 0.89, angle)
class BottomDial(Dial):
MIN_ANGLE = 144.0 * RAD
MAX_ANGLE = 216.0 * RAD
def angle(self, value: float):
return super().angle((self.max_value - value) + self.min_value)
def draw_legend(self, cr: cairo.Context, radius: float, value: float, text: str, x_offset: float=0.0, y_offset: float=0.0):
angle = self.angle(value)
cr.move_to(self.x + radius * self.radius * math.cos(angle) + x_offset,
self.y + radius * self.radius * math.sin(angle) + y_offset)
cr.show_text(text)
cr.stroke()
def draw_bg(self, cr: cairo.Context):
cr.set_line_width(24)
cr.set_source_rgb(0.2, 0.2, 0.2)
cr.arc(self.x,
self.y,
self.radius - 36,
self.MIN_ANGLE - self.ANGLE_OFFSET - 4 * RAD,
self.MAX_ANGLE - self.ANGLE_OFFSET + 4 * RAD)
cr.stroke()
def draw_fg(self, cr: cairo.Context):
min_radius = 0.8
max_radius = 0.9
self.draw_needle(cr, min_radius, max_radius, self.angle(self.value))