138 lines
3.7 KiB
TypeScript
138 lines
3.7 KiB
TypeScript
'use client'
|
||
|
||
import React from 'react'
|
||
|
||
interface DetectorDataPoint {
|
||
value: number
|
||
label?: string
|
||
timestamp?: string
|
||
status?: string
|
||
}
|
||
|
||
interface DetectorChartProps {
|
||
className?: string
|
||
data?: DetectorDataPoint[]
|
||
type?: 'line' | 'bar'
|
||
}
|
||
|
||
const DetectorChart: React.FC<DetectorChartProps> = ({
|
||
className = '',
|
||
data,
|
||
type = 'line'
|
||
}) => {
|
||
if (type === 'bar') {
|
||
const defaultBarData = [
|
||
{ value: 85, label: 'Янв' },
|
||
{ value: 70, label: 'Фев' },
|
||
{ value: 90, label: 'Мар' },
|
||
{ value: 65, label: 'Апр' },
|
||
{ value: 80, label: 'Май' },
|
||
{ value: 95, label: 'Июн' }
|
||
]
|
||
|
||
const barData = (Array.isArray(data) && data.length > 0)
|
||
? data.slice(0, 6).map((d, i) => ({
|
||
value: d.value || 0,
|
||
label: d.label || defaultBarData[i]?.label || `${i + 1}`
|
||
}))
|
||
: defaultBarData
|
||
|
||
return (
|
||
<div className={`w-full h-full ${className}`}>
|
||
<svg className="w-full h-full" viewBox="0 0 400 200">
|
||
<g>
|
||
{barData.map((bar, index) => {
|
||
const barWidth = 50
|
||
const barSpacing = 15
|
||
const x = index * (barWidth + barSpacing) + 20
|
||
const barHeight = (bar.value / 100) * 150
|
||
const y = 160 - barHeight
|
||
|
||
return (
|
||
<g key={index}>
|
||
<rect
|
||
x={x}
|
||
y={y}
|
||
width={barWidth}
|
||
height={barHeight}
|
||
fill="rgb(42, 157, 144)"
|
||
rx="4"
|
||
ry="4"
|
||
/>
|
||
<text
|
||
x={x + barWidth / 2}
|
||
y={180}
|
||
textAnchor="middle"
|
||
fill="#71717a"
|
||
fontSize="12"
|
||
>
|
||
{bar.label}
|
||
</text>
|
||
</g>
|
||
)
|
||
})}
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Line chart implementation
|
||
const defaultLineData = [
|
||
{ value: 150 },
|
||
{ value: 120 },
|
||
{ value: 100 },
|
||
{ value: 80 },
|
||
{ value: 90 },
|
||
{ value: 70 },
|
||
{ value: 60 }
|
||
]
|
||
|
||
const lineData = (Array.isArray(data) && data.length > 0)
|
||
? data.slice(0, 7)
|
||
: defaultLineData
|
||
|
||
const maxVal = Math.max(...lineData.map(d => d.value || 0), 1)
|
||
const width = 400
|
||
const height = 200
|
||
const padding = 20
|
||
const plotHeight = height - 40
|
||
const stepX = lineData.length > 1 ? (width - 2 * padding) / (lineData.length - 1) : 0
|
||
|
||
const points = lineData.map((d, i) => {
|
||
const x = padding + i * stepX
|
||
const y = height - padding - ((d.value || 0) / 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 - padding},${height - padding} L${padding},${height - padding} Z`
|
||
|
||
return (
|
||
<div className={`w-full h-full ${className}`}>
|
||
<svg className="w-full h-full" viewBox={`0 0 ${width} ${height}`}>
|
||
<defs>
|
||
<linearGradient id="detectorGradient" x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="0%" stopColor="rgb(231, 110, 80)" stopOpacity="0.3" />
|
||
<stop offset="100%" stopColor="rgb(231, 110, 80)" stopOpacity="0" />
|
||
</linearGradient>
|
||
</defs>
|
||
<path
|
||
d={areaPath}
|
||
fill="url(#detectorGradient)"
|
||
/>
|
||
<path
|
||
d={linePath}
|
||
stroke="rgb(231, 110, 80)"
|
||
strokeWidth="2"
|
||
fill="none"
|
||
/>
|
||
{points.map((p, i) => (
|
||
<circle key={i} cx={p.x} cy={p.y} r="3" fill="rgb(231, 110, 80)" />
|
||
))}
|
||
</svg>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default DetectorChart |