Use f-strings Avoid unnecessary casts to str()bool()int() Ensure file closure Merge nested ifs Simplify for-assigns-appends with comprehensions and internal functions Avoid shadowing internal function names Initialize variables Return value directly instead of assign then return Make some methods static
192 lines
6.5 KiB
Python
192 lines
6.5 KiB
Python
import datetime
|
|
import gi
|
|
import logging
|
|
import math
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gdk, Gtk
|
|
|
|
|
|
class HeaterGraph(Gtk.DrawingArea):
|
|
def __init__(self, printer, font_size):
|
|
super().__init__()
|
|
self.set_hexpand(True)
|
|
self.set_vexpand(True)
|
|
self.get_style_context().add_class('heatergraph')
|
|
self.printer = printer
|
|
self.store = {}
|
|
self.max_length = 0
|
|
self.connect('draw', self.draw_graph)
|
|
self.add_events(Gdk.EventMask.TOUCH_MASK)
|
|
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
|
self.connect('touch-event', self.event_cb)
|
|
self.connect('button_press_event', self.event_cb)
|
|
self.font_size = round(font_size * 0.75)
|
|
|
|
def add_object(self, name, ev_type, rgb=None, dashed=False, fill=False):
|
|
if rgb is None:
|
|
rgb = [0, 0, 0]
|
|
if name not in self.store:
|
|
self.store.update({name: {"show": True}})
|
|
self.store[name].update({ev_type: {
|
|
"dashed": dashed,
|
|
"fill": fill,
|
|
"rgb": rgb
|
|
}})
|
|
self.max_length = max(self.max_length, len(self.printer.get_temp_store(name, ev_type)))
|
|
|
|
@staticmethod
|
|
def event_cb(da, ev):
|
|
if ev.ev_type == Gdk.EventType.BUTTON_PRESS:
|
|
x = ev.x
|
|
y = ev.y
|
|
logging.info(f"Graph area: {x} {y}")
|
|
|
|
def get_max_length(self):
|
|
return min(len(self.printer.get_temp_store(name, "temperatures"))
|
|
for name in self.store if "temperatures" in self.store[name])
|
|
|
|
def get_max_num(self, data_points=0):
|
|
mnum = []
|
|
for x in self.store:
|
|
mnum.extend(max(self.printer.get_temp_store(x, t, data_points)) for t in self.store[x] if t != "show")
|
|
return max(mnum)
|
|
|
|
def draw_graph(self, da, ctx):
|
|
width = da.get_allocated_width()
|
|
height = da.get_allocated_height()
|
|
|
|
g_width_start = round(self.font_size * 2.75)
|
|
g_width = width - 15
|
|
g_height_start = 10
|
|
g_height = height - self.font_size * 2
|
|
|
|
ctx.set_source_rgb(.5, .5, .5)
|
|
ctx.set_line_width(1)
|
|
ctx.set_tolerance(0.1)
|
|
|
|
ctx.move_to(g_width_start, g_height_start)
|
|
ctx.line_to(g_width, g_height_start)
|
|
ctx.line_to(g_width, g_height)
|
|
ctx.line_to(g_width_start, g_height)
|
|
ctx.line_to(g_width_start, g_height_start)
|
|
ctx.stroke()
|
|
|
|
ctx.set_source_rgb(1, 0, 0)
|
|
ctx.move_to(g_width_start, height)
|
|
|
|
gsize = [
|
|
[g_width_start, g_height_start],
|
|
[g_width, g_height]
|
|
]
|
|
|
|
self.max_length = self.get_max_length()
|
|
graph_width = gsize[1][0] - gsize[0][0]
|
|
points_per_pixel = self.max_length / graph_width
|
|
data_points = int(round(graph_width * points_per_pixel, 0))
|
|
max_num = math.ceil(self.get_max_num(data_points) * 1.1 / 10) * 10
|
|
d_width = 1 / points_per_pixel
|
|
|
|
d_height_scale = self.graph_lines(ctx, gsize, max_num)
|
|
self.graph_time(ctx, gsize, points_per_pixel)
|
|
|
|
for name in self.store:
|
|
if not self.store[name]['show']:
|
|
continue
|
|
for dev_type in self.store[name]:
|
|
d = self.printer.get_temp_store(name, dev_type, data_points)
|
|
if d is False:
|
|
continue
|
|
self.graph_data(ctx, d, gsize, d_height_scale, d_width, self.store[name][dev_type]["rgb"],
|
|
self.store[name][dev_type]["dashed"], self.store[name][dev_type]["fill"])
|
|
|
|
@staticmethod
|
|
def graph_data(ctx, data, gsize, hscale, swidth, rgb, dashed=False, fill=False):
|
|
i = 0
|
|
ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], 1)
|
|
ctx.move_to(gsize[0][0] + 1, gsize[0][1] - 1)
|
|
if dashed:
|
|
ctx.set_dash([10, 5])
|
|
else:
|
|
ctx.set_dash([1, 0])
|
|
d_len = len(data) - 1
|
|
for d in data:
|
|
p_x = i * swidth + gsize[0][0] if i != d_len else gsize[1][0] - 1
|
|
p_y = gsize[1][1] - 1 - (d * hscale)
|
|
if i == 0:
|
|
ctx.move_to(gsize[0][0] + 1, p_y)
|
|
i += 1
|
|
continue
|
|
ctx.line_to(p_x, p_y)
|
|
i += 1
|
|
if fill is False:
|
|
ctx.stroke()
|
|
return
|
|
|
|
ctx.stroke_preserve()
|
|
ctx.line_to(gsize[1][0] - 1, gsize[1][1] - 1)
|
|
ctx.line_to(gsize[0][0] + 1, gsize[1][1] - 1)
|
|
if fill:
|
|
ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], .1)
|
|
ctx.fill()
|
|
|
|
def graph_lines(self, ctx, gsize, max_num):
|
|
nscale = 10
|
|
while (max_num / nscale) > 5:
|
|
nscale += 10
|
|
# nscale = math.floor((max_num / 10) / 4) * 10
|
|
r = int(max_num / nscale) + 1
|
|
hscale = (gsize[1][1] - gsize[0][1]) / (r * nscale)
|
|
|
|
for i in range(r):
|
|
ctx.set_source_rgb(.5, .5, .5)
|
|
lheight = gsize[1][1] - nscale * i * hscale
|
|
ctx.move_to(6, lheight + 3)
|
|
ctx.set_font_size(self.font_size)
|
|
ctx.show_text(str(nscale * i).rjust(3, " "))
|
|
ctx.stroke()
|
|
ctx.set_source_rgba(.5, .5, .5, .2)
|
|
ctx.move_to(gsize[0][0], lheight)
|
|
ctx.line_to(gsize[1][0], lheight)
|
|
ctx.stroke()
|
|
return hscale
|
|
|
|
def graph_time(self, ctx, gsize, points_per_pixel):
|
|
|
|
now = datetime.datetime.now()
|
|
first = gsize[1][0] - (now.second + ((now.minute % 2) * 60)) / points_per_pixel
|
|
steplen = 120 / points_per_pixel # For 120s
|
|
|
|
i = 0
|
|
while True:
|
|
x = first - i * steplen
|
|
if x < gsize[0][0]:
|
|
break
|
|
ctx.set_source_rgba(.5, .5, .5, .2)
|
|
ctx.move_to(x, gsize[0][1])
|
|
ctx.line_to(x, gsize[1][1])
|
|
ctx.stroke()
|
|
|
|
ctx.set_source_rgb(.5, .5, .5)
|
|
ctx.move_to(x - round(self.font_size * 1.5), gsize[1][1] + round(self.font_size * 1.5))
|
|
|
|
h = now.hour
|
|
m = now.minute - (now.minute % 2) - i * 2
|
|
if m < 0:
|
|
h -= 1
|
|
m += 60
|
|
if h < 0:
|
|
h += 24
|
|
ctx.set_font_size(self.font_size)
|
|
ctx.show_text(f"{h:2}:{m:02}")
|
|
ctx.stroke()
|
|
i += 1 if self.max_length < 600 else 2
|
|
|
|
def is_showing(self, device):
|
|
return False if device not in self.store else self.store[device]['show']
|
|
|
|
def set_showing(self, device, show=True):
|
|
if device not in self.store:
|
|
return
|
|
self.store[device]['show'] = show
|