Files
aerbim-ht-monitor/frontend/components/dashboard/AreaChart.tsx

243 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React from 'react'
interface ChartDataPoint {
critical?: number
warning?: number
label?: string
timestamp?: string
}
interface AreaChartProps {
className?: string
data?: ChartDataPoint[]
}
const AreaChart: React.FC<AreaChartProps> = ({ className = '', data }) => {
const width = 635
const height = 280
const margin = { top: 20, right: 30, bottom: 50, left: 60 }
const plotWidth = width - margin.left - margin.right
const plotHeight = height - margin.top - margin.bottom
const baselineY = margin.top + plotHeight
const safeData = (Array.isArray(data) && data.length > 0)
? data
: Array.from({ length: 7 }, () => ({ critical: 0, warning: 0 }))
const maxVal = Math.max(...safeData.map(d => Math.max(d.critical || 0, d.warning || 0)), 1)
const stepX = safeData.length > 1 ? plotWidth / (safeData.length - 1) : plotWidth
// Точки для критических событий (красная линия)
const criticalPoints = safeData.map((d, i) => {
const x = margin.left + i * stepX
const y = baselineY - (Math.min(d.critical || 0, maxVal) / maxVal) * plotHeight
return { x, y }
})
// Точки для предупреждений (оранжевая линия)
const warningPoints = safeData.map((d, i) => {
const x = margin.left + i * stepX
const y = baselineY - (Math.min(d.warning || 0, maxVal) / maxVal) * plotHeight
return { x, y }
})
const criticalLinePath = criticalPoints.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
const criticalAreaPath = `${criticalLinePath} L${width - margin.right},${baselineY} L${margin.left},${baselineY} Z`
const warningLinePath = warningPoints.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
const warningAreaPath = `${warningLinePath} L${width - margin.right},${baselineY} L${margin.left},${baselineY} Z`
// Генерируем Y-оси метки
const ySteps = 4
const yLabels = Array.from({ length: ySteps + 1 }, (_, i) => {
const value = (maxVal / ySteps) * (ySteps - i)
const y = margin.top + (i * plotHeight) / ySteps
return { value: value.toFixed(1), y }
})
// Генерируем X-оси метки (показываем каждую 2-ю или 3-ю точку)
const xLabelStep = Math.ceil(safeData.length / 5)
const xLabels = safeData
.map((d, i) => {
const x = margin.left + i * stepX
const label = d.label || d.timestamp || `${i + 1}`
return { label, x, index: i }
})
.filter((_, i) => i % xLabelStep === 0 || i === safeData.length - 1)
return (
<div className={`w-full h-full ${className}`}>
<svg className="w-full h-full" viewBox={`0 0 ${width} ${height}`}>
<defs>
<linearGradient id="criticalGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#ef4444" stopOpacity="0.3" />
<stop offset="100%" stopColor="#ef4444" stopOpacity="0" />
</linearGradient>
<linearGradient id="warningGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#fb923c" stopOpacity="0.3" />
<stop offset="100%" stopColor="#fb923c" stopOpacity="0" />
</linearGradient>
</defs>
{/* Сетка Y */}
{yLabels.map((label, i) => (
<line
key={`grid-y-${i}`}
x1={margin.left}
y1={label.y}
x2={width - margin.right}
y2={label.y}
stroke="rgba(148, 163, 184, 0.2)"
strokeWidth="1"
strokeDasharray="4,4"
/>
))}
{/* Ось X */}
<line
x1={margin.left}
y1={baselineY}
x2={width - margin.right}
y2={baselineY}
stroke="rgb(148, 163, 184)"
strokeWidth="2"
/>
{/* Ось Y */}
<line
x1={margin.left}
y1={margin.top}
x2={margin.left}
y2={baselineY}
stroke="rgb(148, 163, 184)"
strokeWidth="2"
/>
{/* Y-оси метки и подписи */}
{yLabels.map((label, i) => (
<g key={`y-label-${i}`}>
<line
x1={margin.left - 5}
y1={label.y}
x2={margin.left}
y2={label.y}
stroke="rgb(148, 163, 184)"
strokeWidth="1"
/>
<text
x={margin.left - 10}
y={label.y + 4}
textAnchor="end"
fontSize="12"
fill="rgb(148, 163, 184)"
fontFamily="Arial, sans-serif"
>
{label.value}
</text>
</g>
))}
{/* X-оси метки и подписи */}
{xLabels.map((label, i) => (
<g key={`x-label-${i}`}>
<line
x1={label.x}
y1={baselineY}
x2={label.x}
y2={baselineY + 5}
stroke="rgb(148, 163, 184)"
strokeWidth="1"
/>
<text
x={label.x}
y={baselineY + 20}
textAnchor="middle"
fontSize="11"
fill="rgb(148, 163, 184)"
fontFamily="Arial, sans-serif"
>
{typeof label.label === 'string' ? label.label.substring(0, 10) : `${label.index + 1}`}
</text>
</g>
))}
{/* Подпись оси Y */}
<text
x={20}
y={margin.top + plotHeight / 2}
textAnchor="middle"
fontSize="13"
fill="rgb(148, 163, 184)"
fontFamily="Arial, sans-serif"
transform={`rotate(-90, 20, ${margin.top + plotHeight / 2})`}
>
Количество
</text>
{/* Подпись оси X */}
<text
x={margin.left + plotWidth / 2}
y={height - 10}
textAnchor="middle"
fontSize="13"
fill="rgb(148, 163, 184)"
fontFamily="Arial, sans-serif"
>
Время
</text>
{/* График предупреждений (оранжевый) - рисуем первым чтобы был на заднем плане */}
<path d={warningAreaPath} fill="url(#warningGradient)" />
<path d={warningLinePath} stroke="#fb923c" strokeWidth="2.5" fill="none" />
{/* Точки данных для предупреждений */}
{warningPoints.map((p, i) => (
<circle
key={`warning-${i}`}
cx={p.x}
cy={p.y}
r="4"
fill="#fb923c"
stroke="rgb(15, 23, 42)"
strokeWidth="2"
/>
))}
{/* График критических событий (красный) - рисуем поверх */}
<path d={criticalAreaPath} fill="url(#criticalGradient)" />
<path d={criticalLinePath} stroke="#ef4444" strokeWidth="2.5" fill="none" />
{/* Точки данных для критических */}
{criticalPoints.map((p, i) => (
<circle
key={`critical-${i}`}
cx={p.x}
cy={p.y}
r="4"
fill="#ef4444"
stroke="rgb(15, 23, 42)"
strokeWidth="2"
/>
))}
{/* Легенда */}
<g transform={`translate(${width - margin.right - 120}, ${margin.top})`}>
<circle cx="6" cy="6" r="4" fill="#ef4444" stroke="rgb(15, 23, 42)" strokeWidth="2" />
<text x="18" y="10" fontSize="11" fill="rgb(148, 163, 184)" fontFamily="Arial, sans-serif">
Критические
</text>
<circle cx="6" cy="24" r="4" fill="#fb923c" stroke="rgb(15, 23, 42)" strokeWidth="2" />
<text x="18" y="28" fontSize="11" fill="rgb(148, 163, 184)" fontFamily="Arial, sans-serif">
Предупреждения
</text>
</g>
</svg>
</div>
)
}
export default AreaChart