197 lines
5.6 KiB
Plaintext
197 lines
5.6 KiB
Plaintext
'use client'
|
|
|
|
import React from 'react'
|
|
|
|
interface ChartDataPoint {
|
|
value: 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 }, () => ({ value: 0 }))
|
|
|
|
const maxVal = Math.max(...safeData.map(d => d.value || 0), 1)
|
|
const stepX = safeData.length > 1 ? plotWidth / (safeData.length - 1) : plotWidth
|
|
|
|
const points = safeData.map((d, i) => {
|
|
const x = margin.left + i * stepX
|
|
const y = baselineY - (Math.min(d.value || 0, maxVal) / maxVal) * plotHeight
|
|
return { x, y }
|
|
})
|
|
|
|
const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
|
|
const areaPath = `${linePath} 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="areaGradient" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="rgb(37, 99, 235)" stopOpacity="0.3" />
|
|
<stop offset="100%" stopColor="rgb(37, 99, 235)" 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={areaPath} fill="url(#areaGradient)" />
|
|
<path d={linePath} stroke="rgb(37, 99, 235)" strokeWidth="2.5" fill="none" />
|
|
|
|
{/* Точки данных */}
|
|
{points.map((p, i) => (
|
|
<circle
|
|
key={i}
|
|
cx={p.x}
|
|
cy={p.y}
|
|
r="4"
|
|
fill="rgb(37, 99, 235)"
|
|
stroke="rgb(15, 23, 42)"
|
|
strokeWidth="2"
|
|
/>
|
|
))}
|
|
</svg>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default AreaChart
|