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

261 lines
8.1 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
}
interface BarChartProps {
className?: string
data?: ChartDataPoint[]
}
const BarChart: React.FC<BarChartProps> = ({ 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 barData = (Array.isArray(data) && data.length > 0)
? data.map(d => ({
critical: d.critical || 0,
warning: d.warning || 0,
label: d.label || ''
}))
: Array.from({ length: 12 }, (_, i) => ({ critical: 0, warning: 0, label: `${i + 1}` }))
const maxVal = Math.max(...barData.map(b => (b.critical || 0) + (b.warning || 0)), 1)
// Генерируем 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(barData.length / 8)
const xLabels = barData
.map((d, i) => {
const barWidth = Math.max(30, plotWidth / barData.length - 8)
const barSpacing = (plotWidth - barWidth * barData.length) / (barData.length - 1 || 1)
const x = margin.left + i * (barWidth + barSpacing) + barWidth / 2
return { label: d.label || `${i + 1}`, x, index: i }
})
.filter((_, i) => i % xLabelStep === 0 || i === barData.length - 1)
return (
<div className={`w-full h-full ${className}`}>
<svg className="w-full h-full" viewBox={`0 0 ${width} ${height}`}>
{/* Сетка 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, 8) : `${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>
{/* Столбцы - сгруппированные по критическим и предупреждениям */}
{barData.map((bar, index) => {
const barWidth = Math.max(30, plotWidth / barData.length - 8)
const barSpacing = (plotWidth - barWidth * barData.length) / (barData.length - 1 || 1)
const groupX = margin.left + index * (barWidth + barSpacing)
const totalValue = (bar.critical || 0) + (bar.warning || 0)
// Ширина каждого подстолбца
const subBarWidth = barWidth / 2 - 2
// Критический столбец (красный)
const criticalHeight = (bar.critical / maxVal) * plotHeight
const criticalY = baselineY - criticalHeight
// Предупреждение столбец (оранжевый)
const warningHeight = (bar.warning / maxVal) * plotHeight
const warningY = baselineY - warningHeight
return (
<g key={`bar-group-${index}`}>
{/* Критический столбец */}
{bar.critical > 0 && (
<>
<rect
x={groupX}
y={criticalY}
width={subBarWidth}
height={criticalHeight}
fill="#ef4444"
rx="4"
ry="4"
opacity="0.9"
/>
<rect
x={groupX}
y={criticalY}
width={subBarWidth}
height={criticalHeight}
fill="none"
stroke="#ef4444"
strokeWidth="1"
rx="4"
ry="4"
opacity="0.3"
/>
</>
)}
{/* Предупреждение столбец */}
{bar.warning > 0 && (
<>
<rect
x={groupX + subBarWidth + 4}
y={warningY}
width={subBarWidth}
height={warningHeight}
fill="#fb923c"
rx="4"
ry="4"
opacity="0.9"
/>
<rect
x={groupX + subBarWidth + 4}
y={warningY}
width={subBarWidth}
height={warningHeight}
fill="none"
stroke="#fb923c"
strokeWidth="1"
rx="4"
ry="4"
opacity="0.3"
/>
</>
)}
</g>
)
})}
{/* Легенда */}
<g transform={`translate(${width - margin.right - 120}, ${margin.top})`}>
<rect x="0" y="0" width="12" height="12" fill="#ef4444" rx="2" />
<text x="18" y="10" fontSize="11" fill="rgb(148, 163, 184)" fontFamily="Arial, sans-serif">
Критические
</text>
<rect x="0" y="18" width="12" height="12" fill="#fb923c" rx="2" />
<text x="18" y="28" fontSize="11" fill="rgb(148, 163, 184)" fontFamily="Arial, sans-serif">
Предупреждения
</text>
</g>
</svg>
</div>
)
}
export default BarChart