Commit 5c7d7b62 authored by Roman Alifanov's avatar Roman Alifanov

widgets: add info_graph_large and info_graph_multi

parent 64da4d68
from gi.repository import Adw, Gtk
from .BaseWidget import BaseWidget
class InfoGraphLargeWidget(BaseWidget):
def __init__(self, setting):
super().__init__(setting)
self.history = []
self.max_points = setting.map.get('points', 60) if setting.map else 60
self.min_value = setting.map.get('min', 0) if setting.map else 0
self.max_value = setting.map.get('max', 100) if setting.map else 100
self.color = setting.map.get('color', '#3584e4') if setting.map else '#3584e4'
self.suffix = setting.map.get('suffix', '%') if setting.map else '%'
self.show_max = setting.map.get('show_max', False) if setting.map else False
self.max_seen = self.min_value
def create_row(self):
self.box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=0
)
self.drawing_area = Gtk.DrawingArea()
self.drawing_area.set_size_request(-1, 120)
self.drawing_area.set_hexpand(True)
self.drawing_area.set_draw_func(self._draw_graph)
label_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=2,
margin_start=12,
margin_end=12,
margin_top=8,
margin_bottom=12
)
self.title_label = Gtk.Label(
label=self.setting.name,
xalign=0,
css_classes=["dim-label", "caption"]
)
self.value_label = Gtk.Label(
label="--",
xalign=0,
css_classes=["title-3"]
)
label_box.append(self.title_label)
label_box.append(self.value_label)
self.box.append(self.drawing_area)
self.box.append(label_box)
self.row = Gtk.ListBoxRow(
selectable=False,
activatable=False
)
self.row.set_child(self.box)
return self.row
def _parse_color(self, color_str):
if color_str.startswith('#'):
color_str = color_str[1:]
r = int(color_str[0:2], 16) / 255.0
g = int(color_str[2:4], 16) / 255.0
b = int(color_str[4:6], 16) / 255.0
return r, g, b
def _draw_graph(self, area, cr, width, height):
r, g, b = self._parse_color(self.color)
cr.set_source_rgba(r, g, b, 0.08)
cr.rectangle(0, 0, width, height)
cr.fill()
if len(self.history) < 2:
return
value_range = self.max_value - self.min_value
if value_range == 0:
value_range = 1
points = []
padding = 0
graph_height = height - padding * 2
step = width / (self.max_points - 1)
start_x = width - (len(self.history) - 1) * step
for i, value in enumerate(self.history):
x = start_x + i * step
normalized = (value - self.min_value) / value_range
normalized = max(0, min(1, normalized))
y = height - padding - (normalized * graph_height)
points.append((x, y))
cr.set_source_rgba(r, g, b, 0.25)
cr.move_to(points[0][0], height)
cr.line_to(points[0][0], points[0][1])
for x, y in points[1:]:
cr.line_to(x, y)
cr.line_to(points[-1][0], height)
cr.close_path()
cr.fill()
cr.set_source_rgba(r, g, b, 0.8)
cr.set_line_width(1.5)
cr.move_to(points[0][0], points[0][1])
for x, y in points[1:]:
cr.line_to(x, y)
cr.stroke()
def update_display(self):
value = self.setting._current_value
if value is not None:
try:
value = float(value)
self.history.append(value)
if len(self.history) > self.max_points:
self.history.pop(0)
if value > self.max_seen:
self.max_seen = value
if self.show_max:
self.value_label.set_label(
f"{value:.0f}{self.suffix} \u00b7 Max: {self.max_seen:.0f}{self.suffix}"
)
else:
self.value_label.set_label(f"{value:.0f}{self.suffix}")
self.drawing_area.queue_draw()
except (ValueError, TypeError):
pass
from gi.repository import Gtk
from .BaseWidget import BaseWidget
class InfoGraphMultiWidget(BaseWidget):
def __init__(self, setting):
super().__init__(setting)
self.graphs = {}
self.max_points = setting.map.get('points', 40) if setting.map else 40
self.min_value = setting.map.get('min', 0) if setting.map else 0
self.max_value = setting.map.get('max', 100) if setting.map else 100
self.color = setting.map.get('color', '#3584e4') if setting.map else '#3584e4'
self.suffix = setting.map.get('suffix', '%') if setting.map else '%'
self.columns = setting.map.get('columns', 3) if setting.map else 3
def create_row(self):
self.flow_box = Gtk.FlowBox(
homogeneous=True,
selection_mode=Gtk.SelectionMode.NONE,
margin_start=6,
margin_end=6,
margin_top=6,
margin_bottom=6,
row_spacing=6,
column_spacing=6
)
self.row = Gtk.ListBoxRow(
selectable=False,
activatable=False
)
self.row.set_child(self.flow_box)
return self.row
def _parse_color(self, color_str):
if color_str.startswith('#'):
color_str = color_str[1:]
r = int(color_str[0:2], 16) / 255.0
g = int(color_str[2:4], 16) / 255.0
b = int(color_str[4:6], 16) / 255.0
return r, g, b
def _create_graph_card(self, name):
box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=0,
css_classes=["card"],
width_request=150
)
drawing_area = Gtk.DrawingArea()
drawing_area.set_size_request(-1, 70)
drawing_area.set_hexpand(True)
label_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=2,
margin_start=10,
margin_end=10,
margin_top=6,
margin_bottom=10
)
title_label = Gtk.Label(
label=name,
xalign=0,
css_classes=["dim-label", "caption"]
)
value_label = Gtk.Label(
label="--",
xalign=0,
css_classes=["title-4"]
)
label_box.append(title_label)
label_box.append(value_label)
box.append(drawing_area)
box.append(label_box)
self.graphs[name] = {
'history': [],
'drawing_area': drawing_area,
'value_label': value_label,
'box': box
}
drawing_area.set_draw_func(lambda area, cr, w, h: self._draw_graph(cr, w, h, name))
return box
def _draw_graph(self, cr, width, height, name):
if name not in self.graphs:
return
history = self.graphs[name]['history']
r, g, b = self._parse_color(self.color)
cr.set_source_rgba(r, g, b, 0.1)
cr.rectangle(0, 0, width, height)
cr.fill()
if len(history) < 2:
return
value_range = self.max_value - self.min_value
if value_range == 0:
value_range = 1
points = []
padding_y = 4
graph_height = height - padding_y * 2
step = width / (self.max_points - 1)
start_x = width - (len(history) - 1) * step
for i, value in enumerate(history):
x = start_x + i * step
normalized = (value - self.min_value) / value_range
normalized = max(0, min(1, normalized))
y = height - padding_y - (normalized * graph_height)
points.append((x, y))
cr.set_source_rgba(r, g, b, 0.6)
cr.set_line_width(1.5)
cr.move_to(points[0][0], points[0][1])
for x, y in points[1:]:
cr.line_to(x, y)
cr.stroke()
def update_display(self):
data = self.setting._current_value
if not data or not isinstance(data, list):
return
for item in data:
if not isinstance(item, dict):
continue
name = item.get('name', '')
value = item.get('value')
subtitle = item.get('subtitle', '')
if not name or value is None:
continue
try:
value = float(value)
except (ValueError, TypeError):
continue
if name not in self.graphs:
card = self._create_graph_card(name)
self.flow_box.append(card)
graph = self.graphs[name]
graph['history'].append(value)
if len(graph['history']) > self.max_points:
graph['history'].pop(0)
if subtitle:
graph['value_label'].set_label(f"{value:.0f} {self.suffix} \u00b7 {subtitle}")
else:
graph['value_label'].set_label(f"{value:.0f} {self.suffix}")
graph['drawing_area'].queue_draw()
...@@ -10,6 +10,8 @@ from .ButtonWidget import ButtonWidget ...@@ -10,6 +10,8 @@ from .ButtonWidget import ButtonWidget
from .InfoLabelWidget import InfoLabelWidget from .InfoLabelWidget import InfoLabelWidget
from .InfoDictWidget import InfoDictWidget from .InfoDictWidget import InfoDictWidget
from .InfoGraphWidget import InfoGraphWidget from .InfoGraphWidget import InfoGraphWidget
from .InfoGraphLargeWidget import InfoGraphLargeWidget
from .InfoGraphMultiWidget import InfoGraphMultiWidget
from .DualListWidget import DualListWidget from .DualListWidget import DualListWidget
from .ImageChooserWidget import ImageChooserWidget from .ImageChooserWidget import ImageChooserWidget
from .DualImageChooserWidget import DualImageChooserWidget from .DualImageChooserWidget import DualImageChooserWidget
...@@ -33,6 +35,8 @@ class WidgetFactory: ...@@ -33,6 +35,8 @@ class WidgetFactory:
'info_label': InfoLabelWidget, 'info_label': InfoLabelWidget,
'info_dict': InfoDictWidget, 'info_dict': InfoDictWidget,
'info_graph': InfoGraphWidget, 'info_graph': InfoGraphWidget,
'info_graph_large': InfoGraphLargeWidget,
'info_graph_multi': InfoGraphMultiWidget,
'list_dual': DualListWidget, 'list_dual': DualListWidget,
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment