import React, { useState, useRef, useEffect } from 'react'; import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer, ReferenceArea } from 'recharts'; import { HOUR, DAY } from './constants'; const TIME_FORMATS = { LONG: 'dd.MM HH:mm', // Для диапазона > 24 часов MEDIUM: 'HH:mm', // Для диапазона > 1 часа SHORT: 'HH:mm:ss' // Для коротких диапазонов }; const LineChartComponent = ({ chartData, metricName, colors, onRangeSelect, filteredData }) => { const [selectionArea, setSelectionArea] = useState(null); const [isSelecting, setIsSelecting] = useState(false); const chartRef = useRef(null); const allTimestamps = Object.values(chartData) .flat() .map(point => point.timestamp) .filter((timestamp, index, self) => self.indexOf(timestamp) === index) .sort((a, b) => a - b); const data = allTimestamps.map(timestamp => { const point = { timestamp }; const firstPoint = Object.values(chartData) .flat() .find(p => p.timestamp === timestamp); if (firstPoint) { point.time = firstPoint.time; point.fullTime = firstPoint.fullTime; } Object.keys(chartData).forEach(key => { const instanceData = chartData[key].find(p => p.timestamp === timestamp); point[key] = instanceData ? instanceData.value : null; }); return point; }); const displayData = filteredData || data; const instanceKeys = displayData.length ? Object.keys(displayData[0]).filter(k => !['timestamp', 'time', 'fullTime'].includes(k)) : []; // Функция для определения оптимального формата времени в зависимости от диапазона const getTimeFormat = () => { if (!data.length) return TIME_FORMATS.SHORT; const range = data[data.length - 1].timestamp - data[0].timestamp; if (range > DAY) return TIME_FORMATS.LONG; if (range > HOUR) return TIME_FORMATS.MEDIUM; return TIME_FORMATS.SHORT; }; useEffect(() => { const handleSelectStart = (e) => { if (isSelecting) { e.preventDefault(); } }; document.addEventListener('selectstart', handleSelectStart); return () => document.removeEventListener('selectstart', handleSelectStart); }, [isSelecting]); const handleMouseDown = (e) => { if (!e) return; // Получаем индекс точки по координатам const activeIndex = e.activeTooltipIndex; if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return; setIsSelecting(true); setSelectionArea({ start: data[activeIndex].timestamp, end: null, startIndex: activeIndex, endIndex: null }); }; const handleMouseMove = (e) => { if (!isSelecting || !selectionArea?.start || !e) return; const activeIndex = e.activeTooltipIndex; if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return; setSelectionArea(prev => ({ ...prev, end: data[activeIndex].timestamp, endIndex: activeIndex })); }; const handleMouseUp = () => { if (!isSelecting || !selectionArea?.start || !selectionArea?.end) { setIsSelecting(false); setSelectionArea(null); return; } const startIndex = Math.min(selectionArea.startIndex, selectionArea.endIndex); const endIndex = Math.max(selectionArea.startIndex, selectionArea.endIndex); // Нормализуем индексы к диапазону [0, 1] для родительского компонента const normalizedStart = startIndex / (data.length - 1); const normalizedEnd = endIndex / (data.length - 1); onRangeSelect({ startIndex: normalizedStart, endIndex: normalizedEnd }); setIsSelecting(false); setSelectionArea(null); }; const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { const currentPoint = data.find(point => point.timestamp === label); return (

{currentPoint?.fullTime || new Date(label).toLocaleString('ru-RU')}

{payload.map((item, index) => (

{`Значение: ${item.value}`}

))}
); } return null; }; if (!data.length) { return
Нет данных для отображения
; } return (
{ const date = new Date(timestamp); const format = getTimeFormat(); if (format === 'dd.MM HH:mm') { return date.toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }); } else if (format === 'HH:mm') { return date.toLocaleString('ru-RU', { hour: '2-digit', minute: '2-digit' }); } else { return date.toLocaleString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } }} /> } /> {instanceKeys.map((instance, index) => ( ))} {selectionArea?.start && selectionArea?.end && ( )}
); }; export default React.memo(LineChartComponent);