import { useEffect, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { YoloVideoData } from "@/features/recognition/types/yolo" interface YoloDataVisualizationProps { yoloData: YoloVideoData width?: number height?: number } /** * Компонент для визуализации данных YOLO в виде графика * Показывает количество обнаруженных объектов по времени */ export function YoloDataVisualization({ yoloData, width = 800, height = 400 }: YoloDataVisualizationProps) { const { t } = useTranslation() const svgRef = useRef(null) const [selectedClass, setSelectedClass] = useState(null) // Получаем уникальные классы объектов const uniqueClasses = Array.from( new Set(yoloData.frames.flatMap((frame) => frame.detections.map((detection) => detection.class))), ) // Цвета для разных классов const classColors: Record = { person: "#ff6b6b", car: "#4ecdc4", dog: "#45b7d1", cat: "#f9ca24", bicycle: "#6c5ce7", motorcycle: "#a29bfe", bus: "#fd79a8", truck: "#00b894", } // Получаем цвет для класса const getColorForClass = (className: string): string => { return classColors[className] || "#95a5a6" } // Подготавливаем данные для графика const chartData = yoloData.frames.map((frame) => { const classCounts: Record = {} // Подсчитываем количество объектов каждого класса в кадре frame.detections.forEach((detection) => { const className = detection.class classCounts[className] = (classCounts[className] || 0) + 1 }) return { timestamp: frame.timestamp, totalDetections: frame.detections.length, classCounts, } }) // Находим максимальное количество обнаружений для масштабирования const maxDetections = Math.max(...chartData.map((data) => data.totalDetections), 1) // Размеры графика с отступами const margin = { top: 20, right: 20, bottom: 40, left: 60 } const chartWidth = width - margin.left - margin.right const chartHeight = height - margin.top - margin.bottom // Масштабы const xScale = (timestamp: number) => (timestamp / Math.max(...chartData.map((d) => d.timestamp))) * chartWidth const yScale = (count: number) => chartHeight - (count / maxDetections) * chartHeight useEffect(() => { // Здесь можно добавить дополнительную логику для D3.js, если потребуется // Пока используем простую SVG визуализацию }, [yoloData]) return (

{t("Анализ обнаружений YOLO")}

{t("Общее количество кадров")}: {yoloData.frames.length}

{/* Легенда */}
{uniqueClasses.map((className) => ( ))}
{/* График */} {/* Фон */} {/* Область графика */} {/* Сетка */} {[0, 0.25, 0.5, 0.75, 1].map((ratio) => ( {Math.round(maxDetections * (1 - ratio))} ))} {/* Линия графика */} {chartData.length > 1 && ( { const x = xScale(data.timestamp) const y = selectedClass ? yScale(data.classCounts[selectedClass] || 0) : yScale(data.totalDetections) return `${index === 0 ? "M" : "L"} ${x} ${y}` }) .join(" ")}`} fill="none" stroke={selectedClass ? getColorForClass(selectedClass) : "#007bff"} strokeWidth={2} /> )} {/* Точки данных */} {chartData.map((data, index) => { const x = xScale(data.timestamp) const y = selectedClass ? yScale(data.classCounts[selectedClass] || 0) : yScale(data.totalDetections) return ( ) })} {/* Оси */} {/* Подписи осей */} {t("Время (секунды)")} {t("Количество обнаружений")} {/* Статистика */}
{t("Всего кадров")}
{yoloData.frames.length}
{t("Уникальных классов")}
{uniqueClasses.length}
{t("Всего обнаружений")}
{chartData.reduce((sum, data) => sum + data.totalDetections, 0)}
{t("Среднее за кадр")}
{(chartData.reduce((sum, data) => sum + data.totalDetections, 0) / chartData.length).toFixed(1)}
) }