Updates
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { useMemo } from "react";
|
||||
import type { TrafficPoint } from "../(data)/dashboard-mock-data";
|
||||
|
||||
type Props = {
|
||||
data: TrafficPoint[];
|
||||
};
|
||||
|
||||
const W = 800;
|
||||
const H = 240;
|
||||
const PAD_L = 46;
|
||||
const PAD_R = 12;
|
||||
const PAD_T = 12;
|
||||
const PAD_B = 28;
|
||||
|
||||
function formatAxis(value: number) {
|
||||
return value >= 1 ? `${value}G` : `${Math.round(value * 1000)}M`;
|
||||
}
|
||||
|
||||
export default function TrafficChart({ data }: Props) {
|
||||
const { max, downPath, downArea, upPath, yTicks, xTicks } = useMemo(() => {
|
||||
const plotW = W - PAD_L - PAD_R;
|
||||
const plotH = H - PAD_T - PAD_B;
|
||||
const max = Math.ceil(
|
||||
Math.max(...data.flatMap((p) => [p.up, p.down])),
|
||||
);
|
||||
|
||||
const x = (i: number) => PAD_L + (i / (data.length - 1)) * plotW;
|
||||
const y = (v: number) => PAD_T + (1 - v / max) * plotH;
|
||||
|
||||
const toPath = (key: "up" | "down") =>
|
||||
data
|
||||
.map(
|
||||
(p, i) =>
|
||||
`${i == 0 ? "M" : "L"}${x(i).toFixed(2)},${y(
|
||||
p[key],
|
||||
).toFixed(2)}`,
|
||||
)
|
||||
.join(" ");
|
||||
|
||||
const downPath = toPath("down");
|
||||
const upPath = toPath("up");
|
||||
const baseY = (H - PAD_B).toFixed(2);
|
||||
const downArea = `${downPath} L${x(data.length - 1).toFixed(
|
||||
2,
|
||||
)},${baseY} L${PAD_L},${baseY} Z`;
|
||||
|
||||
const yTicks = Array.from({ length: 5 }, (_, i) => {
|
||||
const v = (max / 4) * i;
|
||||
return { v, label: formatAxis(v), y: y(v) };
|
||||
});
|
||||
|
||||
const xTicks: { label: string; x: number }[] = [];
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
xTicks.push({ label: data[i]!.label, x: x(i) });
|
||||
}
|
||||
|
||||
return { max, downPath, downArea, upPath, yTicks, xTicks };
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox={`0 0 ${W} ${H}`}
|
||||
className="w-full h-auto text-secondary"
|
||||
role="img"
|
||||
aria-label="Network traffic over the last 24 hours"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="traffic-down" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="currentColor" stopOpacity="0.14" />
|
||||
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="traffic-up" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#94a3b8" stopOpacity="0.1" />
|
||||
<stop offset="100%" stopColor="#94a3b8" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{yTicks.map((tick, i) => (
|
||||
<g key={i}>
|
||||
<line
|
||||
x1={PAD_L}
|
||||
y1={tick.y}
|
||||
x2={W - PAD_R}
|
||||
y2={tick.y}
|
||||
stroke="#94a3b8"
|
||||
strokeOpacity="0.18"
|
||||
/>
|
||||
<text
|
||||
x={PAD_L - 8}
|
||||
y={tick.y + 3}
|
||||
textAnchor="end"
|
||||
className="fill-current text-zinc-500 dark:text-zinc-600 text-[10.5px] tabular"
|
||||
>
|
||||
{tick.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
<path d={downArea} fill="url(#traffic-down)" />
|
||||
<path
|
||||
d={upPath}
|
||||
fill="none"
|
||||
stroke="#94a3b8"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d={downPath}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.75"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
{xTicks.map((tick, i) => (
|
||||
<text
|
||||
key={i}
|
||||
x={tick.x}
|
||||
y={H - PAD_B + 16}
|
||||
textAnchor="middle"
|
||||
className="fill-current text-zinc-500 dark:text-zinc-600 text-[10.5px] tabular"
|
||||
>
|
||||
{tick.label}
|
||||
</text>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user