mosaicmap/components/colorbar.tsx
2025-08-13 21:54:20 +08:00

178 lines
5.8 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, { 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
orientation?: 'horizontal' | 'vertical'
}
export function Colorbar({
colorMapType,
width = 200,
height = 20,
showLabels = true,
minValue = 0,
maxValue = 100,
unit = '',
orientation = 'horizontal'
}: 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)
if (orientation === 'horizontal') {
// 水平方向:从左到右
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
}
}
} else {
// 垂直方向:从下到上
for (let y = 0; y < height; y++) {
const t = (height - 1 - y) / (height - 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 x = 0; x < width; x++) {
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, orientation])
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
const canvas = canvasRef.current
if (!canvas) return
const rect = canvas.getBoundingClientRect()
let t: number
if (orientation === 'horizontal') {
const x = e.clientX - rect.left
t = x / width
} else {
const y = e.clientY - rect.top
t = (height - y) / height // 垂直方向:从下到上
}
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={orientation === 'horizontal' ? 'relative' : 'flex items-center'}>
<canvas
ref={canvasRef}
className="cursor-crosshair"
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{ display: 'block' }}
/>
{showLabels && (
orientation === 'horizontal' ? (
<div className="flex justify-between text-xs text-gray-600 mt-1">
<span>{formatValue(minValue)}</span>
<span>{formatValue(maxValue)}</span>
</div>
) : (
<div className="flex flex-col justify-between text-xs text-gray-600 ml-2 h-full" style={{ height: `${height}px` }}>
<span>{formatValue(maxValue)}</span>
<span>{formatValue(minValue)}</span>
</div>
)
)}
{isHovered && hoverValue !== null && (
<div
className="absolute bg-black text-white text-xs px-2 py-1 pointer-events-none z-10"
style={
orientation === 'horizontal' ? {
left: `${(hoverValue - minValue) / (maxValue - minValue) * width}px`,
top: '-30px',
transform: 'translateX(-50%)'
} : {
left: `${width + 10}px`,
top: `${((maxValue - hoverValue) / (maxValue - minValue)) * height}px`,
transform: 'translateY(-50%)'
}
}
>
{formatValue(hoverValue)}
</div>
)}
</div>
)
}