133 lines
3.8 KiB
TypeScript
133 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import React, { useRef, useEffect, useState } from 'react'
|
|
import { createColorMap, ColorMapType } from '@/lib/color-maps'
|
|
|
|
interface ColorbarProps {
|
|
colorMapType: ColorMapType
|
|
width?: number
|
|
height?: number
|
|
showLabels?: boolean
|
|
minValue?: number
|
|
maxValue?: number
|
|
unit?: string
|
|
}
|
|
|
|
export function Colorbar({
|
|
colorMapType,
|
|
width = 200,
|
|
height = 20,
|
|
showLabels = true,
|
|
minValue = 0,
|
|
maxValue = 100,
|
|
unit = ''
|
|
}: ColorbarProps) {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
const [isHovered, setIsHovered] = useState(false)
|
|
const [hoverValue, setHoverValue] = useState<number | null>(null)
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current
|
|
if (!canvas) return
|
|
|
|
const ctx = canvas.getContext('2d')
|
|
if (!ctx) return
|
|
|
|
// 设置canvas尺寸
|
|
canvas.width = width
|
|
canvas.height = height
|
|
|
|
// 获取色标数据
|
|
const colorMap = createColorMap(colorMapType)
|
|
|
|
// 绘制色标
|
|
const imageData = ctx.createImageData(width, height)
|
|
|
|
for (let x = 0; x < width; x++) {
|
|
const t = x / (width - 1) // 归一化到 0-1
|
|
const colorIndex = Math.floor(t * 255) * 4
|
|
|
|
const r = colorMap[colorIndex]
|
|
const g = colorMap[colorIndex + 1]
|
|
const b = colorMap[colorIndex + 2]
|
|
const a = colorMap[colorIndex + 3]
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
const pixelIndex = (y * width + x) * 4
|
|
imageData.data[pixelIndex] = r
|
|
imageData.data[pixelIndex + 1] = g
|
|
imageData.data[pixelIndex + 2] = b
|
|
imageData.data[pixelIndex + 3] = a
|
|
}
|
|
}
|
|
|
|
ctx.putImageData(imageData, 0, 0)
|
|
|
|
// 添加边框
|
|
ctx.strokeStyle = '#666'
|
|
ctx.lineWidth = 1
|
|
ctx.strokeRect(0, 0, width, height)
|
|
|
|
}, [colorMapType, width, height])
|
|
|
|
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
|
const canvas = canvasRef.current
|
|
if (!canvas) return
|
|
|
|
const rect = canvas.getBoundingClientRect()
|
|
const x = e.clientX - rect.left
|
|
const t = x / width
|
|
const value = minValue + t * (maxValue - minValue)
|
|
setHoverValue(value)
|
|
setIsHovered(true)
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
setIsHovered(false)
|
|
setHoverValue(null)
|
|
}
|
|
|
|
const formatValue = (value: number) => {
|
|
if (unit === '%') {
|
|
return `${value.toFixed(1)}%`
|
|
} else if (unit === 'dBZ') {
|
|
return `${value.toFixed(1)} dBZ`
|
|
} else if (unit === 'mm/h') {
|
|
return `${value.toFixed(1)} mm/h`
|
|
} else {
|
|
return value.toFixed(1)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="relative">
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="cursor-crosshair"
|
|
onMouseMove={handleMouseMove}
|
|
onMouseLeave={handleMouseLeave}
|
|
style={{ display: 'block' }}
|
|
/>
|
|
|
|
{showLabels && (
|
|
<div className="flex justify-between text-xs text-gray-600 mt-1">
|
|
<span>{formatValue(minValue)}</span>
|
|
<span>{formatValue(maxValue)}</span>
|
|
</div>
|
|
)}
|
|
|
|
{isHovered && hoverValue !== null && (
|
|
<div
|
|
className="absolute bg-black text-white text-xs px-2 py-1 rounded pointer-events-none z-10"
|
|
style={{
|
|
left: `${(hoverValue - minValue) / (maxValue - minValue) * width}px`,
|
|
top: '-30px',
|
|
transform: 'translateX(-50%)'
|
|
}}
|
|
>
|
|
{formatValue(hoverValue)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|