'use client'; import dynamic from 'next/dynamic'; import { useCallback, useRef } from 'react'; import type { NetworkMetrics } from '@/types/metrics'; import { formatTimestamp, formatBytes } from '@/lib/formatters'; const ReactECharts = dynamic(() => import('echarts-for-react').then(mod => mod), { ssr: false, loading: () =>
}); interface NetworkChartProps { title?: string; data: NetworkMetrics; height?: number; } export default function NetworkChart({ title = 'Network Traffic', data, height = 300 }: NetworkChartProps) { const chartRef = useRef(null); const soloIndex = useRef(null); if (!data.receive.length && !data.transmit.length) { return (
{title} - 데이터 없음
); } const rxSeries = data.receive.map((inst) => ({ name: `RX ${inst.instance || 'total'}`, type: 'line' as const, smooth: true, symbol: 'none', emphasis: { focus: 'series' as const }, areaStyle: { opacity: 0.3 }, data: inst.dataPoints.map((d) => [d.timestamp * 1000, d.value]), })); const txSeries = data.transmit.map((inst) => ({ name: `TX ${inst.instance || 'total'}`, type: 'line' as const, smooth: true, symbol: 'none', emphasis: { focus: 'series' as const }, lineStyle: { type: 'dashed' as const }, data: inst.dataPoints.map((d) => [d.timestamp * 1000, -d.value]), })); const allSeries = [...rxSeries, ...txSeries]; const seriesNames = allSeries.map((s) => s.name); const option = { title: { text: title, left: 'center', textStyle: { fontSize: 14 } }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, formatter: (params: any) => { if (!Array.isArray(params)) params = [params]; if (params.length === 0) return ''; const time = formatTimestamp(params[0].data[0] / 1000); const lines = params.map( (p: any) => `${p.marker} ${p.seriesName}: ${formatBytes(Math.abs(p.data[1]))}/s` ); return `${time}
${lines.join('
')}`; }, }, legend: { bottom: 0, type: 'scroll', selectedMode: 'multiple', }, grid: { left: '3%', right: '3%', bottom: '15%', top: '15%', containLabel: true }, xAxis: { type: 'time', axisLabel: { formatter: (val: number) => { const d = new Date(val); return `${(d.getMonth() + 1).toString().padStart(2, '0')}/${d.getDate().toString().padStart(2, '0')}\n${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`; }, }, }, yAxis: { type: 'value', axisLabel: { formatter: (val: number) => `${formatBytes(Math.abs(val))}/s`, }, }, dataZoom: [ { type: 'inside', start: 0, end: 100 }, { type: 'slider', start: 0, end: 100, height: 20, bottom: 30 }, ], series: allSeries, }; const onEvents = useCallback(() => ({ click: (params: any) => { const chart = chartRef.current?.getEchartsInstance(); if (!chart) return; if (soloIndex.current === params.seriesIndex) { const selected: Record = {}; seriesNames.forEach((name) => { selected[name] = true; }); chart.setOption({ legend: { selected } }); soloIndex.current = null; } else { const selected: Record = {}; seriesNames.forEach((name) => { selected[name] = false; }); selected[params.seriesName] = true; chart.setOption({ legend: { selected } }); soloIndex.current = params.seriesIndex; } }, }), [seriesNames]); return ( ); }