import LineChartDefault from "@/components/charts/line-chart" import type { DataPoint } from "@/components/charts/line-chart" import { toFixedFloat, decimalString } from "@/lib/utils" import { useLingui } from "@lingui/react/macro" import { ChartCard, FilterBar } from "../chart-card" import type { ChartData, NetworkProbeRecord, NetworkProbeStatsRecord } from "@/types" import { useMemo } from "react" import { atom } from "nanostores" import { useStore } from "@nanostores/react" import { probeKey } from "@/lib/use-network-probes" const $filter = atom("") type ProbeChartProps = { probeStats: NetworkProbeStatsRecord[] grid?: boolean probes: NetworkProbeRecord[] chartData: ChartData empty: boolean } type ProbeChartBaseProps = ProbeChartProps & { valueIndex: number title: string description: string tickFormatter: (value: number) => string contentFormatter: ({ value }: { value: number | string }) => string | number domain?: [number | "auto", number | "auto"] } function ProbeChart({ probeStats, grid, probes, chartData, empty, valueIndex, title, description, tickFormatter, contentFormatter, domain, }: ProbeChartBaseProps) { const filter = useStore($filter) const { dataPoints, visibleKeys } = useMemo(() => { const sortedProbes = [...probes].sort((a, b) => b.resAvg1h - a.resAvg1h) const count = sortedProbes.length const points: DataPoint[] = [] const visibleKeys: string[] = [] const filterTerms = filter ? filter .toLowerCase() .split(" ") .filter((term) => term.length > 0) : [] for (let i = 0; i < count; i++) { const p = sortedProbes[i] const key = probeKey(p) const filtered = filterTerms.length > 0 && !filterTerms.some((term) => key.toLowerCase().includes(term)) if (filtered) { continue } visibleKeys.push(key) points.push({ order: i, label: p.name || p.target, dataKey: (record: NetworkProbeStatsRecord) => record.stats?.[key]?.[valueIndex] ?? "-", color: count <= 5 ? i + 1 : `hsl(${(i * 360) / count}, var(--chart-saturation), var(--chart-lightness))`, }) } return { dataPoints: points, visibleKeys } }, [probes, filter, valueIndex]) const filteredProbeStats = useMemo(() => { if (!visibleKeys.length) return probeStats return probeStats.filter((record) => visibleKeys.some((key) => record.stats?.[key] != null)) }, [probeStats, visibleKeys]) const legend = dataPoints.length < 10 return ( } empty={empty} title={title} description={description} grid={grid} > ) } export function ResponseChart({ probeStats, grid, probes, chartData, empty }: ProbeChartProps) { const { t } = useLingui() return ( `${toFixedFloat(value, value >= 10 ? 0 : 1)} ms`} contentFormatter={({ value }) => { if (typeof value !== "number") { return value } return `${decimalString(value, 2)} ms` }} /> ) } export function LossChart({ probeStats, grid, probes, chartData, empty }: ProbeChartProps) { const { t } = useLingui() return ( `${toFixedFloat(value, value >= 10 ? 0 : 1)}%`} contentFormatter={({ value }) => { if (typeof value !== "number") { return value } return `${decimalString(value, 2)}%` }} /> ) }