111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Canvas, View, Text } from '@tarojs/components'
|
|
import Taro from '@tarojs/taro'
|
|
|
|
const V_PADDING = 8
|
|
const HEIGHT = 110
|
|
|
|
interface Props {
|
|
points: number[]
|
|
canvasId?: string
|
|
}
|
|
|
|
export function VitalWave({ points, canvasId = 'vitalWave' }: Props) {
|
|
const [width, setWidth] = useState(200)
|
|
|
|
const empty = !points || points.length < 2
|
|
|
|
// 测量容器宽度
|
|
useEffect(() => {
|
|
const query = Taro.createSelectorQuery()
|
|
query.select(`#${canvasId}-wrap`).boundingClientRect((rect: any) => {
|
|
if (rect && rect.width > 0) {
|
|
setWidth(Math.floor(rect.width))
|
|
}
|
|
}).exec()
|
|
}, [canvasId])
|
|
|
|
// 数据变化重绘
|
|
useEffect(() => {
|
|
if (empty || width <= 0) return
|
|
drawWave(canvasId, width, HEIGHT, points)
|
|
}, [points, width, empty, canvasId])
|
|
|
|
return (
|
|
<View id={`${canvasId}-wrap`} style={{ position: 'relative', width: '100%', height: `${HEIGHT * 2}rpx` }}>
|
|
<Canvas
|
|
canvasId={canvasId}
|
|
style={{ width: `${width}px`, height: `${HEIGHT}px` }}
|
|
/>
|
|
{!empty && (
|
|
<>
|
|
<View style={{
|
|
position: 'absolute', top: '33.3%', left: 0, right: 0,
|
|
borderTop: '1px dashed rgba(245, 34, 45, 0.25)'
|
|
}} />
|
|
<View style={{
|
|
position: 'absolute', top: '66.6%', left: 0, right: 0,
|
|
borderTop: '1px dashed rgba(245, 34, 45, 0.25)'
|
|
}} />
|
|
</>
|
|
)}
|
|
{empty && (
|
|
<View style={{
|
|
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
|
}}>
|
|
<Text style={{ fontSize: '24rpx', color: '#b5b9c0' }}>暂无数据</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function drawWave(canvasId: string, w: number, h: number, pts: number[]) {
|
|
const ctx = Taro.createCanvasContext(canvasId)
|
|
const min = Math.min(...pts)
|
|
const max = Math.max(...pts)
|
|
const span = max - min || 1
|
|
const xAt = (i: number) => (pts.length <= 1 ? 0 : (i / (pts.length - 1)) * w)
|
|
const yAt = (v: number) => h - V_PADDING - ((v - min) / span) * (h - V_PADDING * 2)
|
|
|
|
const pathTo = (closeBottom: boolean) => {
|
|
ctx.beginPath()
|
|
ctx.moveTo(xAt(0), yAt(pts[0]))
|
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
const x0 = xAt(Math.max(0, i - 1))
|
|
const y0 = yAt(pts[Math.max(0, i - 1)])
|
|
const x1 = xAt(i)
|
|
const y1 = yAt(pts[i])
|
|
const x2 = xAt(i + 1)
|
|
const y2 = yAt(pts[i + 1])
|
|
const x3 = xAt(Math.min(pts.length - 1, i + 2))
|
|
const y3 = yAt(pts[Math.min(pts.length - 1, i + 2)])
|
|
const c1x = x1 + (x2 - x0) / 6
|
|
const c1y = y1 + (y2 - y0) / 6
|
|
const c2x = x2 - (x3 - x1) / 6
|
|
const c2y = y2 - (y3 - y1) / 6
|
|
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, x2, y2)
|
|
}
|
|
if (closeBottom) {
|
|
ctx.lineTo(w, h)
|
|
ctx.lineTo(0, h)
|
|
ctx.closePath()
|
|
}
|
|
}
|
|
|
|
pathTo(true)
|
|
const grad = ctx.createLinearGradient(0, 0, 0, h)
|
|
grad.addColorStop(0, 'rgba(245, 34, 45, 0.25)')
|
|
grad.addColorStop(1, 'rgba(245, 34, 45, 0)')
|
|
ctx.setFillStyle(grad)
|
|
ctx.fill()
|
|
|
|
pathTo(false)
|
|
ctx.setStrokeStyle('#f5222d')
|
|
ctx.setLineWidth(2)
|
|
ctx.stroke()
|
|
|
|
ctx.draw()
|
|
}
|