Files
waveform-analysis/src/components/rendering/seriesStyle.ts
李启源 5b0ba7413e
All checks were successful
Package component / package (push) Successful in 5m56s
feat: 优化波形渲染性能与交互
2026-07-22 16:36:48 +08:00

105 lines
3.2 KiB
TypeScript

import {
symbol,
symbolCircle,
symbolDiamond,
symbolSquare,
symbolTriangle,
type SymbolType,
} from 'd3'
import type { WaveformLineStyle, WaveformLineType, WaveformPointType } from '../../types'
const LEGEND_SWATCH_CENTER_X = 13
const LEGEND_ERROR_BAR_TOP = 2
const LEGEND_ERROR_BAR_BOTTOM = 14
const LEGEND_ERROR_BAR_DEFAULT_CAP_WIDTH = 8
const LEGEND_ERROR_BAR_MAX_CAP_WIDTH = 24
const pointSymbols: Record<Exclude<WaveformPointType, 'none'>, SymbolType> = {
circle: symbolCircle,
square: symbolSquare,
triangle: symbolTriangle,
diamond: symbolDiamond,
}
export function waveformPointSymbolPath(pointType: WaveformPointType, size = 48): string | null {
if (pointType === 'none') return null
return symbol().type(pointSymbols[pointType]).size(size)() ?? null
}
export function waveformPointSeriesPath(
pointType: WaveformPointType,
points: ReadonlyArray<{ x: number; y: number }>,
size = 48,
): string | null {
if (pointType === 'none' || points.length === 0) return null
if (pointType === 'circle') {
const radius = Math.sqrt(size / Math.PI)
return points
.map(
({ x, y }) =>
`M${x + radius},${y}A${radius},${radius},0,1,1,${x - radius},${y}` +
`A${radius},${radius},0,1,1,${x + radius},${y}`,
)
.join('')
}
if (pointType === 'square') {
const side = Math.sqrt(size)
const halfSide = side / 2
return points
.map(({ x, y }) => `M${x - halfSide},${y - halfSide}h${side}v${side}h${-side}Z`)
.join('')
}
if (pointType === 'triangle') {
const topOffset = Math.sqrt(size / ((Math.sqrt(3) * 3) / 4))
const halfWidth = (topOffset * Math.sqrt(3)) / 2
const bottomOffset = topOffset / 2
return points
.map(
({ x, y }) =>
`M${x},${y - topOffset}L${x + halfWidth},${y + bottomOffset}` +
`L${x - halfWidth},${y + bottomOffset}Z`,
)
.join('')
}
const verticalOffset = Math.sqrt(size / (2 * Math.tan(Math.PI / 6)))
const horizontalOffset = verticalOffset * Math.tan(Math.PI / 6)
return points
.map(
({ x, y }) =>
`M${x},${y - verticalOffset}L${x + horizontalOffset},${y}` +
`L${x},${y + verticalOffset}L${x - horizontalOffset},${y}Z`,
)
.join('')
}
export function waveformLegendLinePath(lineType: WaveformLineType): string | null {
if (lineType === 'none') return null
return 'M1 8H25'
}
export function waveformLineDasharray(lineStyle: WaveformLineStyle): string | undefined {
if (lineStyle === 'dashed') return '8 5'
if (lineStyle === 'dash-dot') return '8 5 1.5 5'
return undefined
}
export function waveformLegendErrorBarPath(capWidth: number): string {
const resolvedCapWidth =
Number.isFinite(capWidth) && capWidth > 0
? Math.min(capWidth, LEGEND_ERROR_BAR_MAX_CAP_WIDTH)
: LEGEND_ERROR_BAR_DEFAULT_CAP_WIDTH
const capHalfWidth = resolvedCapWidth / 2
const capStart = LEGEND_SWATCH_CENTER_X - capHalfWidth
const capEnd = LEGEND_SWATCH_CENTER_X + capHalfWidth
return [
`M${capStart} ${LEGEND_ERROR_BAR_TOP}H${capEnd}`,
`M${LEGEND_SWATCH_CENTER_X} ${LEGEND_ERROR_BAR_TOP}V${LEGEND_ERROR_BAR_BOTTOM}`,
`M${capStart} ${LEGEND_ERROR_BAR_BOTTOM}H${capEnd}`,
].join('')
}