Coverage for src / lexigram / admin / dashboard / chart_widget.py: 21%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
1from __future__ import annotations
3from typing import Any
5from lexigram.ui import (
6 AreaChart,
7 BarChart,
8 ChartConfig,
9 ChartDataPoint,
10 ChartType,
11 Component,
12 LineChart,
13 PieChart,
14 el,
15)
17_CHART_TYPE_MAP: dict[ChartType, type[Component]] = {
18 ChartType.BAR: BarChart,
19 ChartType.LINE: LineChart,
20 ChartType.PIE: PieChart,
21 ChartType.AREA: AreaChart,
22}
24_COL_SPAN_MAP = {1: "", 2: "lg:col-span-2", 3: "lg:col-span-3", 4: "lg:col-span-4"}
27class ChartWidget(Component):
28 def __init__(
29 self,
30 title: str,
31 chart_type: ChartType,
32 data: list[ChartDataPoint] | None = None,
33 *,
34 chart_config: ChartConfig | None = None,
35 data_source: str | None = None,
36 refresh_interval: int | None = None,
37 description: str = "",
38 col_span: int = 1,
39 ) -> None:
40 super().__init__()
41 self.title = title
42 self.chart_type = chart_type
43 self.data = data or []
44 self.chart_config = chart_config or ChartConfig()
45 self.data_source = data_source
46 self.refresh_interval = refresh_interval
47 self.description = description
48 self.col_span = col_span
50 def render(self) -> Any:
51 span = _COL_SPAN_MAP.get(self.col_span, "")
53 header = [
54 el(
55 "h3",
56 self.title,
57 class_="text-sm font-semibold text-foreground",
58 ),
59 ]
60 if self.description:
61 header.append(
62 el(
63 "p",
64 self.description,
65 class_="text-xs text-muted-foreground mt-0.5",
66 )
67 )
69 hx_attrs: dict[str, Any] = {}
70 if self.data_source:
71 triggers = ["load"]
72 if self.refresh_interval and self.refresh_interval > 0:
73 triggers.append(f"every {self.refresh_interval * 1000}ms")
74 hx_attrs["hx-get"] = self.data_source
75 hx_attrs["hx-trigger"] = ", ".join(triggers)
76 hx_attrs["hx-swap"] = "innerHTML"
78 body: Component
79 if self.data:
80 chart_cls = _CHART_TYPE_MAP.get(self.chart_type)
81 if chart_cls:
82 body = chart_cls(self.data, self.chart_config)
83 else:
84 body = el(
85 "div",
86 "Unsupported chart type",
87 class_="text-sm text-muted-foreground text-center py-8",
88 )
89 elif self.data_source:
90 body = el(
91 "div",
92 el("div", class_="h-4 bg-muted rounded w-3/4 mb-2"),
93 el("div", class_="h-4 bg-muted rounded w-1/2 mb-2"),
94 class_="animate-pulse py-2",
95 )
96 else:
97 body = el(
98 "div",
99 "No data",
100 class_="text-sm text-muted-foreground text-center py-8",
101 )
103 return el(
104 "div",
105 el("div", *header, class_="mb-4"),
106 body,
107 class_=f"bg-card rounded-xl shadow-sm border border-border p-5 {span}".strip(),
108 **hx_attrs,
109 )