diff --git a/src/features/stats/MeshTab.tsx b/src/features/stats/MeshTab.tsx index 2c4b5f1..5b4440c 100644 --- a/src/features/stats/MeshTab.tsx +++ b/src/features/stats/MeshTab.tsx @@ -2,7 +2,7 @@ import { useMemo } from "react"; import { formatCount } from "../../lib/formatters"; import { useChartColors, type ChartColors } from "./chartTheme"; import { useStatsOverview, useStatsObservations, usePayloadBreakdown, useTopNodes, useTopObservers, useRadioPresets, useScopes } from "./useStats"; -import { observationsAreaOption, leaderboardOption, typeBarOption, donutOption } from "./chartOptions"; +import { observationsAreaOption, leaderboardOption, typeBarOption } from "./chartOptions"; import { Card, ChartCard, StatCard } from "./cards"; import { useLiveOverview } from "./useLiveStats"; import { aggregatePresets, formatPreset } from "./transforms"; @@ -90,20 +90,6 @@ export function MeshTab({ range, onSelectObserver, wsManager }: MeshTabProps) { [observerIds, onSelectObserver], ); - const nodeTypeData = useMemo(() => { - const counts = new Map(); - for (const n of topNodes.data ?? []) counts.set(n.nodeTypeName, (counts.get(n.nodeTypeName) ?? 0) + 1); - return [...counts.entries()] - .map(([name, value]) => ({ name, value, color: nodeTypeColor(name, colors) })) - .sort((a, b) => b.value - a.value); - }, [topNodes.data, colors]); - const nodeTypeTotal = useMemo(() => nodeTypeData.reduce((a, d) => a + d.value, 0), [nodeTypeData]); - // few categories, so a donut reads fine here — payload types stays a bar chart (10+ categories) - const nodeTypeOption = useMemo( - () => donutOption(nodeTypeData, colors, String(nodeTypeTotal), "NODES"), - [nodeTypeData, nodeTypeTotal, colors], - ); - const presetRows = useMemo( () => aggregatePresets(radioPresets.data ?? []).slice(0, 8).map((r) => ({ name: formatPreset(r.preset), value: r.value, color: colors.primary })), [radioPresets.data, colors], @@ -151,7 +137,13 @@ export function MeshTab({ range, onSelectObserver, wsManager }: MeshTabProps) { isEmpty={payloadItems.length === 0} /> Top observers · {range}} height={208} option={observersOption} isLoading={topObservers.isLoading} isError={topObservers.isError} isEmpty={observerRows.length === 0} onEvents={observerEvents} /> - + {/* needs a /stats/node-types endpoint (ticket filed) — the old donut counted types among + the top-10 nodes only, which read as the region's whole population */} + +
+ Coming soon +
+
Scopes · all regions}> diff --git a/src/features/stats/chartOptions.ts b/src/features/stats/chartOptions.ts index b1c6d05..284fc94 100644 --- a/src/features/stats/chartOptions.ts +++ b/src/features/stats/chartOptions.ts @@ -131,57 +131,6 @@ export function leaderboardOption( }; } -// Donut for small category sets (node types — usually 3 or 4). The center total is a title block -// anchored at the ring's x with textAlign center, which ECharts centers properly — the old graphic -// text anchored its LEFT edge there and clipped against the ring. -export function donutOption( - items: { name: string; value: number; color?: string }[], - c: ChartColors, - centerValue: string, - centerLabel: string, -): EChartsOption { - return { - animation: false, - backgroundColor: "transparent", - tooltip: { trigger: "item", ...tooltipStyle(c), formatter: "{b}: {c} ({d}%)" }, - title: { - text: centerValue, - subtext: centerLabel, - // anchor the block's center exactly on the pie's center (35%, 50%) — textAlign/-VerticalAlign - // make left/top the anchor point instead of the block's top-left corner - left: "35%", - top: "50%", - textAlign: "center", - textVerticalAlign: "middle", - itemGap: 2, - textStyle: { color: c.textBright, fontFamily: MONO, fontSize: 21, fontWeight: 700 }, - subtextStyle: { color: c.textMuted, fontFamily: MONO, fontSize: 9 }, - }, - legend: { - orient: "vertical", - right: 10, - top: "middle", - itemWidth: 9, - itemHeight: 9, - itemGap: 7, - textStyle: { color: c.textNormal, fontFamily: MONO, fontSize: 10 }, - inactiveColor: c.textDim, - }, - series: [ - { - type: "pie", - radius: ["46%", "68%"], - center: ["35%", "50%"], - avoidLabelOverlap: false, - itemStyle: { borderColor: c.bgSurface, borderWidth: 2, borderRadius: 4 }, - label: { show: false }, - emphasis: { scaleSize: 5 }, - data: items.map((it, i) => ({ name: it.name, value: it.value, itemStyle: { color: it.color ?? c.series[i % c.series.length] } })), - }, - ], - }; -} - // Vertical bars for the payload-type breakdown. Replaced the old donut: with 10+ slivers the legend // needed scrolling, names truncated, and thin slices couldn't be compared by eye — bars label every // category inline and need no legend at all. diff --git a/src/features/stats/echarts-setup.ts b/src/features/stats/echarts-setup.ts index 319b6f5..e703520 100644 --- a/src/features/stats/echarts-setup.ts +++ b/src/features/stats/echarts-setup.ts @@ -6,7 +6,6 @@ import * as echarts from "echarts/core"; import { LineChart, BarChart, PieChart, GaugeChart } from "echarts/charts"; import { GridComponent, - TitleComponent, TooltipComponent, LegendComponent, GraphicComponent, @@ -21,7 +20,6 @@ echarts.use([ PieChart, GaugeChart, GridComponent, - TitleComponent, TooltipComponent, LegendComponent, GraphicComponent, diff --git a/tests/features/stats/chart-options.test.ts b/tests/features/stats/chart-options.test.ts index 9f56bcd..031100e 100644 --- a/tests/features/stats/chart-options.test.ts +++ b/tests/features/stats/chart-options.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any -- poking into loose ECharts option shapes */ import { describe, it, expect } from "vitest"; -import { typeBarOption, leaderboardOption, donutOption } from "../../../src/features/stats/chartOptions"; +import { typeBarOption, leaderboardOption } from "../../../src/features/stats/chartOptions"; import type { ChartColors } from "../../../src/features/stats/chartTheme"; const colors: ChartColors = { @@ -51,20 +51,6 @@ describe("typeBarOption", () => { }); }); -describe("donutOption", () => { - it("centers the total on the ring via a textAlign-centered title at the pie's x", () => { - const opt = donutOption([{ name: "repeater", value: 3 }], colors, "10", "NODES") as Record; - // the old graphic-text approach anchored its left edge at the ring center and clipped - expect(opt.graphic).toBeUndefined(); - expect(opt.title.textAlign).toBe("center"); - expect(opt.title.textVerticalAlign).toBe("middle"); - expect(opt.title.text).toBe("10"); - // anchor must sit exactly on the pie's center point - expect(opt.title.left).toBe(opt.series[0].center[0]); - expect(opt.title.top).toBe(opt.series[0].center[1]); - }); -}); - describe("leaderboardOption", () => { it("left-aligns names at the card edge and truncates long ones to the label gutter", () => { const rows = [{ name: "A very long observer name that overflows", value: 5, color: "#abc" }];