feat(widgets): scale chart axes/tooltips with unit + scale options
Consistent graph scaling across every line-chart widget. A new shared frontend/src/lib/metricFormat.ts picks a decimal prefix (kB/MB/GB, kbps/Mbps, Gbps, …) from the series magnitude and formats values; LineSeriesChart accepts unit + scale and formats both the Y-axis ticks and the tooltip with the SAME prefix (one consistent unit per axis). MetricChartWidget (Prometheus) and QbittorrentSpeedWidget pass the widget config through; qBit speed defaults to bytes/sec → MB/s. WidgetConfigDialog now renders `enum` schema fields as a <Select> dropdown, so the backend's unit/scale Literal enums become consistent pickers in every graph widget's config (and any future enum option). Decimal (x1000) prefixes by default (matches Mbps/MB/s/Grafana). Tests: 13 new metricFormat tests (auto/fixed scaling, percent, seconds, nulls, trailing-zero trimming). 179/179 frontend tests pass; tsc + ESLint clean.
This commit is contained in:
@@ -7,6 +7,12 @@ import {
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import {
|
||||
type MetricUnit,
|
||||
type MetricScale,
|
||||
metricScaleInfo,
|
||||
formatScaled,
|
||||
} from "../lib/metricFormat";
|
||||
|
||||
export interface SeriesPoint {
|
||||
t: number;
|
||||
@@ -51,13 +57,32 @@ const CHART_COLORS = [
|
||||
interface LineSeriesChartProps {
|
||||
series: ChartSeries[];
|
||||
height?: number;
|
||||
/** Display unit for the Y axis + tooltip (drives decimal-prefix scaling). */
|
||||
unit?: MetricUnit;
|
||||
/** "auto" picks a prefix from the data magnitude; k/m/g/t force one. */
|
||||
scale?: MetricScale;
|
||||
}
|
||||
|
||||
/** Shared recharts line-chart renderer used by PrometheusChart + qBit speed widgets. */
|
||||
export function LineSeriesChart({
|
||||
series,
|
||||
height = 300,
|
||||
unit = "none",
|
||||
scale = "auto",
|
||||
}: LineSeriesChartProps) {
|
||||
// Choose ONE (divisor, suffix) from the series magnitude so the axis and
|
||||
// tooltip stay consistent (e.g. all values shown in MB/s).
|
||||
const maxAbs = series.reduce((m, s) => {
|
||||
for (const p of s.points) {
|
||||
const v = p.v == null ? 0 : Math.abs(p.v);
|
||||
if (v > m) m = v;
|
||||
}
|
||||
return m;
|
||||
}, 0);
|
||||
const scaleInfo = metricScaleInfo(maxAbs, unit, scale);
|
||||
const fmt = (v: number | null | undefined) =>
|
||||
formatScaled(v, scaleInfo, unit);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
<LineChart data={mergeSeries(series)}>
|
||||
@@ -68,9 +93,15 @@ export function LineSeriesChart({
|
||||
tick={{ fontSize: 11 }}
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<YAxis tick={{ fontSize: 11 }} className="fill-muted-foreground" />
|
||||
<YAxis
|
||||
tickFormatter={fmt}
|
||||
tick={{ fontSize: 11 }}
|
||||
width={56}
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<Tooltip
|
||||
labelFormatter={(label) => formatTime(Number(label))}
|
||||
formatter={(value) => fmt(Number(value))}
|
||||
contentStyle={{
|
||||
backgroundColor: "var(--color-popover)",
|
||||
border: "1px solid var(--color-border)",
|
||||
|
||||
Reference in New Issue
Block a user