step 2: add gauge state sizing and animation model
This commit is contained in:
150
src/dpg_gauges/gauges.py
Normal file
150
src/dpg_gauges/gauges.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Pure gauge configuration dataclasses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import Any, Literal
|
||||
|
||||
from .scales import validate_markers, validate_range, validate_thresholds
|
||||
from .themes import GaugeStyle, GaugeTheme, get_theme, merge_style
|
||||
from .types import (
|
||||
AnimationConfig,
|
||||
Callback,
|
||||
GaugeMarker,
|
||||
GaugeValue,
|
||||
SmoothingConfig,
|
||||
StatusState,
|
||||
Tag,
|
||||
ThresholdBand,
|
||||
ValueFormatter,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_animation(value: AnimationConfig | bool | None) -> AnimationConfig | None:
|
||||
if isinstance(value, bool):
|
||||
return AnimationConfig(enabled=value)
|
||||
return value
|
||||
|
||||
|
||||
def _normalize_smoothing(value: SmoothingConfig | bool | None) -> SmoothingConfig | None:
|
||||
if isinstance(value, bool):
|
||||
return SmoothingConfig(enabled=value)
|
||||
return value
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GaugeConfig:
|
||||
tag: Tag | None = None
|
||||
label: str | None = None
|
||||
value: GaugeValue = None
|
||||
min_value: float = 0.0
|
||||
max_value: float = 100.0
|
||||
unit: str = ""
|
||||
precision: int = 0
|
||||
width: int = 0
|
||||
height: int = 0
|
||||
autosize_x: bool = False
|
||||
autosize_y: bool = False
|
||||
show: bool = True
|
||||
show_label: bool = True
|
||||
show_value: bool = True
|
||||
show_unit: bool = True
|
||||
tooltip: str | None = None
|
||||
callback: Callback | None = None
|
||||
user_data: Any = None
|
||||
theme: str | GaugeTheme | None = None
|
||||
style: GaugeStyle | None = None
|
||||
animation: AnimationConfig | bool | None = None
|
||||
smoothing: SmoothingConfig | bool | None = None
|
||||
clamp: bool = True
|
||||
format_value: ValueFormatter | None = None
|
||||
thresholds: Sequence[ThresholdBand] = ()
|
||||
markers: Sequence[GaugeMarker] = ()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
validate_range(self.min_value, self.max_value)
|
||||
validate_thresholds(self.thresholds, self.min_value, self.max_value)
|
||||
validate_markers(self.markers, self.min_value, self.max_value)
|
||||
if self.precision < 0:
|
||||
msg = "precision must be non-negative"
|
||||
raise ValueError(msg)
|
||||
object.__setattr__(self, "theme", get_theme(self.theme))
|
||||
object.__setattr__(self, "style", self.style or get_theme(self.theme).style)
|
||||
object.__setattr__(self, "animation", _normalize_animation(self.animation))
|
||||
object.__setattr__(self, "smoothing", _normalize_smoothing(self.smoothing))
|
||||
object.__setattr__(self, "thresholds", tuple(self.thresholds))
|
||||
object.__setattr__(self, "markers", tuple(self.markers))
|
||||
|
||||
def with_style_overrides(self, **overrides: Any) -> GaugeConfig:
|
||||
return replace(self, style=merge_style(self.style, **overrides))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AnalogGaugeConfig(GaugeConfig):
|
||||
start_angle: float = 225.0
|
||||
end_angle: float = -45.0
|
||||
major_ticks: int = 8
|
||||
minor_ticks: int = 4
|
||||
show_tick_labels: bool = True
|
||||
needle_length: float = 0.82
|
||||
needle_width: float = 3.0
|
||||
center_cap_radius: float = 7.0
|
||||
arc_width: float = 8.0
|
||||
maintain_aspect: bool = True
|
||||
redline_start: float | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DigitalGaugeConfig(GaugeConfig):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BarGaugeConfig(GaugeConfig):
|
||||
orientation: Literal["horizontal", "vertical"] = "horizontal"
|
||||
fill_mode: Literal["clamp", "centered"] = "clamp"
|
||||
corner_radius: float = 4.0
|
||||
show_scale: bool = True
|
||||
stretch: bool = True
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LevelGaugeConfig(BarGaugeConfig):
|
||||
orientation: Literal["horizontal", "vertical"] = "vertical"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StatusLightConfig(GaugeConfig):
|
||||
state: str | bool | int = False
|
||||
states: Mapping[Any, StatusState] | None = None
|
||||
blink: bool = False
|
||||
pulse: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SegmentedGaugeConfig(BarGaugeConfig):
|
||||
segments: int = 10
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CompassGaugeConfig(AnalogGaugeConfig):
|
||||
min_value: float = 0.0
|
||||
max_value: float = 360.0
|
||||
start_angle: float = 90.0
|
||||
end_angle: float = -270.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ThermometerGaugeConfig(LevelGaugeConfig):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BatteryGaugeConfig(LevelGaugeConfig):
|
||||
charging: bool | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MultiValueGaugeConfig(GaugeConfig):
|
||||
values: Mapping[str, GaugeValue] | None = None
|
||||
Reference in New Issue
Block a user