From a64a0f32590f81732e9dd71abe8903ee42655259 Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Mon, 25 May 2026 19:32:04 +0800 Subject: [PATCH] Sync extension marks before hover frames render Extension mark syncState copied primary mark states in afterRender, so interactive state changes happened after the frame had already painted. Moving the copy to the stage beforeRender hook keeps hover-driven extension marks aligned with the primary mark in the same draw pass while afterRender only ensures the stage hook is attached or rebound. Constraint: Hover-only bar outlines rely on extension marks syncing primary mark active state in the same frame Rejected: Keep syncing in afterRender | state changes are visible one render late Confidence: high Scope-risk: narrow Directive: Keep syncState state copying before drawing; do not move it back to afterRender without hover regression verification Tested: Manual localhost page confirmation by user; git diff --check Not-tested: Full package typecheck because existing unrelated @visactor/vchart export/type errors block rushx compile --- .../extension-mark-sync-state.ts | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/vchart-extension/src/components/extension-mark-sync-state/extension-mark-sync-state.ts b/packages/vchart-extension/src/components/extension-mark-sync-state/extension-mark-sync-state.ts index 4ba8cb81ff..d90c3dd5a0 100644 --- a/packages/vchart-extension/src/components/extension-mark-sync-state/extension-mark-sync-state.ts +++ b/packages/vchart-extension/src/components/extension-mark-sync-state/extension-mark-sync-state.ts @@ -2,8 +2,8 @@ * @description ExtensionMark SyncState 插件 * * 将配置了 syncState: true 的 extensionMark 的 graphics 与主 mark 的 graphics - * 通过 context.key 配对,在 afterRender 回调中将主 mark 的当前状态同步到 - * extensionMark graphic。 + * 通过 context.key 配对,在 beforeRender 回调中将主 mark 的当前状态同步到 + * extensionMark graphic,确保扩展 mark 在当前帧绘制前拿到最新状态。 */ import { type IChartPlugin, @@ -18,6 +18,7 @@ import { registerChartPlugin, isValid } from '@visactor/vchart'; +import type { IStage } from '@visactor/vrender-core'; import type { IExtensionMarkSpecWithSyncState } from './type'; const EXTENSION_MARK_SYNC_STATE_PLUGIN_TYPE = 'ExtensionMarkSyncStatePlugin'; @@ -29,8 +30,12 @@ export class ExtensionMarkSyncStatePlugin extends BasePlugin void; + /** 取消 beforeRender 订阅的函数,release 时调用 */ + private _unsubscribeBeforeRender?: () => void; /** 已订阅的 event 实例,chart reMake 后会更换 */ private _subscribedEvent?: IEvent; + /** 已订阅的 stage 实例,chart reMake 后会更换 */ + private _subscribedStage?: IStage; constructor() { super(EXTENSION_MARK_SYNC_STATE_PLUGIN_TYPE); @@ -38,6 +43,8 @@ export class ExtensionMarkSyncStatePlugin extends BasePlugin this._syncStates(); + // 首次 render 后 stage 才一定存在;后续 reMake 若替换 stage,也通过该事件重新挂载 beforeRender。 + const handler = () => this._ensureBeforeRenderSyncHook(); event.on(ChartEvent.afterRender, handler); this._subscribedEvent = event; this._unsubscribeAfterRender = () => { @@ -65,9 +75,33 @@ export class ExtensionMarkSyncStatePlugin extends BasePlugin this._syncStates(); + stage.hooks.beforeRender.tap(this.name, handler); + this._subscribedStage = stage; + this._unsubscribeBeforeRender = () => { + stage.hooks.beforeRender.unTap(this.name); + this._subscribedStage = undefined; + }; + } + /** 检测 chartSpec 中是否存在配置了 syncState 的 extensionMark */ private _detectSyncState(chartSpec: any): boolean { const seriesSpecs = chartSpec?.series;