fix(chart): guard deferred pointer updates

This commit is contained in:
李启源
2026-07-21 09:18:46 +08:00
parent 671fde76f7
commit c63ea2855e
2 changed files with 73 additions and 30 deletions

View File

@@ -13,6 +13,7 @@ import {
} from 'd3'
import { resolveWaveformRenderingOptions } from '../core'
import { formatScientificAxisExponent, formatScientificAxisLabel, paddedDomain } from '../utils'
import { useAnimationFrameThrottle } from './utils/useAnimationFrameThrottle'
import {
computed,
nextTick,
@@ -173,11 +174,10 @@ const annotationInteraction = useWaveformAnnotationInteraction()
const editorSeriesOptions = ref<AnnotationSeriesCandidate[]>([])
let generatedAnnotationId = 0
let synchronizingZoomTransform = false
let zoomAnimationFrame: number | null = null
let pendingSharedZoomTransform: ZoomTransform | null = null
const pendingIndependentZoomTransforms = new Map<number, ZoomTransform>()
let hoverAnimationFrame: number | null = null
let pendingHoverUpdate: (() => void) | null = null
const zoomThrottle = useAnimationFrameThrottle()
const hoverThrottle = useAnimationFrameThrottle()
const preparedSeries = usePreparedWaveformSeries(() => props.data, handleDataReferenceChange)
// 用于传递给 WaveformTooltip 的接口
@@ -608,27 +608,18 @@ function commitPendingZoom() {
}
function scheduleZoomCommit() {
if (zoomAnimationFrame !== null) return
zoomAnimationFrame = requestAnimationFrame(() => {
zoomAnimationFrame = null
commitPendingZoom()
})
zoomThrottle.schedule(() => commitPendingZoom())
}
function flushPendingZoom() {
if (zoomAnimationFrame !== null) {
cancelAnimationFrame(zoomAnimationFrame)
zoomAnimationFrame = null
}
zoomThrottle.flush()
commitPendingZoom()
}
function cancelPendingZoom() {
pendingSharedZoomTransform = null
pendingIndependentZoomTransforms.clear()
if (zoomAnimationFrame === null) return
cancelAnimationFrame(zoomAnimationFrame)
zoomAnimationFrame = null
zoomThrottle.cancel()
}
function clearZoomBindings() {
@@ -705,21 +696,11 @@ function configureZoom() {
}
function cancelPendingHover() {
pendingHoverUpdate = null
if (hoverAnimationFrame === null) return
cancelAnimationFrame(hoverAnimationFrame)
hoverAnimationFrame = null
hoverThrottle.cancel()
}
function scheduleHover(update: () => void) {
pendingHoverUpdate = update
if (hoverAnimationFrame !== null) return
hoverAnimationFrame = requestAnimationFrame(() => {
hoverAnimationFrame = null
const nextUpdate = pendingHoverUpdate
pendingHoverUpdate = null
nextUpdate?.()
})
hoverThrottle.schedule(update)
}
function hoveredPointsMatch(nextPoints: HoveredSeriesPoint[]): boolean {
@@ -1051,9 +1032,15 @@ function handleIndependentPointerMove(event: PointerEvent, trackIndex: number) {
const overlay = event.currentTarget as SVGRectElement | null
if (!overlay) return
const [pointerX, pointerY] = pointer(event, overlay)
// 捕获轨道对象以避免竞态条件
const track = trackLayouts.value[trackIndex]
if (!track || !track.hasVisibleSeries) return
scheduleHover(() => {
const track = trackLayouts.value[trackIndex]
if (!track) return
// 重新验证轨道仍然有效且有可见系列
const currentTrack = trackLayouts.value[trackIndex]
if (!currentTrack || !currentTrack.hasVisibleSeries || currentTrack !== track) return
const xValue = track.xScale.invert(Math.max(0, Math.min(innerWidth.value, pointerX)))
const nextPoints = track.seriesList.flatMap((series) => {
const point = nearestPoint(series, xValue)
@@ -1177,7 +1164,7 @@ watch(
)
if (
retainedIds.size !== internalHiddenSeriesIds.value.size ||
Array.from(retainedIds).some((seriesId) => !internalHiddenSeriesIds.value.has(seriesId))
Array.from(internalHiddenSeriesIds.value).some((seriesId) => !retainedIds.has(seriesId))
) {
internalHiddenSeriesIds.value = retainedIds
}