first
This commit is contained in:
@@ -15,44 +15,182 @@ interface AreaChartProps {
|
||||
|
||||
const AreaChart: React.FC<AreaChartProps> = ({ className = '', data }) => {
|
||||
const width = 635
|
||||
const height = 200
|
||||
const paddingBottom = 20
|
||||
const baselineY = height - paddingBottom
|
||||
const maxPlotHeight = height - 40
|
||||
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 ? width / (safeData.length - 1) : width
|
||||
const stepX = safeData.length > 1 ? plotWidth / (safeData.length - 1) : plotWidth
|
||||
|
||||
const points = safeData.map((d, i) => {
|
||||
const x = i * stepX
|
||||
const y = baselineY - (Math.min(d.value || 0, maxVal) / maxVal) * maxPlotHeight
|
||||
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},${baselineY} L0,${baselineY} Z`
|
||||
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(42, 157, 144)" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="rgb(42, 157, 144)" stopOpacity="0" />
|
||||
<stop offset="0%" stopColor="rgb(37, 99, 235)" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="rgb(37, 99, 235)" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d={areaPath} fill="url(#areaGradient)" />
|
||||
<path d={linePath} stroke="rgb(42, 157, 144)" strokeWidth="2" fill="none" />
|
||||
</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="3" fill="rgb(42, 157, 144)" />
|
||||
<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
|
||||
export default AreaChart
|
||||
|
||||
Reference in New Issue
Block a user