From 6f66026cf9cb70794e95dff7d66473111a85ae54 Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Mon, 27 Apr 2026 14:06:54 +0800
Subject: [PATCH 1/8] chore: unblock VChart validation on alpha.2

Use the published VRender alpha.2 packages for the VChart validation branch.
Keep the minimal app-aware stage path and external ownership fixes in review scope.
Add regression coverage for VRender patches and final attrs.

Constraint: Published npm alpha is the validation source

Constraint: option.stage and option.app remain external-owned

Rejected: Local linked VRender build | final evidence must use npm alpha

Rejected: Broad env/bootstrap migration | this branch keeps stage changes minimal

Confidence: high

Scope-risk: moderate

Directive: Do not downgrade @visactor/vrender* or bypass VRender state patches

Tested: rush install --check-only

Tested: rush test --only @visactor/vchart

Tested: rush build --to @visactor/vchart

Tested: eslint touched VChart files

Tested: browser strict smoke for basicBar/basicLine/textHeavy hover/select

Not-tested: full miniApp/wx/tt/harmony/lynx app factory migration
---
 common/config/rush/pnpm-lock.yaml             | 116 ++++-----
 docs/package.json                             |   6 +-
 packages/openinula-vchart/package.json        |   6 +-
 packages/react-vchart/package.json            |   4 +-
 packages/vchart-extension/package.json        |   8 +-
 .../__tests__/unit/core/vchart-event.test.ts  |  91 +++++++
 .../vchart/__tests__/unit/core/vchart.test.ts | 241 +++++++++++++++++-
 packages/vchart/package.json                  |   8 +-
 packages/vchart/src/compile/compiler.ts       |  36 ++-
 .../vchart/src/compile/interface/compiler.ts  |  10 +-
 packages/vchart/src/compile/stage-app.ts      |  51 ++++
 tools/story-player/package.json               |   8 +-
 12 files changed, 494 insertions(+), 91 deletions(-)
 create mode 100644 packages/vchart/src/compile/stage-app.ts

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 3c7d870779..546b5efda2 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -37,11 +37,11 @@ importers:
         specifier: 1.2.4-alpha.5
         version: 1.2.4-alpha.5
       '@visactor/vrender':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vtable':
         specifier: 1.19.0-alpha.0
         version: 1.19.0-alpha.0
@@ -203,11 +203,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart
       '@visactor/vrender-core':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -294,11 +294,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart-extension
       '@visactor/vrender-core':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -529,17 +529,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-components':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-core':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vscale':
         specifier: ~1.0.23
         version: 1.0.23
@@ -692,17 +692,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-components':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-core':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -1260,14 +1260,14 @@ importers:
         specifier: workspace:2.0.22
         version: link:../../packages/vchart
       '@visactor/vrender':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-core':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-kits':
-        specifier: 1.0.45
-        version: 1.0.45
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -3073,29 +3073,29 @@ packages:
   '@visactor/vrender-animate@1.0.0-alpha.18':
     resolution: {integrity: sha512-9kTtvp1ef+1t+AtUiza6A7qBQP7SmvOu3/ILGrqs/HGdZVj1XGjbYvD/X/zwKJ3LEb7gGV5fa8x95e4czTvRSA==}
 
-  '@visactor/vrender-animate@1.0.45':
-    resolution: {integrity: sha512-6v7LRpr+zugxR8JH3RCnodYxrrzHkSp4GaBTNwXuJ7bwThi/YzXTvmBz2pfQNmUG/rRze8Bm7vqneHVdXuFhng==}
+  '@visactor/vrender-animate@1.1.0-alpha.2':
+    resolution: {integrity: sha512-jlegJK92jVvT/PF16nMeRjlN68IoSgvJz/tKgsZVveJCt5276LSm2K5vpIjXNeaEKvmUnKes+45sZZX6kWNC/A==}
 
   '@visactor/vrender-components@1.0.0-alpha.18':
     resolution: {integrity: sha512-7Euq+ZfswL74n2pgkaqZSsPxoSa5SPIGyXatN1eUrdzM2Z0kX6U0RcJg01fctvRs4op6WhcecRLqGvnHcBeb9Q==}
 
-  '@visactor/vrender-components@1.0.45':
-    resolution: {integrity: sha512-rYsG/rncT5FgYYWqv09f6LkZEAk/IS45IEYWBD0SCgKguYnpEebYDmDba1muJGn+rRv/vGlqg1pYqXLw8mEU5w==}
+  '@visactor/vrender-components@1.1.0-alpha.2':
+    resolution: {integrity: sha512-nmBTBXEPvDRYbvXtOCLZ89+9WK1A+wLsE4RbuKv2omjA9D+XYRE7QZsW3O1g1/XBD8JqDVDiz+tk2Pp11IBBMw==}
 
   '@visactor/vrender-core@1.0.0-alpha.18':
     resolution: {integrity: sha512-0ihtNvCyNkOsWPFgRqowHzq0IcQgS2Wl/nPpKbVtxWKveenwlhA+ZKoQvam6VJyBY7jeNe1pROy0mJMDyVAJQw==}
 
-  '@visactor/vrender-core@1.0.45':
-    resolution: {integrity: sha512-kvYAsKGZ+dXbhOQzjjbMkRzI815KgaHoWOW7iyVorXTldQBTsxLtFIi/J3VfYqGdM3apUgsFoy8uvjS5DepPUA==}
+  '@visactor/vrender-core@1.1.0-alpha.2':
+    resolution: {integrity: sha512-f9BTOK3Rv5yiTxMJMPLV2VYIgwcsakJ/VXCOp88hbWgy/or2pvPW4/42sOLALDMCjSnpd2zGhV2R2vy2+nSD2w==}
 
   '@visactor/vrender-kits@1.0.0-alpha.18':
     resolution: {integrity: sha512-Tvolkq+4G8qiPFZo0Aj8M//Yr6jR2h8FNkFEyWM9gbQbEiTkjpmHAJOYnoSsaPtPrcMSlG4EhJSFDk6ymANHVg==}
 
-  '@visactor/vrender-kits@1.0.45':
-    resolution: {integrity: sha512-iaeRitht8IqNvJwdKmcPKAYL0a4+f8xhK2bWUumSQjNbZJQx9UzGuDp73cIdNXzCNoCiJZElpsOiIGu3kymqiw==}
+  '@visactor/vrender-kits@1.1.0-alpha.2':
+    resolution: {integrity: sha512-fXG3SB/PDcmA2oK6FqHTBvb8438BDeeCkpIvzt1WI/lpghzD6wT7Dd3HhY8hxonArDUUqAsorZyFeQFNPezUTQ==}
 
-  '@visactor/vrender@1.0.45':
-    resolution: {integrity: sha512-mHIB9euxbgpotNLe8cn8IThTJbXgrn/B1Nx/PCqp/OeBEcNihmy5X9ODJVgUhrF3dY1ZX0EeE2zrEE0mE/+eHQ==}
+  '@visactor/vrender@1.1.0-alpha.2':
+    resolution: {integrity: sha512-MNW2rTJLgCVa0T4TTavQ+fMJp2PkvkZwxWAwdzdNlFAIX9WriF24L+Xqwu8nvqsJiUz2BEUbgYP+7sTZcn7krA==}
 
   '@visactor/vscale@0.18.18':
     resolution: {integrity: sha512-iRG4kv+5Fv4KX3AxEfV95XU3I6OmF0QizyAhqHxKa7L1MaT+MRvDDk5zHWf1E8gialLbL2xDe3GnT6g/4u5jhA==}
@@ -15026,9 +15026,9 @@ snapshots:
       '@visactor/vrender-core': 1.0.0-alpha.18
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-animate@1.0.45':
+  '@visactor/vrender-animate@1.1.0-alpha.2':
     dependencies:
-      '@visactor/vrender-core': 1.0.45
+      '@visactor/vrender-core': 1.1.0-alpha.2
       '@visactor/vutils': 1.0.23
 
   '@visactor/vrender-components@1.0.0-alpha.18':
@@ -15039,11 +15039,11 @@ snapshots:
       '@visactor/vscale': 1.0.4
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-components@1.0.45':
+  '@visactor/vrender-components@1.1.0-alpha.2':
     dependencies:
-      '@visactor/vrender-animate': 1.0.45
-      '@visactor/vrender-core': 1.0.45
-      '@visactor/vrender-kits': 1.0.45
+      '@visactor/vrender-animate': 1.1.0-alpha.2
+      '@visactor/vrender-core': 1.1.0-alpha.2
+      '@visactor/vrender-kits': 1.1.0-alpha.2
       '@visactor/vscale': 1.0.23
       '@visactor/vutils': 1.0.23
 
@@ -15052,7 +15052,7 @@ snapshots:
       '@visactor/vutils': 1.0.4
       color-convert: 2.0.1
 
-  '@visactor/vrender-core@1.0.45':
+  '@visactor/vrender-core@1.1.0-alpha.2':
     dependencies:
       '@visactor/vutils': 1.0.23
       color-convert: 2.0.1
@@ -15066,21 +15066,21 @@ snapshots:
       lottie-web: 5.13.0
       roughjs: 4.5.2
 
-  '@visactor/vrender-kits@1.0.45':
+  '@visactor/vrender-kits@1.1.0-alpha.2':
     dependencies:
       '@resvg/resvg-js': 2.4.1
-      '@visactor/vrender-core': 1.0.45
+      '@visactor/vrender-core': 1.1.0-alpha.2
       '@visactor/vutils': 1.0.23
       gifuct-js: 2.1.2
       lottie-web: 5.13.0
       roughjs: 4.6.6
 
-  '@visactor/vrender@1.0.45':
+  '@visactor/vrender@1.1.0-alpha.2':
     dependencies:
-      '@visactor/vrender-animate': 1.0.45
-      '@visactor/vrender-components': 1.0.45
-      '@visactor/vrender-core': 1.0.45
-      '@visactor/vrender-kits': 1.0.45
+      '@visactor/vrender-animate': 1.1.0-alpha.2
+      '@visactor/vrender-components': 1.1.0-alpha.2
+      '@visactor/vrender-core': 1.1.0-alpha.2
+      '@visactor/vrender-kits': 1.1.0-alpha.2
 
   '@visactor/vscale@0.18.18':
     dependencies:
diff --git a/docs/package.json b/docs/package.json
index 5f958df927..916924de51 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,8 +19,8 @@
     "@visactor/vchart-theme": "~1.6.6",
     "@visactor/vmind": "1.2.4-alpha.5",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
+    "@visactor/vrender": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
     "@visactor/vtable": "1.19.0-alpha.0",
     "@visactor/vtable-editors": "1.19.0-alpha.0",
     "@visactor/vtable-gantt": "1.19.0-alpha.0",
@@ -58,4 +58,4 @@
     "react-device-detect": "^2.2.2",
     "minimist": "1.2.8"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/openinula-vchart/package.json b/packages/openinula-vchart/package.json
index 2f7629c7f8..bcc4a86dae 100644
--- a/packages/openinula-vchart/package.json
+++ b/packages/openinula-vchart/package.json
@@ -30,8 +30,8 @@
   "dependencies": {
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
+    "@visactor/vrender-core": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
@@ -78,4 +78,4 @@
     "access": "public",
     "registry": "https://registry.npmjs.org/"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json
index 16de6da94c..3aa5be4964 100644
--- a/packages/react-vchart/package.json
+++ b/packages/react-vchart/package.json
@@ -36,8 +36,8 @@
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vchart-extension": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
+    "@visactor/vrender-core": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json
index cda702106a..25789045fa 100644
--- a/packages/vchart-extension/package.json
+++ b/packages/vchart-extension/package.json
@@ -26,10 +26,10 @@
     "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
-    "@visactor/vrender-components": "1.0.45",
-    "@visactor/vrender-animate": "1.0.45",
+    "@visactor/vrender-core": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender-components": "1.1.0-alpha.2",
+    "@visactor/vrender-animate": "1.1.0-alpha.2",
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
     "@visactor/vdataset": "~1.0.23",
diff --git a/packages/vchart/__tests__/unit/core/vchart-event.test.ts b/packages/vchart/__tests__/unit/core/vchart-event.test.ts
index 39ec0a0efe..2896595ed3 100644
--- a/packages/vchart/__tests__/unit/core/vchart-event.test.ts
+++ b/packages/vchart/__tests__/unit/core/vchart-event.test.ts
@@ -310,6 +310,97 @@ describe('vchart event test', () => {
     }
   });
 
+  it('should apply interaction state style through vrender state patch', () => {
+    const stateContainer = createDiv();
+    const stateDom = createDiv(stateContainer);
+
+    const chartWithState = new VChart(
+      {
+        type: 'bar',
+        width: 400,
+        height: 300,
+        data: [
+          {
+            id: 'data',
+            values: [
+              { x: 'Mon', y: 10 },
+              { x: 'Tue', y: 12 }
+            ]
+          }
+        ],
+        xField: 'x',
+        yField: 'y',
+        hover: { enable: true, trigger: 'pointerover', triggerOff: 'pointerout' },
+        select: { enable: true, trigger: 'click', mode: 'single' },
+        bar: {
+          state: {
+            hover: {
+              fillOpacity: 0.31
+            },
+            selected: {
+              stroke: '#111827',
+              lineWidth: 4
+            }
+          }
+        }
+      } as IBarChartSpec,
+      {
+        dom: stateDom,
+        animation: false
+      }
+    );
+
+    chartWithState.renderSync();
+
+    try {
+      const chart = chartWithState.getChart() as IChart;
+      const barSeries = chart.getAllSeries()[0];
+      const barMark = barSeries.getMarks().find((mark: IMark) => mark.name === 'bar');
+      expect(barMark).toBeDefined();
+      if (!barMark) {
+        throw new Error('Expected bar mark to exist');
+      }
+
+      const barGraphic = barMark.getGraphics()[0] as IMarkGraphic & {
+        attribute: Record<string, unknown>;
+        resolvedStatePatch?: Record<string, unknown>;
+        stateProxy?: (stateName: string, states: string[]) => Record<string, unknown>;
+      };
+      expect(barGraphic).toBeDefined();
+      expect(typeof barGraphic.stateProxy).toBe('function');
+
+      const hoverPatch = barGraphic.stateProxy?.('hover', ['hover']);
+      expect(hoverPatch?.fillOpacity).toBe(0.31);
+
+      chart.getEvent().emit('pointerover', { item: barGraphic } as unknown as BaseEventParams);
+
+      expect(barGraphic.hasState('hover')).toBe(true);
+      expect(barGraphic.resolvedStatePatch?.fillOpacity).toBe(0.31);
+      expect(barGraphic.attribute.fillOpacity).toBe(0.31);
+
+      chart.getEvent().emit('pointerout', { item: barGraphic } as unknown as BaseEventParams);
+
+      expect(barGraphic.hasState('hover')).toBe(false);
+      expect(barGraphic.resolvedStatePatch).toBeUndefined();
+      expect(barGraphic.attribute.fillOpacity).not.toBe(0.31);
+
+      const selectedPatch = barGraphic.stateProxy?.('selected', ['selected']);
+      expect(selectedPatch?.stroke).toBe('#111827');
+      expect(selectedPatch?.lineWidth).toBe(4);
+
+      chart.getEvent().emit('click', { item: barGraphic } as unknown as BaseEventParams);
+
+      expect(barGraphic.hasState('selected')).toBe(true);
+      expect(barGraphic.resolvedStatePatch?.stroke).toBe('#111827');
+      expect(barGraphic.resolvedStatePatch?.lineWidth).toBe(4);
+      expect(barGraphic.attribute.stroke).toBe('#111827');
+      expect(barGraphic.attribute.lineWidth).toBe(4);
+    } finally {
+      chartWithState.release();
+      removeDom(stateContainer);
+    }
+  });
+
   it('should merge only identical interaction triggers in common chart', () => {
     const commonContainer = createDiv();
     const commonDom = createDiv(commonContainer);
diff --git a/packages/vchart/__tests__/unit/core/vchart.test.ts b/packages/vchart/__tests__/unit/core/vchart.test.ts
index c31a5dbdc1..03a09b6b4e 100644
--- a/packages/vchart/__tests__/unit/core/vchart.test.ts
+++ b/packages/vchart/__tests__/unit/core/vchart.test.ts
@@ -1,6 +1,13 @@
-import type { Group, IArc, Text } from '@visactor/vrender-core';
+import { createBrowserApp, Stage, type Group, type IApp, type IArc, type Text } from '@visactor/vrender-core';
+import {
+  installBrowserEnvToApp,
+  installBrowserPickersToApp,
+  installDefaultGraphicsToApp
+} from '@visactor/vrender-kits';
 import type { IBarChartSpec } from '../../../src';
 import { default as VChart } from '../../../src';
+import { getDefaultVRenderApp } from '../../../src/compile/stage-app';
+import { registerBrowserEnv } from '../../../src/env';
 import { createDiv, createCanvas, removeDom } from '../../util/dom';
 import type { ICommonChartSpec } from '../../../src/chart/common';
 import type { IAreaSeriesSpec } from '../../../src/series/area/interface';
@@ -508,7 +515,7 @@ describe('VChart', () => {
       expect(value1).toBe(mark.attribute.x);
 
       const value2 = vchart.convertValueToPosition(0, { axisId: 'left' });
-      expect(value2).toBe(394);
+      expect(value2).toBe(vchart.getChart()!.getAllSeries()[0].getRegion().getLayoutRect().height);
     });
 
     it('should convert correctly in funnel chart', () => {
@@ -759,4 +766,234 @@ describe('VChart', () => {
       expect(axis.getScale().domain()).toEqual([-52, 30]);
     });
   });
+
+  describe('external stage ownership', () => {
+    let canvasDom: HTMLCanvasElement;
+    let charts: VChart[];
+    let externalStage: Stage | undefined;
+    let rawExternalStageRelease: (() => void) | undefined;
+    let externalApp: IApp | undefined;
+    let rawExternalAppRelease: (() => void) | undefined;
+
+    const spec = (valueOffset: number = 0): IBarChartSpec => ({
+      type: 'bar',
+      width: 200,
+      height: 150,
+      data: [
+        {
+          id: 'data',
+          values: [
+            { x: 'A', y: 12 + valueOffset },
+            { x: 'B', y: 18 + valueOffset }
+          ]
+        }
+      ],
+      xField: 'x',
+      yField: 'y',
+      animation: false
+    });
+
+    const createExternalStage = () => {
+      registerBrowserEnv();
+      externalStage = new Stage({
+        width: 200,
+        height: 150,
+        canvas: canvasDom,
+        autoRender: true,
+        disableDirtyBounds: true
+      });
+      rawExternalStageRelease = externalStage.release.bind(externalStage);
+      return externalStage;
+    };
+
+    const createExternalApp = () => {
+      externalApp = createBrowserApp();
+      installBrowserEnvToApp(externalApp);
+      installDefaultGraphicsToApp(externalApp);
+      installBrowserPickersToApp(externalApp);
+      rawExternalAppRelease = externalApp.release.bind(externalApp);
+      return externalApp;
+    };
+
+    beforeEach(() => {
+      canvasDom = createCanvas();
+      canvasDom.width = 200;
+      canvasDom.height = 150;
+      charts = [];
+    });
+
+    afterEach(() => {
+      charts.forEach(chart => {
+        if (!(chart as any)._isReleased) {
+          chart.release();
+        }
+      });
+      if (externalStage && (externalStage as any).releaseStatus !== 'released') {
+        rawExternalStageRelease?.();
+      }
+      if (externalApp && !externalApp.released) {
+        rawExternalAppRelease?.();
+      }
+      removeDom(canvasDom);
+      externalStage = undefined;
+      rawExternalStageRelease = undefined;
+      externalApp = undefined;
+      rawExternalAppRelease = undefined;
+    });
+
+    it('should not release an externally owned stage and should remove chart-owned root group', () => {
+      const stage = createExternalStage();
+      const releaseStage = jest.fn(() => rawExternalStageRelease?.());
+      stage.release = releaseStage as any;
+
+      const chart = new VChart(spec(), {
+        stage: stage as any,
+        animation: false
+      });
+      charts.push(chart);
+      chart.renderSync();
+
+      const rootGroup = stage.defaultLayer.find(node => node.name === 'root', false);
+      expect(rootGroup).toBeDefined();
+
+      chart.release();
+
+      expect(releaseStage).not.toHaveBeenCalled();
+      expect(stage.window).toBeDefined();
+      expect(stage.defaultLayer).toBeDefined();
+      expect(stage.defaultLayer.find(node => node === rootGroup, false)).toBeFalsy();
+    });
+
+    it('should allow a second VChart instance to reuse the same external stage', () => {
+      const stage = createExternalStage();
+      const releaseStage = jest.fn(() => rawExternalStageRelease?.());
+      stage.release = releaseStage as any;
+
+      const first = new VChart(spec(), {
+        stage: stage as any,
+        animation: false
+      });
+      charts.push(first);
+      first.renderSync();
+      first.updateSpecSync(spec(3));
+      first.release();
+
+      expect(releaseStage).not.toHaveBeenCalled();
+
+      const second = new VChart(spec(6), {
+        stage: stage as any,
+        animation: false
+      });
+      charts.push(second);
+      second.renderSync();
+      second.release();
+
+      expect(releaseStage).not.toHaveBeenCalled();
+    });
+
+    it('should still release an internally created stage', () => {
+      const chart = new VChart(spec(), {
+        renderCanvas: canvasDom,
+        animation: false
+      });
+      charts.push(chart);
+      chart.renderSync();
+
+      const stage = chart.getStage() as unknown as Stage;
+      const rawRelease = stage.release.bind(stage);
+      const releaseStage = jest.fn(() => rawRelease());
+      stage.release = releaseStage as any;
+
+      chart.release();
+
+      expect(releaseStage).toHaveBeenCalledTimes(1);
+    });
+
+    it('should use an external app to create an internally owned stage without releasing the app', () => {
+      const app = createExternalApp();
+      const createStage = jest.spyOn(app, 'createStage');
+      const releaseApp = jest.fn(() => rawExternalAppRelease?.());
+      app.release = releaseApp as any;
+
+      const chart = new VChart(spec(), {
+        app,
+        renderCanvas: canvasDom,
+        animation: false
+      } as any);
+      charts.push(chart);
+      chart.renderSync();
+
+      expect(createStage).toHaveBeenCalledTimes(1);
+
+      const stage = chart.getStage() as unknown as Stage;
+      const rawRelease = stage.release.bind(stage);
+      const releaseStage = jest.fn(() => rawRelease());
+      stage.release = releaseStage as any;
+
+      chart.release();
+
+      expect(releaseStage).toHaveBeenCalledTimes(1);
+      expect(releaseApp).not.toHaveBeenCalled();
+    });
+
+    it('should reuse the fallback app while keeping internally created stages isolated', () => {
+      const fallbackApp = getDefaultVRenderApp('desktop-browser');
+      const createStage = jest.spyOn(fallbackApp, 'createStage');
+      const secondCanvasDom = createCanvas();
+      secondCanvasDom.width = 200;
+      secondCanvasDom.height = 150;
+
+      const first = new VChart(spec(), {
+        renderCanvas: canvasDom,
+        animation: false
+      });
+      charts.push(first);
+      first.renderSync();
+
+      const second = new VChart(spec(6), {
+        renderCanvas: secondCanvasDom,
+        animation: false
+      });
+      charts.push(second);
+      second.renderSync();
+
+      const firstStage = first.getStage() as unknown as Stage;
+      const secondStage = second.getStage() as unknown as Stage;
+      const rawSecondStageRelease = secondStage.release.bind(secondStage);
+      const releaseSecondStage = jest.fn(() => rawSecondStageRelease());
+      secondStage.release = releaseSecondStage as any;
+
+      expect(getDefaultVRenderApp('desktop-browser')).toBe(fallbackApp);
+      expect(createStage).toHaveBeenCalledTimes(2);
+      expect(firstStage).not.toBe(secondStage);
+
+      first.release();
+
+      expect(releaseSecondStage).not.toHaveBeenCalled();
+      expect(secondStage.window).toBeDefined();
+      expect(secondStage.defaultLayer).toBeDefined();
+
+      second.release();
+
+      expect(releaseSecondStage).toHaveBeenCalledTimes(1);
+      removeDom(secondCanvasDom);
+    });
+
+    it('should keep the default dom render path working with fallback app stage creation', () => {
+      const dom = createDiv();
+      const chart = new VChart(spec(), {
+        dom,
+        animation: false
+      });
+      charts.push(chart);
+
+      chart.renderSync();
+      chart.updateSpecSync(spec(9));
+
+      expect(chart.getStage()).toBeDefined();
+
+      chart.release();
+      removeDom(dom);
+    });
+  });
 });
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index ef9bf65d99..477dd24a59 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,10 +126,10 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
-    "@visactor/vrender-core": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
-    "@visactor/vrender-components": "1.0.45",
-    "@visactor/vrender-animate": "1.0.45",
+    "@visactor/vrender-core": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender-components": "1.1.0-alpha.2",
+    "@visactor/vrender-animate": "1.1.0-alpha.2",
     "@visactor/vutils-extension": "workspace:2.0.22"
   },
   "publishConfig": {
diff --git a/packages/vchart/src/compile/compiler.ts b/packages/vchart/src/compile/compiler.ts
index 881086fac9..9a4d257c86 100644
--- a/packages/vchart/src/compile/compiler.ts
+++ b/packages/vchart/src/compile/compiler.ts
@@ -9,7 +9,7 @@ import type { IBoundsLike } from '@visactor/vutils';
 import { array, isArray, isObject, isValid } from '@visactor/vutils';
 import type { EventSourceType } from '../event/interface';
 import type { IChart } from '../chart/interface';
-import { createGroup, Stage, vglobal, waitForAllSubLayers } from '@visactor/vrender-core';
+import { createGroup, vglobal, waitForAllSubLayers } from '@visactor/vrender-core';
 import type { IColor, IEventTarget, IGroup, IStage } from '@visactor/vrender-core';
 import type { IMorphConfig } from '../animation/spec';
 import type { IVChart, IVChartRenderOption } from '../core/interface';
@@ -23,6 +23,7 @@ import { log } from '../util/debug';
 import type { MarkAnimationSpec, TypeAnimationConfig } from '../animation/interface';
 import { AnimationStateEnum } from '../animation/interface';
 import { BuiltIn_DISAPPEAR_ANIMATE_NAME } from '../constant/animate';
+import { createStageFromApp, resolveVRenderApp } from './stage-app';
 
 type EventListener = {
   type: string;
@@ -47,6 +48,8 @@ export class Compiler implements ICompiler {
 
   protected _stage: IStage;
 
+  private _isExternalStage: boolean = false;
+
   protected _stateAnimationConfig: Partial<MarkAnimationSpec>;
   get stateAnimationConfig() {
     return this._stateAnimationConfig;
@@ -127,9 +130,13 @@ export class Compiler implements ICompiler {
       background
     } = this._option;
     vglobal.setEnv(toRenderMode(mode), modeParams ?? {});
-    this._stage =
-      this._option.stage ??
-      (new Stage({
+    const externalStage = this._option.stage;
+    this._isExternalStage = !!externalStage;
+    this._stage = externalStage;
+
+    if (!this._stage) {
+      const app = resolveVRenderApp(this._option.app, mode);
+      this._stage = createStageFromApp(app, {
         background,
         width: this._width,
         height: this._height,
@@ -158,7 +165,8 @@ export class Compiler implements ICompiler {
         ReactDOM: this._option.ReactDOM,
         autoRefresh: isValid(autoRefreshDpr) ? autoRefreshDpr : !isValid(dpr),
         ...(this._option.renderHooks ?? {})
-      }) as unknown as IStage);
+      }) as unknown as IStage;
+    }
 
     this._stage.enableIncrementalAutoRender();
 
@@ -726,17 +734,27 @@ export class Compiler implements ICompiler {
   }
 
   release(): void {
+    const stage = this._stage;
+    const rootGroup = this._rootGroup;
+    const shouldReleaseStage = !!stage && !this._isExternalStage;
+
     this.clearNextRender();
     this.releaseEvent();
-    this._option = this._container = null as any;
     // vgrammar release
     this.releaseGrammar(true);
 
-    if (this._stage !== this._option?.stage) {
-      // don't release the stage created by outside
-      this._stage.release();
+    if (stage) {
+      if (shouldReleaseStage) {
+        stage.release();
+      } else if (rootGroup) {
+        stage.defaultLayer.removeChild(rootGroup);
+        rootGroup.release();
+      }
     }
     this._stage = null;
+    this._rootGroup = null;
+    this._isExternalStage = false;
+    this._option = this._container = null as any;
 
     this.isInited = false;
     this._compileChart = null;
diff --git a/packages/vchart/src/compile/interface/compiler.ts b/packages/vchart/src/compile/interface/compiler.ts
index 9d5df61fc5..6380d1b1fe 100644
--- a/packages/vchart/src/compile/interface/compiler.ts
+++ b/packages/vchart/src/compile/interface/compiler.ts
@@ -1,4 +1,4 @@
-import type { IColor, IStageParams, IStage, ILayer, IOption3D, ITicker } from '@visactor/vrender-core';
+import type { IApp, IColor, IStageParams, IStage, ILayer, IOption3D, ITicker } from '@visactor/vrender-core';
 import type { IPerformanceHook, RenderMode } from '../../typings/spec/common';
 import type { IBoundsLike } from '@visactor/vutils';
 import type { StringOrNumber } from '../../typings';
@@ -114,9 +114,15 @@ export interface IRenderOption {
    */
   canvasControled?: boolean;
   /**
-   * 外部传入的 VRender stage
+   * 外部传入的 VRender stage。普通用户仍应优先使用 dom/renderCanvas；
+   * 显式传入 stage 时，stage ownership 属于外部，VChart release 不会释放该 stage。
    */
   stage?: IStage;
+  /**
+   * 外部传入的 VRender app。VChart 可使用它创建内部 stage，但 app ownership 属于外部，
+   * VChart release 只释放自己创建的 stage，不释放该 app。
+   */
+  app?: IApp;
   /**
    * 外部传入的 VRender layer
    */
diff --git a/packages/vchart/src/compile/stage-app.ts b/packages/vchart/src/compile/stage-app.ts
new file mode 100644
index 0000000000..4cee1db3fc
--- /dev/null
+++ b/packages/vchart/src/compile/stage-app.ts
@@ -0,0 +1,51 @@
+import { createBrowserApp, createNodeApp, type IApp, type IStage, type IStageParams } from '@visactor/vrender-core';
+import {
+  installBrowserEnvToApp,
+  installBrowserPickersToApp,
+  installDefaultGraphicsToApp,
+  installNodeEnvToApp,
+  installNodePickersToApp
+} from '@visactor/vrender-kits';
+import { RenderModeEnum, type RenderMode } from '../typings/spec/common';
+
+type VRenderAppEnv = 'browser' | 'node';
+
+const defaultVRenderApps = new Map<VRenderAppEnv, IApp>();
+
+const getVRenderAppEnv = (mode?: RenderMode): VRenderAppEnv =>
+  mode === RenderModeEnum.node || mode === RenderModeEnum.worker ? 'node' : 'browser';
+
+// Default apps are an internal reuse detail; ordinary VChart users should keep using dom/renderCanvas.
+const createDefaultVRenderApp = (env: VRenderAppEnv): IApp => {
+  const app = env === 'node' ? createNodeApp() : createBrowserApp();
+
+  if (env === 'node') {
+    installNodeEnvToApp(app);
+    installDefaultGraphicsToApp(app);
+    installNodePickersToApp(app);
+  } else {
+    installBrowserEnvToApp(app);
+    installDefaultGraphicsToApp(app);
+    installBrowserPickersToApp(app);
+  }
+
+  return app;
+};
+
+export const getDefaultVRenderApp = (mode?: RenderMode): IApp => {
+  const env = getVRenderAppEnv(mode);
+  const app = defaultVRenderApps.get(env);
+
+  if (app && !app.released) {
+    return app;
+  }
+
+  const nextApp = createDefaultVRenderApp(env);
+  defaultVRenderApps.set(env, nextApp);
+
+  return nextApp;
+};
+
+export const resolveVRenderApp = (app: IApp | undefined, mode?: RenderMode): IApp => app ?? getDefaultVRenderApp(mode);
+
+export const createStageFromApp = (app: IApp, params: Partial<IStageParams>): IStage => app.createStage(params);
diff --git a/tools/story-player/package.json b/tools/story-player/package.json
index 3cc90f174c..6339cca92c 100644
--- a/tools/story-player/package.json
+++ b/tools/story-player/package.json
@@ -56,10 +56,10 @@
     "vite": "3.2.6"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.0.45",
-    "@visactor/vrender-kits": "1.0.45",
+    "@visactor/vrender-core": "1.1.0-alpha.2",
+    "@visactor/vrender-kits": "1.1.0-alpha.2",
     "@visactor/vchart": "workspace:2.0.22",
-    "@visactor/vrender": "1.0.45",
+    "@visactor/vrender": "1.1.0-alpha.2",
     "@visactor/vutils": "~1.0.23"
   }
-}
\ No newline at end of file
+}

From ea7a0007c054cf864b585e3bb10adf582e838144 Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Mon, 27 Apr 2026 15:49:00 +0800
Subject: [PATCH 2/8] chore: align VChart with VRender alpha app and state

Use root app factories for VChart-created stages.

Publish ordinary mark state styles as shared VRender state definitions.

Keep external stage and app ownership outside VChart release.

Constraint: Do not modify VRender or use local VRender2 links

Constraint: Keep this as a minimal VChart migration

Rejected: Manual app bootstrap | root VRender app creators are public API

Rejected: stateProxy normal path | VRender now owns resolvedStatePatch

Confidence: high

Scope-risk: moderate

Directive: Do not use direct addState/removeState for VChart state changes

Tested: install --check-only

Tested: test --only @visactor/vchart

Tested: build --to @visactor/vchart

Tested: eslint touched VChart files

Not-tested: Browser smoke lacked a local browser driver or harness
---
 common/config/rush/pnpm-lock.yaml             |   3 +
 .../__tests__/unit/core/vchart-event.test.ts  | 220 +++++++++++++++++-
 .../vchart/__tests__/unit/core/vchart.test.ts |  47 +++-
 packages/vchart/package.json                  |   1 +
 packages/vchart/src/compile/compiler.ts       |  76 +++---
 packages/vchart/src/compile/stage-app.ts      |  88 ++++---
 packages/vchart/src/component/brush/brush.ts  |  18 +-
 .../vchart/src/interaction/interaction.ts     |  19 +-
 .../triggers/element-active-by-legend.ts      |   3 +-
 .../interaction/triggers/element-active.ts    |   5 +-
 .../triggers/element-highlight-by-group.ts    |   3 +-
 .../triggers/element-highlight-by-legend.ts   |   3 +-
 .../triggers/element-highlight-by-name.ts     |   3 +-
 .../interaction/triggers/element-highlight.ts |   3 +-
 .../interaction/triggers/element-select.ts    |   5 +-
 packages/vchart/src/mark/base/base-mark.ts    |  88 ++++---
 packages/vchart/src/mark/utils/glyph.ts       |   5 +-
 packages/vchart/src/series/sankey/sankey.ts   |  71 +++---
 packages/vchart/src/util/graphic-state.ts     |  32 +++
 19 files changed, 523 insertions(+), 170 deletions(-)
 create mode 100644 packages/vchart/src/util/graphic-state.ts

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 546b5efda2..6862a761e8 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -528,6 +528,9 @@ importers:
       '@visactor/vlayouts':
         specifier: ~1.0.23
         version: 1.0.23
+      '@visactor/vrender':
+        specifier: 1.1.0-alpha.2
+        version: 1.1.0-alpha.2
       '@visactor/vrender-animate':
         specifier: 1.1.0-alpha.2
         version: 1.1.0-alpha.2
diff --git a/packages/vchart/__tests__/unit/core/vchart-event.test.ts b/packages/vchart/__tests__/unit/core/vchart-event.test.ts
index 2896595ed3..2483e9eb15 100644
--- a/packages/vchart/__tests__/unit/core/vchart-event.test.ts
+++ b/packages/vchart/__tests__/unit/core/vchart-event.test.ts
@@ -3,6 +3,7 @@ import VChart, {
   type BaseEventParams,
   type IBarChartSpec,
   type IChart,
+  type ILineChartSpec,
   type ICommonChartSpec,
   type IMark,
   type IMarkGraphic,
@@ -10,6 +11,22 @@ import VChart, {
 } from '../../../src';
 import { createDiv, removeDom } from '../../util/dom';
 
+type StateGraphic = IMarkGraphic & {
+  attribute: Record<string, unknown>;
+  resolvedStatePatch?: Record<string, unknown>;
+  stateProxy?: (stateName: string, states: string[]) => Record<string, unknown>;
+};
+
+type SharedStateDefinitions = Record<
+  string,
+  {
+    resolver?: (context: { graphic: IMarkGraphic }) => Record<string, unknown> | undefined;
+  }
+>;
+
+const getSharedStateDefinitions = (mark: IMark) =>
+  (mark.getProduct() as unknown as { sharedStateDefinitions?: SharedStateDefinitions }).sharedStateDefinitions;
+
 describe('vchart event test', () => {
   let container: HTMLElement;
   let dom: HTMLElement;
@@ -361,15 +378,14 @@ describe('vchart event test', () => {
         throw new Error('Expected bar mark to exist');
       }
 
-      const barGraphic = barMark.getGraphics()[0] as IMarkGraphic & {
-        attribute: Record<string, unknown>;
-        resolvedStatePatch?: Record<string, unknown>;
-        stateProxy?: (stateName: string, states: string[]) => Record<string, unknown>;
-      };
+      const barGraphic = barMark.getGraphics()[0] as StateGraphic;
       expect(barGraphic).toBeDefined();
-      expect(typeof barGraphic.stateProxy).toBe('function');
+      const sharedStateDefinitions = getSharedStateDefinitions(barMark);
+      expect(typeof sharedStateDefinitions?.hover?.resolver).toBe('function');
+      expect(typeof sharedStateDefinitions?.selected?.resolver).toBe('function');
+      expect(barGraphic.stateProxy).toBeFalsy();
 
-      const hoverPatch = barGraphic.stateProxy?.('hover', ['hover']);
+      const hoverPatch = sharedStateDefinitions.hover.resolver({ graphic: barGraphic });
       expect(hoverPatch?.fillOpacity).toBe(0.31);
 
       chart.getEvent().emit('pointerover', { item: barGraphic } as unknown as BaseEventParams);
@@ -384,7 +400,7 @@ describe('vchart event test', () => {
       expect(barGraphic.resolvedStatePatch).toBeUndefined();
       expect(barGraphic.attribute.fillOpacity).not.toBe(0.31);
 
-      const selectedPatch = barGraphic.stateProxy?.('selected', ['selected']);
+      const selectedPatch = sharedStateDefinitions.selected.resolver({ graphic: barGraphic });
       expect(selectedPatch?.stroke).toBe('#111827');
       expect(selectedPatch?.lineWidth).toBe(4);
 
@@ -395,6 +411,194 @@ describe('vchart event test', () => {
       expect(barGraphic.resolvedStatePatch?.lineWidth).toBe(4);
       expect(barGraphic.attribute.stroke).toBe('#111827');
       expect(barGraphic.attribute.lineWidth).toBe(4);
+
+      chartWithState.updateSpecSync({
+        ...(chartWithState.getSpec() as IBarChartSpec),
+        data: [
+          {
+            id: 'data',
+            values: [
+              { x: 'Mon', y: 14 },
+              { x: 'Tue', y: 16 }
+            ]
+          }
+        ]
+      });
+
+      barGraphic.useStates([]);
+
+      expect(barGraphic.resolvedStatePatch).toBeUndefined();
+      expect(barGraphic.attribute.stroke).not.toBe('#111827');
+      expect(barGraphic.attribute.lineWidth).not.toBe(4);
+    } finally {
+      chartWithState.release();
+      removeDom(stateContainer);
+    }
+  });
+
+  it('should apply line interaction state style through shared vrender state definitions', () => {
+    const stateContainer = createDiv();
+    const stateDom = createDiv(stateContainer);
+
+    const chartWithState = new VChart(
+      {
+        type: 'line',
+        width: 400,
+        height: 300,
+        data: [
+          {
+            id: 'data',
+            values: [
+              { x: 'Mon', y: 10 },
+              { x: 'Tue', y: 12 }
+            ]
+          }
+        ],
+        xField: 'x',
+        yField: 'y',
+        hover: { enable: true, trigger: 'pointerover', triggerOff: 'pointerout' },
+        select: { enable: true, trigger: 'click', mode: 'single' },
+        line: {
+          state: {
+            hover: {
+              stroke: '#ea580c'
+            },
+            selected: {
+              lineWidth: 5
+            }
+          }
+        }
+      } as ILineChartSpec,
+      {
+        dom: stateDom,
+        animation: false
+      }
+    );
+
+    chartWithState.renderSync();
+
+    try {
+      const chart = chartWithState.getChart() as IChart;
+      const lineSeries = chart.getAllSeries()[0];
+      const lineMark = lineSeries.getMarks().find((mark: IMark) => mark.name === 'line');
+      expect(lineMark).toBeDefined();
+      if (!lineMark) {
+        throw new Error('Expected line mark to exist');
+      }
+
+      const lineGraphic = lineMark.getGraphics()[0] as StateGraphic;
+      const sharedStateDefinitions = getSharedStateDefinitions(lineMark);
+      expect(typeof sharedStateDefinitions?.hover?.resolver).toBe('function');
+      expect(typeof sharedStateDefinitions?.selected?.resolver).toBe('function');
+      expect(lineGraphic.stateProxy).toBeFalsy();
+
+      expect(sharedStateDefinitions.hover.resolver({ graphic: lineGraphic })?.stroke).toBe('#ea580c');
+      chart.getEvent().emit('pointerover', { item: lineGraphic } as unknown as BaseEventParams);
+      expect(lineGraphic.hasState('hover')).toBe(true);
+      expect(lineGraphic.resolvedStatePatch?.stroke).toBe('#ea580c');
+      expect(lineGraphic.attribute.stroke).toBe('#ea580c');
+
+      chart.getEvent().emit('pointerout', { item: lineGraphic } as unknown as BaseEventParams);
+      expect(lineGraphic.hasState('hover')).toBe(false);
+      expect(lineGraphic.resolvedStatePatch).toBeUndefined();
+      expect(lineGraphic.attribute.stroke).not.toBe('#ea580c');
+
+      expect(sharedStateDefinitions.selected.resolver({ graphic: lineGraphic })?.lineWidth).toBe(5);
+      chart.getEvent().emit('click', { item: lineGraphic } as unknown as BaseEventParams);
+      expect(lineGraphic.hasState('selected')).toBe(true);
+      expect(lineGraphic.resolvedStatePatch?.lineWidth).toBe(5);
+      expect(lineGraphic.attribute.lineWidth).toBe(5);
+
+      lineGraphic.useStates([]);
+      expect(lineGraphic.resolvedStatePatch).toBeUndefined();
+    } finally {
+      chartWithState.release();
+      removeDom(stateContainer);
+    }
+  });
+
+  it('should apply text custom-mark interaction state style through shared vrender state definitions', () => {
+    const stateContainer = createDiv();
+    const stateDom = createDiv(stateContainer);
+
+    const chartWithState = new VChart(
+      {
+        type: 'bar',
+        width: 400,
+        height: 300,
+        data: [
+          {
+            id: 'data',
+            values: [{ x: 'Mon', y: 10 }]
+          }
+        ],
+        xField: 'x',
+        yField: 'y',
+        hover: { enable: true, trigger: 'pointerover', triggerOff: 'pointerout' },
+        select: { enable: true, trigger: 'click', mode: 'single' },
+        customMark: [
+          {
+            type: 'text',
+            name: 'stateText',
+            style: {
+              x: 40,
+              y: 40,
+              text: 'state',
+              fill: '#334155',
+              fontSize: 14
+            },
+            state: {
+              hover: {
+                fill: '#dc2626'
+              },
+              selected: {
+                fontSize: 24
+              }
+            }
+          }
+        ]
+      } as IBarChartSpec,
+      {
+        dom: stateDom,
+        animation: false
+      }
+    );
+
+    chartWithState.renderSync();
+
+    try {
+      const chart = chartWithState.getChart() as IChart;
+      const textMark = chart.getAllMarks().find((mark: IMark) => mark.name === 'stateText');
+      expect(textMark).toBeDefined();
+      if (!textMark) {
+        throw new Error('Expected text custom mark to exist');
+      }
+
+      const textGraphic = textMark.getGraphics()[0] as StateGraphic;
+      const sharedStateDefinitions = getSharedStateDefinitions(textMark);
+      expect(typeof sharedStateDefinitions?.hover?.resolver).toBe('function');
+      expect(typeof sharedStateDefinitions?.selected?.resolver).toBe('function');
+      expect(textGraphic.stateProxy).toBeFalsy();
+
+      expect(sharedStateDefinitions.hover.resolver({ graphic: textGraphic })?.fill).toBe('#dc2626');
+      textGraphic.useStates(['hover']);
+      expect(textGraphic.hasState('hover')).toBe(true);
+      expect(textGraphic.resolvedStatePatch?.fill).toBe('#dc2626');
+      expect(textGraphic.attribute.fill).toBe('#dc2626');
+
+      textGraphic.useStates([]);
+      expect(textGraphic.hasState('hover')).toBe(false);
+      expect(textGraphic.resolvedStatePatch).toBeUndefined();
+      expect(textGraphic.attribute.fill).not.toBe('#dc2626');
+
+      expect(sharedStateDefinitions.selected.resolver({ graphic: textGraphic })?.fontSize).toBe(24);
+      textGraphic.useStates(['selected']);
+      expect(textGraphic.hasState('selected')).toBe(true);
+      expect(textGraphic.resolvedStatePatch?.fontSize).toBe(24);
+      expect(textGraphic.attribute.fontSize).toBe(24);
+
+      textGraphic.useStates([]);
+      expect(textGraphic.resolvedStatePatch).toBeUndefined();
     } finally {
       chartWithState.release();
       removeDom(stateContainer);
diff --git a/packages/vchart/__tests__/unit/core/vchart.test.ts b/packages/vchart/__tests__/unit/core/vchart.test.ts
index 03a09b6b4e..d07f21e45e 100644
--- a/packages/vchart/__tests__/unit/core/vchart.test.ts
+++ b/packages/vchart/__tests__/unit/core/vchart.test.ts
@@ -1,9 +1,5 @@
-import { createBrowserApp, Stage, type Group, type IApp, type IArc, type Text } from '@visactor/vrender-core';
-import {
-  installBrowserEnvToApp,
-  installBrowserPickersToApp,
-  installDefaultGraphicsToApp
-} from '@visactor/vrender-kits';
+import { Stage, type Group, type IApp, type IArc, type Text } from '@visactor/vrender-core';
+import { createBrowserVRenderApp } from '@visactor/vrender';
 import type { IBarChartSpec } from '../../../src';
 import { default as VChart } from '../../../src';
 import { getDefaultVRenderApp } from '../../../src/compile/stage-app';
@@ -807,10 +803,7 @@ describe('VChart', () => {
     };
 
     const createExternalApp = () => {
-      externalApp = createBrowserApp();
-      installBrowserEnvToApp(externalApp);
-      installDefaultGraphicsToApp(externalApp);
-      installBrowserPickersToApp(externalApp);
+      externalApp = createBrowserVRenderApp();
       rawExternalAppRelease = externalApp.release.bind(externalApp);
       return externalApp;
     };
@@ -979,6 +972,40 @@ describe('VChart', () => {
       removeDom(secondCanvasDom);
     });
 
+    it('should release the fallback app after the last internally owned stage is released', () => {
+      const fallbackApp = getDefaultVRenderApp('desktop-browser');
+      const rawFallbackAppRelease = fallbackApp.release.bind(fallbackApp);
+      const releaseFallbackApp = jest.fn(() => rawFallbackAppRelease());
+      fallbackApp.release = releaseFallbackApp as any;
+      const secondCanvasDom = createCanvas();
+      secondCanvasDom.width = 200;
+      secondCanvasDom.height = 150;
+
+      const first = new VChart(spec(), {
+        renderCanvas: canvasDom,
+        animation: false
+      });
+      charts.push(first);
+      first.renderSync();
+
+      const second = new VChart(spec(6), {
+        renderCanvas: secondCanvasDom,
+        animation: false
+      });
+      charts.push(second);
+      second.renderSync();
+
+      first.release();
+
+      expect(releaseFallbackApp).not.toHaveBeenCalled();
+
+      second.release();
+
+      expect(releaseFallbackApp).toHaveBeenCalledTimes(1);
+      expect(getDefaultVRenderApp('desktop-browser')).not.toBe(fallbackApp);
+      removeDom(secondCanvasDom);
+    });
+
     it('should keep the default dom render path working with fallback app stage creation', () => {
       const dom = createDiv();
       const chart = new VChart(spec(), {
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index 477dd24a59..b684b8e7c0 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,6 +126,7 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
+    "@visactor/vrender": "1.1.0-alpha.2",
     "@visactor/vrender-core": "1.1.0-alpha.2",
     "@visactor/vrender-kits": "1.1.0-alpha.2",
     "@visactor/vrender-components": "1.1.0-alpha.2",
diff --git a/packages/vchart/src/compile/compiler.ts b/packages/vchart/src/compile/compiler.ts
index 9a4d257c86..54dbf1afeb 100644
--- a/packages/vchart/src/compile/compiler.ts
+++ b/packages/vchart/src/compile/compiler.ts
@@ -50,6 +50,8 @@ export class Compiler implements ICompiler {
 
   private _isExternalStage: boolean = false;
 
+  private _releaseVRenderAppRef?: () => void;
+
   protected _stateAnimationConfig: Partial<MarkAnimationSpec>;
   get stateAnimationConfig() {
     return this._stateAnimationConfig;
@@ -132,40 +134,49 @@ export class Compiler implements ICompiler {
     vglobal.setEnv(toRenderMode(mode), modeParams ?? {});
     const externalStage = this._option.stage;
     this._isExternalStage = !!externalStage;
+    this._releaseVRenderAppRef = undefined;
     this._stage = externalStage;
 
     if (!this._stage) {
-      const app = resolveVRenderApp(this._option.app, mode);
-      this._stage = createStageFromApp(app, {
-        background,
-        width: this._width,
-        height: this._height,
-        container: this._container.dom ?? null,
-        canvas: this._container.canvas ?? null,
-        dpr,
-        viewBox: this._option.viewBox,
-        canvasControled: this._option.canvasControled,
-        beforeRender: (stage: IStage) => {
-          this._compileChart?.onBeforeRender();
-          this._option.beforeRender?.(stage);
-        },
-        afterRender: this._option.afterRender,
-        disableDirtyBounds: true,
-        autoRender: true,
-        ticker: this._option.ticker,
-        pluginList: this._option.pluginList,
-        enableHtmlAttribute: this._option.enableHtmlAttribute,
-        optimize: this._option.optimize,
-        supportsTouchEvents: this._option.supportsTouchEvents,
-        supportsPointerEvents: this._option.supportsPointerEvents,
-        event: {
-          clickInterval: clickInterval,
-          autoPreventDefault: autoPreventDefault
-        },
-        ReactDOM: this._option.ReactDOM,
-        autoRefresh: isValid(autoRefreshDpr) ? autoRefreshDpr : !isValid(dpr),
-        ...(this._option.renderHooks ?? {})
-      }) as unknown as IStage;
+      const resolvedApp = resolveVRenderApp(this._option.app, mode);
+      this._releaseVRenderAppRef = resolvedApp.releaseAppRef;
+
+      try {
+        this._stage = createStageFromApp(resolvedApp.app, {
+          background,
+          width: this._width,
+          height: this._height,
+          container: this._container.dom ?? null,
+          canvas: this._container.canvas ?? null,
+          dpr,
+          viewBox: this._option.viewBox,
+          canvasControled: this._option.canvasControled,
+          beforeRender: (stage: IStage) => {
+            this._compileChart?.onBeforeRender();
+            this._option.beforeRender?.(stage);
+          },
+          afterRender: this._option.afterRender,
+          disableDirtyBounds: true,
+          autoRender: true,
+          ticker: this._option.ticker,
+          pluginList: this._option.pluginList,
+          enableHtmlAttribute: this._option.enableHtmlAttribute,
+          optimize: this._option.optimize,
+          supportsTouchEvents: this._option.supportsTouchEvents,
+          supportsPointerEvents: this._option.supportsPointerEvents,
+          event: {
+            clickInterval: clickInterval,
+            autoPreventDefault: autoPreventDefault
+          },
+          ReactDOM: this._option.ReactDOM,
+          autoRefresh: isValid(autoRefreshDpr) ? autoRefreshDpr : !isValid(dpr),
+          ...(this._option.renderHooks ?? {})
+        }) as unknown as IStage;
+      } catch (error) {
+        this._releaseVRenderAppRef?.();
+        this._releaseVRenderAppRef = undefined;
+        throw error;
+      }
     }
 
     this._stage.enableIncrementalAutoRender();
@@ -737,6 +748,7 @@ export class Compiler implements ICompiler {
     const stage = this._stage;
     const rootGroup = this._rootGroup;
     const shouldReleaseStage = !!stage && !this._isExternalStage;
+    const releaseVRenderAppRef = this._releaseVRenderAppRef;
 
     this.clearNextRender();
     this.releaseEvent();
@@ -751,9 +763,11 @@ export class Compiler implements ICompiler {
         rootGroup.release();
       }
     }
+    releaseVRenderAppRef?.();
     this._stage = null;
     this._rootGroup = null;
     this._isExternalStage = false;
+    this._releaseVRenderAppRef = undefined;
     this._option = this._container = null as any;
 
     this.isInited = false;
diff --git a/packages/vchart/src/compile/stage-app.ts b/packages/vchart/src/compile/stage-app.ts
index 4cee1db3fc..9f6ecb6853 100644
--- a/packages/vchart/src/compile/stage-app.ts
+++ b/packages/vchart/src/compile/stage-app.ts
@@ -1,51 +1,79 @@
-import { createBrowserApp, createNodeApp, type IApp, type IStage, type IStageParams } from '@visactor/vrender-core';
-import {
-  installBrowserEnvToApp,
-  installBrowserPickersToApp,
-  installDefaultGraphicsToApp,
-  installNodeEnvToApp,
-  installNodePickersToApp
-} from '@visactor/vrender-kits';
+import { createBrowserVRenderApp, createNodeVRenderApp } from '@visactor/vrender';
+import type { IApp, IStage, IStageParams } from '@visactor/vrender-core';
 import { RenderModeEnum, type RenderMode } from '../typings/spec/common';
 
 type VRenderAppEnv = 'browser' | 'node';
 
-const defaultVRenderApps = new Map<VRenderAppEnv, IApp>();
+type DefaultVRenderAppRecord = {
+  app: IApp;
+  refCount: number;
+};
+
+export type ResolvedVRenderApp = {
+  app: IApp;
+  releaseAppRef?: () => void;
+};
+
+const defaultVRenderApps = new Map<VRenderAppEnv, DefaultVRenderAppRecord>();
 
 const getVRenderAppEnv = (mode?: RenderMode): VRenderAppEnv =>
   mode === RenderModeEnum.node || mode === RenderModeEnum.worker ? 'node' : 'browser';
 
 // Default apps are an internal reuse detail; ordinary VChart users should keep using dom/renderCanvas.
-const createDefaultVRenderApp = (env: VRenderAppEnv): IApp => {
-  const app = env === 'node' ? createNodeApp() : createBrowserApp();
-
-  if (env === 'node') {
-    installNodeEnvToApp(app);
-    installDefaultGraphicsToApp(app);
-    installNodePickersToApp(app);
-  } else {
-    installBrowserEnvToApp(app);
-    installDefaultGraphicsToApp(app);
-    installBrowserPickersToApp(app);
+const createDefaultVRenderApp = (env: VRenderAppEnv): IApp =>
+  env === 'node' ? createNodeVRenderApp() : createBrowserVRenderApp();
+
+const getDefaultVRenderAppRecord = (mode?: RenderMode): DefaultVRenderAppRecord => {
+  const env = getVRenderAppEnv(mode);
+  const record = defaultVRenderApps.get(env);
+
+  if (record && !record.app.released) {
+    return record;
   }
 
-  return app;
+  const nextRecord = {
+    app: createDefaultVRenderApp(env),
+    refCount: 0
+  };
+  defaultVRenderApps.set(env, nextRecord);
+
+  return nextRecord;
 };
 
-export const getDefaultVRenderApp = (mode?: RenderMode): IApp => {
+export const getDefaultVRenderApp = (mode?: RenderMode): IApp => getDefaultVRenderAppRecord(mode).app;
+
+const retainDefaultVRenderApp = (mode?: RenderMode): ResolvedVRenderApp => {
   const env = getVRenderAppEnv(mode);
-  const app = defaultVRenderApps.get(env);
+  const record = getDefaultVRenderAppRecord(mode);
+  let released = false;
 
-  if (app && !app.released) {
-    return app;
-  }
+  record.refCount += 1;
 
-  const nextApp = createDefaultVRenderApp(env);
-  defaultVRenderApps.set(env, nextApp);
+  return {
+    app: record.app,
+    releaseAppRef: () => {
+      if (released) {
+        return;
+      }
+      released = true;
+      record.refCount -= 1;
 
-  return nextApp;
+      if (record.refCount <= 0) {
+        defaultVRenderApps.delete(env);
+        if (!record.app.released) {
+          record.app.release();
+        }
+      }
+    }
+  };
 };
 
-export const resolveVRenderApp = (app: IApp | undefined, mode?: RenderMode): IApp => app ?? getDefaultVRenderApp(mode);
+export const resolveVRenderApp = (app: IApp | undefined, mode?: RenderMode): ResolvedVRenderApp => {
+  if (app) {
+    return { app };
+  }
+
+  return retainDefaultVRenderApp(mode);
+};
 
 export const createStageFromApp = (app: IApp, params: Partial<IStageParams>): IStage => app.createStage(params);
diff --git a/packages/vchart/src/component/brush/brush.ts b/packages/vchart/src/component/brush/brush.ts
index d130fc57d1..686a974702 100644
--- a/packages/vchart/src/component/brush/brush.ts
+++ b/packages/vchart/src/component/brush/brush.ts
@@ -25,6 +25,7 @@ import { brush } from '../../theme/builtin/common/component/brush';
 import { isReverse, statePointToData } from '../data-zoom/util';
 import type { CartesianAxis } from '../axis/cartesian';
 import type { IRenderOption } from '../../compile/interface';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 const IN_BRUSH_STATE = 'inBrush';
 const OUT_BRUSH_STATE = 'outOfBrush';
@@ -499,15 +500,15 @@ export class Brush<T extends IBrushSpec = IBrushSpec> extends BaseComponent<T> i
         // now: 不在当前brush中
         const isBrushContainItem = this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem);
         if (this._outOfBrushElementsMap?.[elementKey] && isBrushContainItem) {
-          graphicItem.addState(IN_BRUSH_STATE, true);
+          addGraphicState(graphicItem, IN_BRUSH_STATE, true);
           if (!this._inBrushElementsMap[operateMask?.name]) {
             this._inBrushElementsMap[operateMask?.name] = {};
           }
           this._inBrushElementsMap[operateMask?.name][elementKey] = graphicItem;
           delete this._outOfBrushElementsMap[elementKey];
         } else if (this._inBrushElementsMap?.[operateMask?.name]?.[elementKey] && !isBrushContainItem) {
-          graphicItem.removeState(IN_BRUSH_STATE);
-          graphicItem.addState(OUT_BRUSH_STATE, true);
+          removeGraphicState(graphicItem, IN_BRUSH_STATE);
+          addGraphicState(graphicItem, OUT_BRUSH_STATE, true);
           this._outOfBrushElementsMap[elementKey] = graphicItem;
           delete this._inBrushElementsMap[operateMask.name][elementKey];
         }
@@ -577,7 +578,7 @@ export class Brush<T extends IBrushSpec = IBrushSpec> extends BaseComponent<T> i
               this._linkedOutOfBrushElementsMap?.[elementKey] &&
               this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem)
             ) {
-              graphicItem.addState(IN_BRUSH_STATE, true);
+              addGraphicState(graphicItem, IN_BRUSH_STATE, true);
               if (!this._linkedInBrushElementsMap[operateMask?.name]) {
                 this._linkedInBrushElementsMap[operateMask?.name] = {};
               }
@@ -587,8 +588,8 @@ export class Brush<T extends IBrushSpec = IBrushSpec> extends BaseComponent<T> i
               this._linkedInBrushElementsMap?.[operateMask?.name]?.[elementKey] &&
               !this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem)
             ) {
-              graphicItem.removeState(IN_BRUSH_STATE);
-              graphicItem.addState(OUT_BRUSH_STATE, true);
+              removeGraphicState(graphicItem, IN_BRUSH_STATE);
+              addGraphicState(graphicItem, OUT_BRUSH_STATE, true);
               this._linkedOutOfBrushElementsMap[elementKey] = graphicItem;
             }
           });
@@ -643,9 +644,8 @@ export class Brush<T extends IBrushSpec = IBrushSpec> extends BaseComponent<T> i
         }
         graphics.forEach((el: IMarkGraphic) => {
           const elementKey = mark.id + '_' + el.context.key;
-          el.removeState(IN_BRUSH_STATE);
-          el.removeState(OUT_BRUSH_STATE);
-          stateName && el.addState(stateName, true);
+          removeGraphicState(el, [IN_BRUSH_STATE, OUT_BRUSH_STATE]);
+          stateName && addGraphicState(el, stateName, true);
           elementMap[elementKey] = el;
         });
       });
diff --git a/packages/vchart/src/interaction/interaction.ts b/packages/vchart/src/interaction/interaction.ts
index b70d7dace8..c90456cd11 100644
--- a/packages/vchart/src/interaction/interaction.ts
+++ b/packages/vchart/src/interaction/interaction.ts
@@ -2,6 +2,7 @@ import type { StateValue } from '../compile/mark';
 import type { IMarkGraphic } from '../mark/interface';
 import type { IInteraction } from './interface/common';
 import type { ITrigger } from './interface/trigger';
+import { addGraphicState, removeGraphicState } from '../util/graphic-state';
 
 export class Interaction implements IInteraction {
   private _stateGraphicsByTrigger: Map<ITrigger, IMarkGraphic[]> = new Map();
@@ -89,7 +90,7 @@ export class Interaction implements IInteraction {
       if (hasReverse) {
         const m = g.parent?.mark;
         const hasAnimation = (m as any).hasAnimationByState && (m as any).hasAnimationByState('state');
-        g.addState(reverseState, true, hasAnimation);
+        addGraphicState(g, reverseState, true, hasAnimation);
       }
     });
 
@@ -100,7 +101,7 @@ export class Interaction implements IInteraction {
       if (hasReverse) {
         const m = g.parent?.mark;
         const hasAnimation = (m as any).hasAnimationByState && (m as any).hasAnimationByState('state');
-        g.removeState(reverseState, hasAnimation);
+        removeGraphicState(g, reverseState, hasAnimation);
       }
     });
   }
@@ -119,7 +120,7 @@ export class Interaction implements IInteraction {
       if (hasState) {
         const m = g.parent?.mark;
         const hasAnimation = (m as any).hasAnimationByState && (m as any).hasAnimationByState('state');
-        g.removeState(state, hasAnimation);
+        removeGraphicState(g, state, hasAnimation);
       }
     });
 
@@ -128,7 +129,7 @@ export class Interaction implements IInteraction {
       if (hasState) {
         const m = g.parent?.mark;
         const hasAnimation = (m as any).hasAnimationByState && (m as any).hasAnimationByState('state');
-        g.addState(state, true, hasAnimation);
+        addGraphicState(g, state, true, hasAnimation);
       }
     });
   }
@@ -155,11 +156,11 @@ export class Interaction implements IInteraction {
         const isStated = statedGraphics && statedGraphics.includes(g);
         if (isStated) {
           if (hasState) {
-            g.addState(state, true, hasAnimation);
+            addGraphicState(g, state, true, hasAnimation);
           }
         } else {
           if (hasReverse) {
-            g.addState(reverseState, true, hasAnimation);
+            addGraphicState(g, reverseState, true, hasAnimation);
           }
         }
       });
@@ -184,7 +185,7 @@ export class Interaction implements IInteraction {
 
         if (isStated) {
           if (hasState) {
-            g.addState(state, true, hasAnimation);
+            addGraphicState(g, state, true, hasAnimation);
           }
         }
       });
@@ -211,14 +212,14 @@ export class Interaction implements IInteraction {
         if (graphics && graphics.length) {
           if (reverseState && markIdByState[reverseState] && markIdByState[reverseState].includes(mark.id)) {
             graphics.forEach(g => {
-              g.removeState(reverseState, hasAnimation);
+              removeGraphicState(g, reverseState, hasAnimation);
             });
           }
 
           if (state && markIdByState[state] && markIdByState[state].includes(mark.id)) {
             graphics.forEach(g => {
               if (statedGraphics.includes(g)) {
-                g.removeState(state, hasAnimation);
+                removeGraphicState(g, state, hasAnimation);
               }
             });
           }
diff --git a/packages/vchart/src/interaction/triggers/element-active-by-legend.ts b/packages/vchart/src/interaction/triggers/element-active-by-legend.ts
index 160461fb2c..a9a317bd0a 100644
--- a/packages/vchart/src/interaction/triggers/element-active-by-legend.ts
+++ b/packages/vchart/src/interaction/triggers/element-active-by-legend.ts
@@ -7,6 +7,7 @@ import { generateFilterValue } from './util';
 import type { IMarkGraphic } from '../../mark/interface';
 import type { BaseEventParams } from '../../core';
 import { STATE_VALUE_ENUM } from '../../compile/mark/interface';
+import { removeGraphicState } from '../../util/graphic-state';
 
 const type = 'element-active-by-legend';
 const defaultOptions: Partial<IElementActiveByLegendOptions> = {
@@ -80,7 +81,7 @@ export class ElementActiveByLegend
     if (g) {
       const statedGraphics = interaction.getStatedGraphics(this);
       if (statedGraphics && statedGraphics.includes(g)) {
-        g.removeState(state);
+        removeGraphicState(g, state);
         interaction.setStatedGraphics(
           this,
           statedGraphics.filter(sg => sg !== g)
diff --git a/packages/vchart/src/interaction/triggers/element-active.ts b/packages/vchart/src/interaction/triggers/element-active.ts
index 02a450693d..74a5497cf0 100644
--- a/packages/vchart/src/interaction/triggers/element-active.ts
+++ b/packages/vchart/src/interaction/triggers/element-active.ts
@@ -5,6 +5,7 @@ import { TRIGGER_TYPE_ENUM } from './enum';
 import { STATE_VALUE_ENUM } from '../../compile/mark/interface';
 import type { IMarkGraphic } from '../../mark/interface';
 import type { BaseEventParams } from '../../event/interface';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 const defaultOptions: Partial<IElementActiveOptions> = {
   state: STATE_VALUE_ENUM.STATE_ACTIVE,
@@ -43,7 +44,7 @@ export class ElementActive extends BaseTrigger<IElementActiveOptions> implements
       const { state, interaction } = this.options;
 
       if (this.isGraphicInStateMark(g, state)) {
-        g.addState(state, true);
+        addGraphicState(g, state, true);
 
         interaction.setStatedGraphics(this, [g]);
       }
@@ -57,7 +58,7 @@ export class ElementActive extends BaseTrigger<IElementActiveOptions> implements
     const g = graphic ?? statedGraphics?.[0];
 
     if (g && statedGraphics?.includes(g)) {
-      g.removeState(state);
+      removeGraphicState(g, state);
       interaction.setStatedGraphics(
         this,
         statedGraphics.filter(sg => sg !== g)
diff --git a/packages/vchart/src/interaction/triggers/element-highlight-by-group.ts b/packages/vchart/src/interaction/triggers/element-highlight-by-group.ts
index 95d75dfa21..6f58a5f5bf 100644
--- a/packages/vchart/src/interaction/triggers/element-highlight-by-group.ts
+++ b/packages/vchart/src/interaction/triggers/element-highlight-by-group.ts
@@ -5,6 +5,7 @@ import type { IMarkGraphic } from '../../mark/interface';
 import { isNil } from '@visactor/vutils';
 import type { BaseEventParams } from '../../core';
 import { highlightDefaultOptions } from './util';
+import { removeGraphicState } from '../../util/graphic-state';
 
 const type = 'element-highlight-by-group';
 
@@ -84,7 +85,7 @@ export class ElementHighlightByGroup
         const { interaction } = this.options;
         const statedGraphics = interaction.getStatedGraphics(this);
 
-        g.removeState([this.options.highlightState, this.options.blurState]);
+        removeGraphicState(g, [this.options.highlightState, this.options.blurState]);
 
         interaction.setStatedGraphics(
           this,
diff --git a/packages/vchart/src/interaction/triggers/element-highlight-by-legend.ts b/packages/vchart/src/interaction/triggers/element-highlight-by-legend.ts
index 43065be4e9..b1032a83cc 100644
--- a/packages/vchart/src/interaction/triggers/element-highlight-by-legend.ts
+++ b/packages/vchart/src/interaction/triggers/element-highlight-by-legend.ts
@@ -6,6 +6,7 @@ import { ChartEvent } from '../../constant/event';
 import { generateFilterValue } from './util';
 import type { IMarkGraphic } from '../../mark/interface/common';
 import type { BaseEventParams } from '../../event/interface';
+import { removeGraphicState } from '../../util/graphic-state';
 
 const type = 'element-highlight-by-legend';
 const defaultOptions: Partial<IElementHighlightByLegendOptions> = {
@@ -84,7 +85,7 @@ export class ElementHighlightByLegend
     if (g) {
       const statedGraphics = interaction.getStatedGraphics(this);
       if (statedGraphics && statedGraphics.includes(g)) {
-        g.removeState([highlightState, blurState]);
+        removeGraphicState(g, [highlightState, blurState]);
         interaction.setStatedGraphics(
           this,
           statedGraphics.filter(sg => sg !== g)
diff --git a/packages/vchart/src/interaction/triggers/element-highlight-by-name.ts b/packages/vchart/src/interaction/triggers/element-highlight-by-name.ts
index 8b948c930c..6667b8ca1e 100644
--- a/packages/vchart/src/interaction/triggers/element-highlight-by-name.ts
+++ b/packages/vchart/src/interaction/triggers/element-highlight-by-name.ts
@@ -6,6 +6,7 @@ import type { BaseEventParams } from '../../event/interface';
 import { array } from '@visactor/vutils';
 import type { IMarkGraphic } from '../../mark/interface';
 import { generateFilterValue } from './util';
+import { removeGraphicState } from '../../util/graphic-state';
 
 const type = 'element-highlight-by-name';
 const defaultOptions: Partial<IElementHighlightByNameOptions> = {
@@ -95,7 +96,7 @@ export class ElementHighlightByName
     if (g) {
       const statedGraphics = interaction.getStatedGraphics(this);
       if (statedGraphics && statedGraphics.includes(g)) {
-        g.removeState([highlightState, blurState]);
+        removeGraphicState(g, [highlightState, blurState]);
         interaction.setStatedGraphics(
           this,
           statedGraphics.filter(sg => sg !== g)
diff --git a/packages/vchart/src/interaction/triggers/element-highlight.ts b/packages/vchart/src/interaction/triggers/element-highlight.ts
index 6a3bcddc80..dab41d2eb3 100644
--- a/packages/vchart/src/interaction/triggers/element-highlight.ts
+++ b/packages/vchart/src/interaction/triggers/element-highlight.ts
@@ -7,6 +7,7 @@ import { Factory } from '../../core/factory';
 import type { GraphicEventType } from '@visactor/vrender-core';
 import { TRIGGER_TYPE_ENUM } from './enum';
 import type { BaseEventParams } from '../../event/interface';
+import { removeGraphicState } from '../../util/graphic-state';
 
 const defaultOptions: Partial<IElementHighlightOptions> = {
   highlightState: STATE_VALUE_ENUM.STATE_HIGHLIGHT,
@@ -128,7 +129,7 @@ export class ElementHighlight
   reset(markGraphic: IMarkGraphic, e?: BaseEventParams) {
     if (markGraphic) {
       if (this._markSet.getMarkInId(markGraphic.context.markId)) {
-        markGraphic.removeState([this.options.highlightState, this.options.blurState]);
+        removeGraphicState(markGraphic, [this.options.highlightState, this.options.blurState]);
       }
     } else {
       this.resetAll(e);
diff --git a/packages/vchart/src/interaction/triggers/element-select.ts b/packages/vchart/src/interaction/triggers/element-select.ts
index b13b3acfe1..be8eb65fb1 100644
--- a/packages/vchart/src/interaction/triggers/element-select.ts
+++ b/packages/vchart/src/interaction/triggers/element-select.ts
@@ -7,6 +7,7 @@ import { parseTriggerOffOfSelect } from './util';
 import { Factory } from '../../core/factory';
 import { TRIGGER_TYPE_ENUM } from './enum';
 import type { BaseEventParams } from '../../event/interface';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 const defaultOptions: Partial<IElementSelectOptions> = {
   state: STATE_VALUE_ENUM.STATE_SELECTED,
@@ -117,7 +118,7 @@ export class ElementSelect extends BaseTrigger<IElementSelectOptions> implements
         if (this._timer) {
           clearTimeout(this._timer);
         }
-        markGraphic.addState(state, true);
+        addGraphicState(markGraphic, state, true);
 
         const newStatedGraphics = this.options.interaction.updateStates(
           this,
@@ -143,7 +144,7 @@ export class ElementSelect extends BaseTrigger<IElementSelectOptions> implements
   reset(markGraphic: IMarkGraphic, e?: BaseEventParams) {
     if (markGraphic) {
       if (this._markSet.getMarkInId(markGraphic.context.markId)) {
-        markGraphic.removeState([this.options.state, this.options.reverseState]);
+        removeGraphicState(markGraphic, [this.options.state, this.options.reverseState]);
       }
     } else {
       this.resetAll(e);
diff --git a/packages/vchart/src/mark/base/base-mark.ts b/packages/vchart/src/mark/base/base-mark.ts
index ea1e199480..25cc20920f 100644
--- a/packages/vchart/src/mark/base/base-mark.ts
+++ b/packages/vchart/src/mark/base/base-mark.ts
@@ -55,7 +55,14 @@ import { array, degreeToRadian, isArray, isBoolean, isFunction, isNil, isObject,
 import { curveTypeTransform, groupData, runEncoder } from '../utils/common';
 import type { ICompilableInitOption } from '../../compile/interface';
 import { LayoutState } from '../../compile/interface';
-import type { IGroupGraphicAttribute, IGraphicAttribute, IGroup, IGraphic } from '@visactor/vrender-core';
+import type {
+  IGroupGraphicAttribute,
+  IGraphicAttribute,
+  IGroup,
+  IGraphic,
+  StateDefinitionsInput,
+  StateResolveContext
+} from '@visactor/vrender-core';
 import { createGroup, CustomPath2D } from '@visactor/vrender-core';
 import { isStateAttrChangeable } from '../../compile/mark/util';
 import { Factory } from '../../core/factory';
@@ -70,6 +77,7 @@ import { CompilableData } from '../../compile/data/compilable-data';
 import { getDiffAttributesOfGraphic } from '../../util/mark';
 import { log } from '../../util/debug';
 import { morph as runMorph } from '../../compile/morph';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 export type ExChannelCall = (
   key: string | number | symbol,
@@ -257,13 +265,13 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
 
   protected _stateSort?: (stateA: string, stateB: string) => number;
 
-  declare protected _product: Maybe<IGroup>;
+  protected declare _product: Maybe<IGroup>;
   getProduct() {
     return this._product;
   }
 
   // 保存上一次的mark，用于morph的时候获取上次的图元
-  declare protected _lastMark?: IMark;
+  protected declare _lastMark?: IMark;
 
   /** 初始化 mark data */
   protected initMarkData(option: ICompilableInitOption) {
@@ -526,7 +534,7 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
     return this._simpleStyle;
   }
 
-  declare protected _option: IMarkOption;
+  protected declare _option: IMarkOption;
 
   protected _attributeContext: IModelMarkAttributeContext;
 
@@ -1155,8 +1163,8 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
     this._keyGetter = isFunction(this.key)
       ? (this.key as (datum: Datum) => string)
       : isValid(this.key)
-        ? (datum: Datum) => datum?.[this.key as string]
-        : (datum: Datum) => datum?.[DEFAULT_DATA_KEY];
+      ? (datum: Datum) => datum?.[this.key as string]
+      : (datum: Datum) => datum?.[DEFAULT_DATA_KEY];
     this._groupKeyGetter = isValid(this._groupKey)
       ? (datum: Datum) => {
           return `${datum?.[this._groupKey]}`;
@@ -1171,8 +1179,8 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
     const animationState = graphicsAnimationStates.every(state => state === AnimationStateEnum.appear)
       ? AnimationStateEnum.appear
       : graphicsAnimationStates.every(state => state === AnimationStateEnum.disappear)
-        ? AnimationStateEnum.disappear
-        : graphicsAnimationStates[0];
+      ? AnimationStateEnum.disappear
+      : graphicsAnimationStates[0];
     return animationState ?? AnimationStateEnum.none;
   }
 
@@ -1193,7 +1201,7 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
       ? {
           ...config,
           // 循环动画的优先级定为最高，不会被屏蔽掉
-          priority: type === 'normal' ? (config.priority ?? Infinity) : config.priority
+          priority: type === 'normal' ? config.priority ?? Infinity : config.priority
         }
       : config;
   }
@@ -1318,8 +1326,8 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
         return diffState === AnimationStateEnum.exit
           ? AnimationStateEnum.exit
           : diffState === AnimationStateEnum.update
-            ? AnimationStateEnum.update
-            : AnimationStateEnum.appear;
+          ? AnimationStateEnum.update
+          : AnimationStateEnum.appear;
       });
     const customizedState = callback(g);
 
@@ -1545,30 +1553,55 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
     g.stateProxy = null;
 
     if (g.context.diffState === DiffState.enter || g.context.diffState === DiffState.update) {
-      g.stateProxy = (stateName: string, nexStates: string[]) => {
-        return this._runEncoderOfGraphic(this._encoderOfState?.[stateName], g);
-      };
-
       g.context.states && g.useStates(g.context.states, hasAnimation);
     }
   };
 
+  protected _applySharedStateDefinitions() {
+    if (!this._product) {
+      return;
+    }
+
+    const stateNames = Object.keys(this._encoderOfState ?? {}).filter(
+      stateName => stateName !== 'group' && stateName !== 'update'
+    );
+
+    if (!stateNames.length) {
+      this._product.sharedStateDefinitions = undefined;
+      return;
+    }
+
+    const sortedStateNames = this._stateSort ? stateNames.slice().sort(this._stateSort) : stateNames;
+    const statePriority = new Map<string, number>();
+
+    sortedStateNames.forEach((stateName, index) => {
+      statePriority.set(stateName, index);
+    });
+
+    const sharedStateDefinitions: StateDefinitionsInput<Record<string, unknown>> = {};
+
+    stateNames.forEach(stateName => {
+      const encoder = this._encoderOfState[stateName];
+
+      sharedStateDefinitions[stateName] = {
+        priority: statePriority.get(stateName) ?? 0,
+        declaredAffectedKeys: Object.keys(encoder ?? {}),
+        resolver: ({ graphic }: StateResolveContext<Record<string, unknown>>) =>
+          this._runEncoderOfGraphic(encoder, graphic as IMarkGraphic)
+      };
+    });
+
+    this._product.sharedStateDefinitions = sharedStateDefinitions;
+  }
+
   protected _addProgressiveGraphic(parent: IGroup, g: IMarkGraphic) {
     (parent as IGroup).incrementalAppendChild(g);
   }
 
   protected _runEncoder(graphics: IMarkGraphic[], noGroupEncode?: boolean) {
     const attrsByGroup = noGroupEncode ? null : this._runGroupEncoder(this._encoderOfState?.group);
-    graphics.forEach((g, index) => {
-      let attrs = this._runEncoderOfGraphic(this._encoderOfState?.update, g);
-      // 此时需要将最终的正确的样式设置给graphic，这样后续的动画目标属性才会正确，否则会动画样式只有默认状态的样式
-      g.currentStates?.forEach((_state: string) => {
-        const stateAttr = this._runEncoderOfGraphic(this._encoderOfState?.[_state], g);
-        attrs = {
-          ...attrs,
-          ...stateAttr
-        };
-      });
+    graphics.forEach(g => {
+      const attrs = this._runEncoderOfGraphic(this._encoderOfState?.update, g);
 
       // 配置的优先级高于encoder
       if (!isNil(this._markConfig.interactive)) {
@@ -1754,6 +1787,7 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
 
   renderInner() {
     this._updateEncoderByState();
+    this._applySharedStateDefinitions();
 
     const data = this._data?.getProduct() ?? [{}];
 
@@ -1818,9 +1852,9 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
 
     this._graphics.forEach(g => {
       if (this.state.checkOneState(g, g.context.data, stateInfo) === 'in') {
-        g.addState(key, true, this.hasAnimationByState('state'));
+        addGraphicState(g, key, true, this.hasAnimationByState('state'));
       } else {
-        g.removeState(key, this.hasAnimationByState('state'));
+        removeGraphicState(g, key, this.hasAnimationByState('state'));
       }
     });
   }
diff --git a/packages/vchart/src/mark/utils/glyph.ts b/packages/vchart/src/mark/utils/glyph.ts
index fa249e08fe..44267562eb 100644
--- a/packages/vchart/src/mark/utils/glyph.ts
+++ b/packages/vchart/src/mark/utils/glyph.ts
@@ -1,4 +1,5 @@
 import type { IMarkGraphic } from '../interface/common';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 export const addRuntimeState = (
   g: IMarkGraphic,
@@ -14,7 +15,7 @@ export const addRuntimeState = (
   g.runtimeStateCache[stateName] = attrs;
 
   if (g.hasState(stateName)) {
-    g.removeState(stateName);
+    removeGraphicState(g, stateName);
   }
-  g.addState(stateName, keepCurrentStates, hasAnimation);
+  addGraphicState(g, stateName, keepCurrentStates, hasAnimation);
 };
diff --git a/packages/vchart/src/series/sankey/sankey.ts b/packages/vchart/src/series/sankey/sankey.ts
index 19989344b8..bcec07cf57 100644
--- a/packages/vchart/src/series/sankey/sankey.ts
+++ b/packages/vchart/src/series/sankey/sankey.ts
@@ -40,6 +40,7 @@ import type { ILabelSpec } from '../../component';
 import { getDatumOfGraphic } from '../../util';
 import { addRuntimeState } from '../../mark/utils/glyph';
 import { sankey } from '../../theme/builtin/common/series/sankey';
+import { addGraphicState, removeGraphicState } from '../../util/graphic-state';
 
 export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> extends CartesianSeries<T> {
   static readonly type: string = SeriesTypeEnum.sankey;
@@ -520,12 +521,10 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
     // const states = [STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE];
 
     allNodeElements.forEach(el => {
-      el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-      el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+      removeGraphicState(el, [STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE]);
     });
     allLinkElements.forEach(el => {
-      el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-      el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+      removeGraphicState(el, [STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE]);
     });
 
     this._needClear = false;
@@ -551,19 +550,19 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
             highlightNodes.push(linkDatum.target);
           }
 
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
-          linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true); // 设置上用户配置选中状态
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true); // 设置上用户配置选中状态
         } else if (linkDatum.target === nodeDatum.key) {
           // 上游link
           if (!highlightNodes.includes(linkDatum.source)) {
             highlightNodes.push(linkDatum.source);
           }
 
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
-          linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true); // 设置上用户配置选中状态
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true); // 设置上用户配置选中状态
         } else {
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-          linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
+          addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
         }
       });
     }
@@ -586,12 +585,12 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
       }
       allLinkElements.forEach(linkEl => {
         if (linkEl === graphic) {
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
 
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, { ratio: 1 });
         } else {
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-          linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
+          addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
         }
       });
     }
@@ -717,11 +716,11 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
           const linkDatum = getDatumOfGraphic(linkEl) as Datum;
 
           if (highlightLinks.includes(linkDatum.key ?? linkDatum.index)) {
-            linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
-            linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true);
+            removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+            addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true);
           } else {
-            linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-            linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+            removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
+            addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
           }
         });
       }
@@ -786,7 +785,7 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
           }, 0);
           const ratio = val / linkDatum.value;
 
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
 
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, { ratio });
 
@@ -803,7 +802,7 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
             highlightNodes.push(linkDatum.target);
           }
 
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
 
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, {
             ratio: upSelectedLink.value / linkDatum.value
@@ -812,8 +811,8 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
           return;
         }
 
-        linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-        linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+        removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
+        addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
 
         return;
       });
@@ -843,17 +842,19 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
       // const states = [STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE];
       if (this._linkMark) {
         allLinkElements.forEach(linkEl => {
-          // linkEl.removeState(states);
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, [
+            STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS,
+            STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE
+          ]);
         });
       }
 
       if (this._nodeMark) {
         allNodeElements.forEach(el => {
-          // el.removeState(states);
-          el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-          el.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(el, [
+            STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS,
+            STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE
+          ]);
         });
       }
     } else {
@@ -895,7 +896,7 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
 
         if (linkDatum.source === curLinkDatum.source && linkDatum.target === curLinkDatum.target) {
           // 自身
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, { ratio: 1 });
 
           return;
@@ -929,7 +930,7 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
             }, 0);
           const ratio = val / linkDatum.value;
 
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
 
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, { ratio }); // 设置默认的部分高亮
 
@@ -948,7 +949,7 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
           if (!highlightNodes.includes(linkDatum.target)) {
             highlightNodes.push(linkDatum.target);
           }
-          linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
+          removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE);
 
           addRuntimeState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, {
             ratio: upSelectedLink.value / (linkDatum as Datum).value
@@ -956,8 +957,8 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
 
           return;
         }
-        linkEl.removeState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
-        linkEl.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+        removeGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS);
+        addGraphicState(linkEl, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
 
         return;
       });
@@ -974,12 +975,12 @@ export class SankeySeries<T extends ISankeySeriesSpec = ISankeySeriesSpec> exten
     }
 
     graphics.forEach(g => {
-      g.removeState([STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS]);
+      removeGraphicState(g, [STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS]);
 
       if (highlightNodes.includes((getDatumOfGraphic(g) as Datum).key)) {
-        g.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true);
+        addGraphicState(g, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS, true);
       } else {
-        g.addState(STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
+        addGraphicState(g, STATE_VALUE_ENUM.STATE_SANKEY_EMPHASIS_REVERSE, true);
       }
     });
   }
diff --git a/packages/vchart/src/util/graphic-state.ts b/packages/vchart/src/util/graphic-state.ts
new file mode 100644
index 0000000000..3edd772621
--- /dev/null
+++ b/packages/vchart/src/util/graphic-state.ts
@@ -0,0 +1,32 @@
+import type { IMarkGraphic } from '../mark/interface';
+
+const normalizeStates = (states?: string | string[]) => (Array.isArray(states) ? states : states ? [states] : []);
+
+export const addGraphicState = (
+  graphic: IMarkGraphic,
+  state: string,
+  keepCurrentStates: boolean = true,
+  hasAnimation?: boolean
+) => {
+  if (!state) {
+    return;
+  }
+
+  const currentStates = keepCurrentStates ? graphic.currentStates ?? [] : [];
+  const nextStates = keepCurrentStates ? Array.from(new Set([...currentStates, state])) : [state];
+
+  graphic.useStates(nextStates, hasAnimation);
+};
+
+export const removeGraphicState = (graphic: IMarkGraphic, state: string | string[], hasAnimation?: boolean) => {
+  const states = normalizeStates(state);
+
+  if (!states.length) {
+    return;
+  }
+
+  const currentStates = (graphic.currentStates ?? []) as string[];
+  const nextStates = currentStates.filter((stateName: string) => !states.includes(stateName));
+
+  graphic.useStates(nextStates, hasAnimation);
+};

From 2a1b6ca9a529ac21d503fae73f08f1c52f666094 Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Tue, 5 May 2026 13:20:27 +0800
Subject: [PATCH 3/8] chore: enable VChart validation on VRender alpha.8

Consume published VRender alpha.8 packages for PR validation.

Refresh VChart direct deps and Rush lockfile only.

Constraint: Use npm registry @visactor/vrender*@1.1.0-alpha.8.

Constraint: No local VRender2 link, file, workspace, yalc, or pack.

Rejected: Local VRender2 linking | PR must test published packages.

Confidence: high

Scope-risk: moderate

Directive: Keep VRender release validation on npm artifacts.

Tested: rush update; rush install --check-only.

Tested: rush test --only @visactor/vchart.

Tested: rush build --to @visactor/vchart; browser smoke.
---
 common/config/rush/pnpm-lock.yaml      | 120 ++++++++++++-------------
 docs/package.json                      |   4 +-
 packages/openinula-vchart/package.json |   4 +-
 packages/react-vchart/package.json     |   4 +-
 packages/vchart-extension/package.json |   8 +-
 packages/vchart/package.json           |  10 +--
 tools/story-player/package.json        |   6 +-
 7 files changed, 78 insertions(+), 78 deletions(-)

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 6862a761e8..acd53bb92f 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -37,11 +37,11 @@ importers:
         specifier: 1.2.4-alpha.5
         version: 1.2.4-alpha.5
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vtable':
         specifier: 1.19.0-alpha.0
         version: 1.19.0-alpha.0
@@ -203,11 +203,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -294,11 +294,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart-extension
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -529,20 +529,20 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vscale':
         specifier: ~1.0.23
         version: 1.0.23
@@ -695,17 +695,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -1263,14 +1263,14 @@ importers:
         specifier: workspace:2.0.22
         version: link:../../packages/vchart
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.2
-        version: 1.1.0-alpha.2
+        specifier: 1.1.0-alpha.8
+        version: 1.1.0-alpha.8
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -3076,29 +3076,29 @@ packages:
   '@visactor/vrender-animate@1.0.0-alpha.18':
     resolution: {integrity: sha512-9kTtvp1ef+1t+AtUiza6A7qBQP7SmvOu3/ILGrqs/HGdZVj1XGjbYvD/X/zwKJ3LEb7gGV5fa8x95e4czTvRSA==}
 
-  '@visactor/vrender-animate@1.1.0-alpha.2':
-    resolution: {integrity: sha512-jlegJK92jVvT/PF16nMeRjlN68IoSgvJz/tKgsZVveJCt5276LSm2K5vpIjXNeaEKvmUnKes+45sZZX6kWNC/A==}
+  '@visactor/vrender-animate@1.1.0-alpha.8':
+    resolution: {integrity: sha512-T1CxxlF2QXJ9ElfzwV2tidNW2BZrU2kkk4vCEs7tAZbk4blQvFzUKosU+rZnIUXvmUQ1SLcjpGb6nwiJ35qL9Q==}
 
   '@visactor/vrender-components@1.0.0-alpha.18':
     resolution: {integrity: sha512-7Euq+ZfswL74n2pgkaqZSsPxoSa5SPIGyXatN1eUrdzM2Z0kX6U0RcJg01fctvRs4op6WhcecRLqGvnHcBeb9Q==}
 
-  '@visactor/vrender-components@1.1.0-alpha.2':
-    resolution: {integrity: sha512-nmBTBXEPvDRYbvXtOCLZ89+9WK1A+wLsE4RbuKv2omjA9D+XYRE7QZsW3O1g1/XBD8JqDVDiz+tk2Pp11IBBMw==}
+  '@visactor/vrender-components@1.1.0-alpha.8':
+    resolution: {integrity: sha512-UgiULMccv93ta4nI3Ee6LO8+LOpiZNDhXAM/xvFbIx9FccpOYWxdQKzH2B7aGzx1HlINz7Pp2+2QWi6+MedPHA==}
 
   '@visactor/vrender-core@1.0.0-alpha.18':
     resolution: {integrity: sha512-0ihtNvCyNkOsWPFgRqowHzq0IcQgS2Wl/nPpKbVtxWKveenwlhA+ZKoQvam6VJyBY7jeNe1pROy0mJMDyVAJQw==}
 
-  '@visactor/vrender-core@1.1.0-alpha.2':
-    resolution: {integrity: sha512-f9BTOK3Rv5yiTxMJMPLV2VYIgwcsakJ/VXCOp88hbWgy/or2pvPW4/42sOLALDMCjSnpd2zGhV2R2vy2+nSD2w==}
+  '@visactor/vrender-core@1.1.0-alpha.8':
+    resolution: {integrity: sha512-Wvvr+G9MVCkigaYoK1a7Y29AphgunA9cgGquetMF8Bch9emMv6KeFer2hUgSCRxD90MV53OxNHKtpIKVVSOPkw==}
 
   '@visactor/vrender-kits@1.0.0-alpha.18':
     resolution: {integrity: sha512-Tvolkq+4G8qiPFZo0Aj8M//Yr6jR2h8FNkFEyWM9gbQbEiTkjpmHAJOYnoSsaPtPrcMSlG4EhJSFDk6ymANHVg==}
 
-  '@visactor/vrender-kits@1.1.0-alpha.2':
-    resolution: {integrity: sha512-fXG3SB/PDcmA2oK6FqHTBvb8438BDeeCkpIvzt1WI/lpghzD6wT7Dd3HhY8hxonArDUUqAsorZyFeQFNPezUTQ==}
+  '@visactor/vrender-kits@1.1.0-alpha.8':
+    resolution: {integrity: sha512-jBUL5UMlEArtjrbHs0D+cNmr2sNgDpDTVcWNRg43JeK4JZTL+WySx5UkAjkUY03N0arskX96O6g5sQwZG9u5tQ==}
 
-  '@visactor/vrender@1.1.0-alpha.2':
-    resolution: {integrity: sha512-MNW2rTJLgCVa0T4TTavQ+fMJp2PkvkZwxWAwdzdNlFAIX9WriF24L+Xqwu8nvqsJiUz2BEUbgYP+7sTZcn7krA==}
+  '@visactor/vrender@1.1.0-alpha.8':
+    resolution: {integrity: sha512-Bn9T9T3yFqpvSS7rVTqvL59A6XPsGDY5TNqJ8K1Mb1GCASfv5YS70NTFmQipvWHrebatWyodKlEEradbflMegA==}
 
   '@visactor/vscale@0.18.18':
     resolution: {integrity: sha512-iRG4kv+5Fv4KX3AxEfV95XU3I6OmF0QizyAhqHxKa7L1MaT+MRvDDk5zHWf1E8gialLbL2xDe3GnT6g/4u5jhA==}
@@ -15029,9 +15029,9 @@ snapshots:
       '@visactor/vrender-core': 1.0.0-alpha.18
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-animate@1.1.0-alpha.2':
+  '@visactor/vrender-animate@1.1.0-alpha.8':
     dependencies:
-      '@visactor/vrender-core': 1.1.0-alpha.2
+      '@visactor/vrender-core': 1.1.0-alpha.8
       '@visactor/vutils': 1.0.23
 
   '@visactor/vrender-components@1.0.0-alpha.18':
@@ -15042,11 +15042,11 @@ snapshots:
       '@visactor/vscale': 1.0.4
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-components@1.1.0-alpha.2':
+  '@visactor/vrender-components@1.1.0-alpha.8':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.2
-      '@visactor/vrender-core': 1.1.0-alpha.2
-      '@visactor/vrender-kits': 1.1.0-alpha.2
+      '@visactor/vrender-animate': 1.1.0-alpha.8
+      '@visactor/vrender-core': 1.1.0-alpha.8
+      '@visactor/vrender-kits': 1.1.0-alpha.8
       '@visactor/vscale': 1.0.23
       '@visactor/vutils': 1.0.23
 
@@ -15055,7 +15055,7 @@ snapshots:
       '@visactor/vutils': 1.0.4
       color-convert: 2.0.1
 
-  '@visactor/vrender-core@1.1.0-alpha.2':
+  '@visactor/vrender-core@1.1.0-alpha.8':
     dependencies:
       '@visactor/vutils': 1.0.23
       color-convert: 2.0.1
@@ -15069,21 +15069,21 @@ snapshots:
       lottie-web: 5.13.0
       roughjs: 4.5.2
 
-  '@visactor/vrender-kits@1.1.0-alpha.2':
+  '@visactor/vrender-kits@1.1.0-alpha.8':
     dependencies:
       '@resvg/resvg-js': 2.4.1
-      '@visactor/vrender-core': 1.1.0-alpha.2
+      '@visactor/vrender-core': 1.1.0-alpha.8
       '@visactor/vutils': 1.0.23
       gifuct-js: 2.1.2
       lottie-web: 5.13.0
       roughjs: 4.6.6
 
-  '@visactor/vrender@1.1.0-alpha.2':
+  '@visactor/vrender@1.1.0-alpha.8':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.2
-      '@visactor/vrender-components': 1.1.0-alpha.2
-      '@visactor/vrender-core': 1.1.0-alpha.2
-      '@visactor/vrender-kits': 1.1.0-alpha.2
+      '@visactor/vrender-animate': 1.1.0-alpha.8
+      '@visactor/vrender-components': 1.1.0-alpha.8
+      '@visactor/vrender-core': 1.1.0-alpha.8
+      '@visactor/vrender-kits': 1.1.0-alpha.8
 
   '@visactor/vscale@0.18.18':
     dependencies:
diff --git a/docs/package.json b/docs/package.json
index 916924de51..98e9ce617c 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,8 +19,8 @@
     "@visactor/vchart-theme": "~1.6.6",
     "@visactor/vmind": "1.2.4-alpha.5",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
     "@visactor/vtable": "1.19.0-alpha.0",
     "@visactor/vtable-editors": "1.19.0-alpha.0",
     "@visactor/vtable-gantt": "1.19.0-alpha.0",
diff --git a/packages/openinula-vchart/package.json b/packages/openinula-vchart/package.json
index bcc4a86dae..4a6f62d2a5 100644
--- a/packages/openinula-vchart/package.json
+++ b/packages/openinula-vchart/package.json
@@ -30,8 +30,8 @@
   "dependencies": {
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender-core": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json
index 3aa5be4964..c9afe14ab1 100644
--- a/packages/react-vchart/package.json
+++ b/packages/react-vchart/package.json
@@ -36,8 +36,8 @@
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vchart-extension": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender-core": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json
index 25789045fa..5136ef67d1 100644
--- a/packages/vchart-extension/package.json
+++ b/packages/vchart-extension/package.json
@@ -26,10 +26,10 @@
     "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
-    "@visactor/vrender-components": "1.1.0-alpha.2",
-    "@visactor/vrender-animate": "1.1.0-alpha.2",
+    "@visactor/vrender-core": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender-components": "1.1.0-alpha.8",
+    "@visactor/vrender-animate": "1.1.0-alpha.8",
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
     "@visactor/vdataset": "~1.0.23",
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index b684b8e7c0..6052a4dc7f 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,11 +126,11 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.2",
-    "@visactor/vrender-core": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
-    "@visactor/vrender-components": "1.1.0-alpha.2",
-    "@visactor/vrender-animate": "1.1.0-alpha.2",
+    "@visactor/vrender": "1.1.0-alpha.8",
+    "@visactor/vrender-core": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender-components": "1.1.0-alpha.8",
+    "@visactor/vrender-animate": "1.1.0-alpha.8",
     "@visactor/vutils-extension": "workspace:2.0.22"
   },
   "publishConfig": {
diff --git a/tools/story-player/package.json b/tools/story-player/package.json
index 6339cca92c..64bea90749 100644
--- a/tools/story-player/package.json
+++ b/tools/story-player/package.json
@@ -56,10 +56,10 @@
     "vite": "3.2.6"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.2",
-    "@visactor/vrender-kits": "1.1.0-alpha.2",
+    "@visactor/vrender-core": "1.1.0-alpha.8",
+    "@visactor/vrender-kits": "1.1.0-alpha.8",
     "@visactor/vchart": "workspace:2.0.22",
-    "@visactor/vrender": "1.1.0-alpha.2",
+    "@visactor/vrender": "1.1.0-alpha.8",
     "@visactor/vutils": "~1.0.23"
   }
 }

From 6bc51096f693f7460b10320b4e817af385a7c3a3 Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Tue, 5 May 2026 16:34:34 +0800
Subject: [PATCH 4/8] test: cover VChart animation static truth with manual
 ticker

Add deterministic regression coverage for VRender alpha animation behavior.

It exercises VChart with ManualTicker instead of browser timing.

Constraint: Uses npm VRender through VChart exports.

Confidence: high

Scope-risk: narrow

Tested: eslint touched animation test --quiet

Tested: jest manual-ticker.test.ts --runInBand --silent=false

Tested: rush test --only @visactor/vchart

Tested: rush install --check-only

Tested: rush build --to @visactor/vchart
---
 .../unit/animation/manual-ticker.test.ts      | 403 ++++++++++++++++++
 1 file changed, 403 insertions(+)
 create mode 100644 packages/vchart/__tests__/unit/animation/manual-ticker.test.ts

diff --git a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
new file mode 100644
index 0000000000..fe9b5662b0
--- /dev/null
+++ b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
@@ -0,0 +1,403 @@
+import VChart, {
+  ManualTicker,
+  type IBarChartSpec,
+  type IChart,
+  type IMark,
+  type IMarkGraphic,
+  type ISeries
+} from '../../../src';
+import { createDiv, removeDom } from '../../util/dom';
+
+type AnimatedGraphic = IMarkGraphic & {
+  attribute: Record<string, any>;
+  baseAttributes?: Record<string, any>;
+  finalAttribute?: Record<string, any>;
+  getFinalAttribute?: () => Record<string, any> | undefined;
+};
+
+type TraversableGraphic = AnimatedGraphic & {
+  type?: string;
+  name?: string;
+  parent?: TraversableGraphic;
+  forEachChildren?: (cb: (child: TraversableGraphic) => void | boolean) => void;
+};
+
+const APPEAR_DURATION = 300;
+const UPDATE_DURATION = 300;
+const COLOR_BY_SERIES: Record<string, string> = {
+  'east-利润profit': '#8D72F6',
+  'east-销售量sales': '#5766EC',
+  'north of east-利润profit': '#66A3FE',
+  'north of east-销售量sales': '#51D5E6'
+};
+
+const createChartContainer = () => {
+  const container = createDiv();
+  const dom = createDiv(container);
+
+  container.style.position = 'fixed';
+  container.style.width = '500px';
+  container.style.height = '500px';
+  container.style.top = '0px';
+  container.style.left = '0px';
+
+  return { container, dom };
+};
+
+const createManualTicker = () => {
+  const ticker = new ManualTicker();
+
+  ticker.autoStop = false;
+
+  return ticker;
+};
+
+const getGraphicFinalAttribute = (graphic: AnimatedGraphic) =>
+  graphic.finalAttribute ?? graphic.getFinalAttribute?.() ?? {};
+
+const expectClose = (actual: number, expected: number) => {
+  expect(actual).toBeCloseTo(expected, 6);
+};
+
+const expectBarYLayout = (graphic: AnimatedGraphic, expected: { y: number; y1: number }) => {
+  expectClose(graphic.attribute.y, expected.y);
+  expectClose(graphic.attribute.y1, expected.y1);
+  expectClose(graphic.baseAttributes?.y, expected.y);
+  expectClose(graphic.baseAttributes?.y1, expected.y1);
+  expectClose(getGraphicFinalAttribute(graphic).y, expected.y);
+  expectClose(getGraphicFinalAttribute(graphic).y1, expected.y1);
+};
+
+const getBarGraphics = (chart: VChart) => {
+  const model = chart.getChart() as IChart;
+  const barSeries = model.getAllSeries().find((series: ISeries) => series.type === 'bar');
+
+  expect(barSeries).toBeDefined();
+  if (!barSeries) {
+    throw new Error('Expected bar series to exist');
+  }
+
+  const barMark = barSeries.getMarks().find((mark: IMark) => mark.name === 'bar');
+
+  expect(barMark).toBeDefined();
+  if (!barMark) {
+    throw new Error('Expected bar mark to exist');
+  }
+
+  return barMark.getGraphics() as AnimatedGraphic[];
+};
+
+const walkGraphics = (root: TraversableGraphic, visitor: (graphic: TraversableGraphic) => void) => {
+  visitor(root);
+  root.forEachChildren?.((child: TraversableGraphic) => {
+    walkGraphics(child, visitor);
+  });
+};
+
+const getAnimatedLabelTexts = (chart: VChart) => {
+  const texts: TraversableGraphic[] = [];
+
+  walkGraphics(chart.getStage() as unknown as TraversableGraphic, graphic => {
+    if (graphic.type === 'text' && graphic.baseAttributes && getGraphicFinalAttribute(graphic).x !== undefined) {
+      texts.push(graphic);
+    }
+  });
+
+  return texts;
+};
+
+const getLabelTextByFill = (chart: VChart, fill: string) => {
+  const label = getAnimatedLabelTexts(chart).find(graphic => graphic.attribute.fill === fill);
+
+  expect(label).toBeDefined();
+  if (!label) {
+    throw new Error(`Expected label with fill ${fill} to exist`);
+  }
+
+  return label;
+};
+
+const getVisibleBarByFill = (chart: VChart, fill: string) => {
+  const bar = getBarGraphics(chart).find(
+    graphic => graphic.attribute.fill === fill && graphic.attribute.visible !== false
+  );
+
+  expect(bar).toBeDefined();
+  if (!bar) {
+    throw new Error(`Expected visible bar with fill ${fill} to exist`);
+  }
+
+  return bar;
+};
+
+const getBarCenterX = (graphic: AnimatedGraphic) => {
+  const attrs = getGraphicFinalAttribute(graphic);
+
+  return attrs.x + attrs.width / 2;
+};
+
+const expectStaticXLayout = (graphic: AnimatedGraphic, expectedX: number) => {
+  expectClose(graphic.baseAttributes?.x, expectedX);
+  expectClose(getGraphicFinalAttribute(graphic).x, expectedX);
+};
+
+describe('manual ticker animation regressions', () => {
+  it('keeps default bar appear starts out of static truth', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      {
+        type: 'bar',
+        width: 400,
+        height: 300,
+        data: [
+          {
+            id: 'barData',
+            values: [{ month: 'Monday', sales: 22 }]
+          }
+        ],
+        xField: 'month',
+        yField: 'sales',
+        axes: [
+          { orient: 'left', visible: false },
+          { orient: 'bottom', visible: false }
+        ],
+        animationAppear: {
+          duration: APPEAR_DURATION,
+          easing: 'linear'
+        }
+      } as IBarChartSpec,
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      const barGraphic = getBarGraphics(chart)[0];
+      const expectedFinalLayout = {
+        y: getGraphicFinalAttribute(barGraphic).y,
+        y1: getGraphicFinalAttribute(barGraphic).y1
+      };
+
+      expect(expectedFinalLayout.y).not.toBe(expectedFinalLayout.y1);
+      expect(barGraphic.baseAttributes?.y).toBe(expectedFinalLayout.y);
+      expect(barGraphic.baseAttributes?.y1).toBe(expectedFinalLayout.y1);
+
+      ticker.tickAt(APPEAR_DURATION / 2);
+
+      expect(barGraphic.attribute.y).not.toBe(barGraphic.attribute.y1);
+      expect(barGraphic.baseAttributes?.y).toBe(expectedFinalLayout.y);
+      expect(barGraphic.baseAttributes?.y1).toBe(expectedFinalLayout.y1);
+      expect(getGraphicFinalAttribute(barGraphic).y).toBe(expectedFinalLayout.y);
+      expect(getGraphicFinalAttribute(barGraphic).y1).toBe(expectedFinalLayout.y1);
+
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      expectBarYLayout(barGraphic, expectedFinalLayout);
+      expect(barGraphic.attribute.y).not.toBe(barGraphic.attribute.y1);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps data label update final x at the filtered bar position', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      {
+        type: 'bar',
+        direction: 'vertical',
+        width: 500,
+        height: 300,
+        xField: ['date', '__DimGroup__'],
+        yField: '__MeaValue__',
+        seriesField: '__DimGroupID__',
+        padding: 0,
+        region: [
+          {
+            clip: true
+          }
+        ],
+        animation: true,
+        animationAppear: {
+          duration: APPEAR_DURATION,
+          easing: 'linear'
+        },
+        animationUpdate: {
+          duration: UPDATE_DURATION,
+          easing: 'linear'
+        },
+        stackCornerRadius: [4, 4, 0, 0],
+        color: {
+          type: 'ordinal',
+          domain: Object.keys(COLOR_BY_SERIES),
+          range: Object.values(COLOR_BY_SERIES),
+          specified: {}
+        },
+        data: {
+          values: [
+            {
+              date: '2019',
+              region: 'east',
+              __OriginalData__: {
+                date: '2019',
+                region: 'east',
+                profit: 10,
+                sales: 20
+              },
+              profit: 10,
+              __MeaId__: 'profit',
+              __MeaName__: '利润',
+              __MeaValue__: 10,
+              __DimGroup__: 'east-利润',
+              __DimGroupID__: 'east-利润profit'
+            },
+            {
+              date: '2019',
+              region: 'east',
+              __OriginalData__: {
+                date: '2019',
+                region: 'east',
+                profit: 10,
+                sales: 20
+              },
+              sales: 20,
+              __MeaId__: 'sales',
+              __MeaName__: '销售量',
+              __MeaValue__: 20,
+              __DimGroup__: 'east-销售量',
+              __DimGroupID__: 'east-销售量sales'
+            },
+            {
+              date: '2019',
+              region: 'north of east',
+              __OriginalData__: {
+                date: '2019',
+                region: 'north of east',
+                profit: 10,
+                sales: 20
+              },
+              profit: 10,
+              __MeaId__: 'profit',
+              __MeaName__: '利润',
+              __MeaValue__: 10,
+              __DimGroup__: 'north of east-利润',
+              __DimGroupID__: 'north of east-利润profit'
+            },
+            {
+              date: '2019',
+              region: 'north of east',
+              __OriginalData__: {
+                date: '2019',
+                region: 'north of east',
+                profit: 10,
+                sales: 20
+              },
+              sales: 20,
+              __MeaId__: 'sales',
+              __MeaName__: '销售量',
+              __MeaValue__: 20,
+              __DimGroup__: 'north of east-销售量',
+              __DimGroupID__: 'north of east-销售量sales'
+            }
+          ]
+        },
+        label: {
+          visible: true,
+          animationUpdate: {
+            duration: UPDATE_DURATION,
+            easing: 'linear'
+          }
+        },
+        legends: {
+          type: 'discrete',
+          visible: true,
+          maxCol: 1,
+          maxRow: 1,
+          autoPage: true,
+          orient: 'right',
+          position: 'start',
+          item: {
+            focus: true,
+            background: {
+              state: {
+                selectedHover: {
+                  fill: '#646A73',
+                  fillOpacity: 0.05
+                }
+              }
+            }
+          }
+        }
+      } as unknown as IBarChartSpec,
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const retainedSeries = Object.keys(COLOR_BY_SERIES).slice(0, 3);
+      const initialXBySeries = retainedSeries.reduce<Record<string, number>>((result, seriesName) => {
+        result[seriesName] = getLabelTextByFill(chart, COLOR_BY_SERIES[seriesName]).attribute.x;
+        return result;
+      }, {});
+
+      chart.setLegendSelectedDataByIndex(0, retainedSeries);
+      chart.renderSync();
+
+      const updateStart = ticker.getTime();
+      const expectedXBySeries = retainedSeries.reduce<Record<string, number>>((result, seriesName) => {
+        result[seriesName] = getBarCenterX(getVisibleBarByFill(chart, COLOR_BY_SERIES[seriesName]));
+        return result;
+      }, {});
+
+      expect(retainedSeries.some(seriesName => initialXBySeries[seriesName] !== expectedXBySeries[seriesName])).toBe(
+        true
+      );
+
+      retainedSeries.forEach(seriesName => {
+        expectStaticXLayout(getLabelTextByFill(chart, COLOR_BY_SERIES[seriesName]), expectedXBySeries[seriesName]);
+      });
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      retainedSeries.forEach(seriesName => {
+        const label = getLabelTextByFill(chart, COLOR_BY_SERIES[seriesName]);
+        const startX = initialXBySeries[seriesName];
+        const finalX = expectedXBySeries[seriesName];
+
+        if (startX !== finalX) {
+          expect(label.attribute.x).toBeGreaterThanOrEqual(Math.min(startX, finalX));
+          expect(label.attribute.x).toBeLessThanOrEqual(Math.max(startX, finalX));
+        }
+        expectStaticXLayout(label, finalX);
+      });
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      retainedSeries.forEach(seriesName => {
+        const label = getLabelTextByFill(chart, COLOR_BY_SERIES[seriesName]);
+        const finalX = expectedXBySeries[seriesName];
+
+        expectClose(label.attribute.x, finalX);
+        expectStaticXLayout(label, finalX);
+      });
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+});

From 5ba9f2c7860641ce46059ca6d768b7b294c0f9bd Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Wed, 6 May 2026 14:30:31 +0800
Subject: [PATCH 5/8] fix: keep miniapp builds from resolving shadow-root
 fallback

VRender alpha leaves a shadow-root fallback require inside the generated VChart

miniapp bundle. The lark and wx second-pass Rollup builds should leave

that fallback untouched instead of converting it into an unresolved proxy module.

Constraint: Do not patch VRender or generated miniapp bundles.

Rejected: Commit regenerated miniapp bundles | generated outputs are not source.

Confidence: high

Scope-risk: narrow

Tested: node common/scripts/install-run-rush.js build --to @visactor/vchart

Tested: node common/scripts/install-run-rush.js build --only @visactor/lark-vchart

Tested: node common/scripts/install-run-rush.js build --only @visactor/wx-vchart

Tested: eslint lark/wx rollup configs --quiet

Tested: node common/scripts/install-run-rush.js install --check-only
---
 packages/lark-vchart/rollup.config.ts | 3 ++-
 packages/wx-vchart/rollup.config.ts   | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/packages/lark-vchart/rollup.config.ts b/packages/lark-vchart/rollup.config.ts
index 315e9985b7..97be5ae637 100644
--- a/packages/lark-vchart/rollup.config.ts
+++ b/packages/lark-vchart/rollup.config.ts
@@ -18,7 +18,8 @@ let options = defineConfig([
     plugins: [
       resolve(),
       commonjs({
-        include: './src/vchart/index.js'
+        include: './src/vchart/index.js',
+        ignore: ['./shadow-root']
       }),
       babel({
         presets: [['@babel/preset-env']],
diff --git a/packages/wx-vchart/rollup.config.ts b/packages/wx-vchart/rollup.config.ts
index f76310a3b8..a91b225958 100644
--- a/packages/wx-vchart/rollup.config.ts
+++ b/packages/wx-vchart/rollup.config.ts
@@ -18,7 +18,8 @@ let options = defineConfig([
     plugins: [
       resolve(),
       commonjs({
-        include: './miniprogram/src/vchart/index.js'
+        include: './miniprogram/src/vchart/index.js',
+        ignore: ['./shadow-root']
       }),
       babel({
         // presets: [['@babel/preset-env']],

From db138714da0975f0f8724b71113bd453fb3e40d7 Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Wed, 6 May 2026 17:31:10 +0800
Subject: [PATCH 6/8] chore: validate VRender alpha.9 animation fix

Consume the npm-published VRender alpha.9 packages and lock the
stackCornerRadius repeated legend filtering regression with a manual
ticker test. The test interrupts update animations and checks final bar
plus clip-path static truth.

Constraint: Registry packages only; no local VRender link or pack.
Rejected: Browser-only manual repro | timing regressions need deterministic checks.
Confidence: high
Scope-risk: moderate
Directive: Keep this regression on manual ticker timing.
Tested: npm test -- --runInBand __tests__/unit/animation/manual-ticker.test.ts
Tested: node common/scripts/install-run-rush.js install --check-only
Tested: eslint packages/vchart/__tests__/unit/animation/manual-ticker.test.ts --quiet
Tested: node common/scripts/install-run-rush.js test --only @visactor/vchart
Tested: node common/scripts/install-run-rush.js build --to @visactor/vchart
Not-tested: Full browser visual smoke in this turn.
---
 common/config/rush/pnpm-lock.yaml             | 120 ++++-----
 docs/package.json                             |   4 +-
 packages/openinula-vchart/package.json        |   4 +-
 packages/react-vchart/package.json            |   4 +-
 packages/vchart-extension/package.json        |   8 +-
 .../unit/animation/manual-ticker.test.ts      | 247 ++++++++++++++++++
 packages/vchart/package.json                  |  10 +-
 tools/story-player/package.json               |   6 +-
 8 files changed, 325 insertions(+), 78 deletions(-)

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index acd53bb92f..15def21eab 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -37,11 +37,11 @@ importers:
         specifier: 1.2.4-alpha.5
         version: 1.2.4-alpha.5
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vtable':
         specifier: 1.19.0-alpha.0
         version: 1.19.0-alpha.0
@@ -203,11 +203,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -294,11 +294,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart-extension
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -529,20 +529,20 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vscale':
         specifier: ~1.0.23
         version: 1.0.23
@@ -695,17 +695,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -1263,14 +1263,14 @@ importers:
         specifier: workspace:2.0.22
         version: link:../../packages/vchart
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.8
-        version: 1.1.0-alpha.8
+        specifier: 1.1.0-alpha.9
+        version: 1.1.0-alpha.9
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -3076,29 +3076,29 @@ packages:
   '@visactor/vrender-animate@1.0.0-alpha.18':
     resolution: {integrity: sha512-9kTtvp1ef+1t+AtUiza6A7qBQP7SmvOu3/ILGrqs/HGdZVj1XGjbYvD/X/zwKJ3LEb7gGV5fa8x95e4czTvRSA==}
 
-  '@visactor/vrender-animate@1.1.0-alpha.8':
-    resolution: {integrity: sha512-T1CxxlF2QXJ9ElfzwV2tidNW2BZrU2kkk4vCEs7tAZbk4blQvFzUKosU+rZnIUXvmUQ1SLcjpGb6nwiJ35qL9Q==}
+  '@visactor/vrender-animate@1.1.0-alpha.9':
+    resolution: {integrity: sha512-dVWOYQeZmttKLBlkl38aDSXzcz8Oqygc8M7RKSCP+jsb0s2kns9jaiaGaK2J1uEEtLxJ4Itz3CGedB+QaqtF9Q==}
 
   '@visactor/vrender-components@1.0.0-alpha.18':
     resolution: {integrity: sha512-7Euq+ZfswL74n2pgkaqZSsPxoSa5SPIGyXatN1eUrdzM2Z0kX6U0RcJg01fctvRs4op6WhcecRLqGvnHcBeb9Q==}
 
-  '@visactor/vrender-components@1.1.0-alpha.8':
-    resolution: {integrity: sha512-UgiULMccv93ta4nI3Ee6LO8+LOpiZNDhXAM/xvFbIx9FccpOYWxdQKzH2B7aGzx1HlINz7Pp2+2QWi6+MedPHA==}
+  '@visactor/vrender-components@1.1.0-alpha.9':
+    resolution: {integrity: sha512-4PMPlLiy+Eyo9+Zhx2vKOw6WvpDQYWUaSrr4gRRKUaTmhEtJk7YNHlrhO37YBvv6iHLhKdhIQR23GvFL7AJ/Gg==}
 
   '@visactor/vrender-core@1.0.0-alpha.18':
     resolution: {integrity: sha512-0ihtNvCyNkOsWPFgRqowHzq0IcQgS2Wl/nPpKbVtxWKveenwlhA+ZKoQvam6VJyBY7jeNe1pROy0mJMDyVAJQw==}
 
-  '@visactor/vrender-core@1.1.0-alpha.8':
-    resolution: {integrity: sha512-Wvvr+G9MVCkigaYoK1a7Y29AphgunA9cgGquetMF8Bch9emMv6KeFer2hUgSCRxD90MV53OxNHKtpIKVVSOPkw==}
+  '@visactor/vrender-core@1.1.0-alpha.9':
+    resolution: {integrity: sha512-qs7iA2BKFWWQILTkWvLYjcjror2+oXMKsVE35UoqTSHMXgzqk7Em1oGAFgukEKRcYRqKMop6oNfUG8DbvH5dcg==}
 
   '@visactor/vrender-kits@1.0.0-alpha.18':
     resolution: {integrity: sha512-Tvolkq+4G8qiPFZo0Aj8M//Yr6jR2h8FNkFEyWM9gbQbEiTkjpmHAJOYnoSsaPtPrcMSlG4EhJSFDk6ymANHVg==}
 
-  '@visactor/vrender-kits@1.1.0-alpha.8':
-    resolution: {integrity: sha512-jBUL5UMlEArtjrbHs0D+cNmr2sNgDpDTVcWNRg43JeK4JZTL+WySx5UkAjkUY03N0arskX96O6g5sQwZG9u5tQ==}
+  '@visactor/vrender-kits@1.1.0-alpha.9':
+    resolution: {integrity: sha512-dACr5rnEkzncekbfraYqcDK9LVYxcUnMN/73OSwrpeog2dbwFDZ/iBoET724NecpmGmi3WAEE+pINB6/acBgvA==}
 
-  '@visactor/vrender@1.1.0-alpha.8':
-    resolution: {integrity: sha512-Bn9T9T3yFqpvSS7rVTqvL59A6XPsGDY5TNqJ8K1Mb1GCASfv5YS70NTFmQipvWHrebatWyodKlEEradbflMegA==}
+  '@visactor/vrender@1.1.0-alpha.9':
+    resolution: {integrity: sha512-vaUTjFPwEsZS3iIrYY5VY5bCMa45hWT5JlO1OP9daKMxN+3nU67bRRCAT1vbOTG69RCcYyqarFsTMl3wS8BqEQ==}
 
   '@visactor/vscale@0.18.18':
     resolution: {integrity: sha512-iRG4kv+5Fv4KX3AxEfV95XU3I6OmF0QizyAhqHxKa7L1MaT+MRvDDk5zHWf1E8gialLbL2xDe3GnT6g/4u5jhA==}
@@ -15029,9 +15029,9 @@ snapshots:
       '@visactor/vrender-core': 1.0.0-alpha.18
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-animate@1.1.0-alpha.8':
+  '@visactor/vrender-animate@1.1.0-alpha.9':
     dependencies:
-      '@visactor/vrender-core': 1.1.0-alpha.8
+      '@visactor/vrender-core': 1.1.0-alpha.9
       '@visactor/vutils': 1.0.23
 
   '@visactor/vrender-components@1.0.0-alpha.18':
@@ -15042,11 +15042,11 @@ snapshots:
       '@visactor/vscale': 1.0.4
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-components@1.1.0-alpha.8':
+  '@visactor/vrender-components@1.1.0-alpha.9':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.8
-      '@visactor/vrender-core': 1.1.0-alpha.8
-      '@visactor/vrender-kits': 1.1.0-alpha.8
+      '@visactor/vrender-animate': 1.1.0-alpha.9
+      '@visactor/vrender-core': 1.1.0-alpha.9
+      '@visactor/vrender-kits': 1.1.0-alpha.9
       '@visactor/vscale': 1.0.23
       '@visactor/vutils': 1.0.23
 
@@ -15055,7 +15055,7 @@ snapshots:
       '@visactor/vutils': 1.0.4
       color-convert: 2.0.1
 
-  '@visactor/vrender-core@1.1.0-alpha.8':
+  '@visactor/vrender-core@1.1.0-alpha.9':
     dependencies:
       '@visactor/vutils': 1.0.23
       color-convert: 2.0.1
@@ -15069,21 +15069,21 @@ snapshots:
       lottie-web: 5.13.0
       roughjs: 4.5.2
 
-  '@visactor/vrender-kits@1.1.0-alpha.8':
+  '@visactor/vrender-kits@1.1.0-alpha.9':
     dependencies:
       '@resvg/resvg-js': 2.4.1
-      '@visactor/vrender-core': 1.1.0-alpha.8
+      '@visactor/vrender-core': 1.1.0-alpha.9
       '@visactor/vutils': 1.0.23
       gifuct-js: 2.1.2
       lottie-web: 5.13.0
       roughjs: 4.6.6
 
-  '@visactor/vrender@1.1.0-alpha.8':
+  '@visactor/vrender@1.1.0-alpha.9':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.8
-      '@visactor/vrender-components': 1.1.0-alpha.8
-      '@visactor/vrender-core': 1.1.0-alpha.8
-      '@visactor/vrender-kits': 1.1.0-alpha.8
+      '@visactor/vrender-animate': 1.1.0-alpha.9
+      '@visactor/vrender-components': 1.1.0-alpha.9
+      '@visactor/vrender-core': 1.1.0-alpha.9
+      '@visactor/vrender-kits': 1.1.0-alpha.9
 
   '@visactor/vscale@0.18.18':
     dependencies:
diff --git a/docs/package.json b/docs/package.json
index 98e9ce617c..d8e19498e3 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,8 +19,8 @@
     "@visactor/vchart-theme": "~1.6.6",
     "@visactor/vmind": "1.2.4-alpha.5",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
     "@visactor/vtable": "1.19.0-alpha.0",
     "@visactor/vtable-editors": "1.19.0-alpha.0",
     "@visactor/vtable-gantt": "1.19.0-alpha.0",
diff --git a/packages/openinula-vchart/package.json b/packages/openinula-vchart/package.json
index 4a6f62d2a5..cf107f14f2 100644
--- a/packages/openinula-vchart/package.json
+++ b/packages/openinula-vchart/package.json
@@ -30,8 +30,8 @@
   "dependencies": {
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender-core": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json
index c9afe14ab1..9f362934ab 100644
--- a/packages/react-vchart/package.json
+++ b/packages/react-vchart/package.json
@@ -36,8 +36,8 @@
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vchart-extension": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender-core": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json
index 5136ef67d1..994627a02c 100644
--- a/packages/vchart-extension/package.json
+++ b/packages/vchart-extension/package.json
@@ -26,10 +26,10 @@
     "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
-    "@visactor/vrender-components": "1.1.0-alpha.8",
-    "@visactor/vrender-animate": "1.1.0-alpha.8",
+    "@visactor/vrender-core": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender-components": "1.1.0-alpha.9",
+    "@visactor/vrender-animate": "1.1.0-alpha.9",
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
     "@visactor/vdataset": "~1.0.23",
diff --git a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
index fe9b5662b0..20f0745176 100644
--- a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
+++ b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
@@ -141,6 +141,178 @@ const expectStaticXLayout = (graphic: AnimatedGraphic, expectedX: number) => {
   expectClose(getGraphicFinalAttribute(graphic).x, expectedX);
 };
 
+const expectStaticRectLayout = (
+  graphic: AnimatedGraphic,
+  expected: { x: number; width: number; y: number; y1: number }
+) => {
+  expectClose(graphic.attribute.x, expected.x);
+  expectClose(graphic.attribute.width, expected.width);
+  expectClose(graphic.attribute.y, expected.y);
+  expectClose(graphic.attribute.y1, expected.y1);
+  expectClose(graphic.baseAttributes?.x, expected.x);
+  expectClose(graphic.baseAttributes?.width, expected.width);
+  expectClose(graphic.baseAttributes?.y, expected.y);
+  expectClose(graphic.baseAttributes?.y1, expected.y1);
+  expectClose(getGraphicFinalAttribute(graphic).x, expected.x);
+  expectClose(getGraphicFinalAttribute(graphic).width, expected.width);
+  expectClose(getGraphicFinalAttribute(graphic).y, expected.y);
+  expectClose(getGraphicFinalAttribute(graphic).y1, expected.y1);
+};
+
+const getBarMarkProduct = (chart: VChart) => {
+  const model = chart.getChart() as IChart;
+  const barSeries = model.getAllSeries().find((series: ISeries) => series.type === 'bar');
+
+  expect(barSeries).toBeDefined();
+  if (!barSeries) {
+    throw new Error('Expected bar series to exist');
+  }
+
+  const barMark = barSeries.getMarks().find((mark: IMark) => mark.name === 'bar') as IMark & {
+    getProduct?: () => AnimatedGraphic;
+  };
+
+  expect(barMark).toBeDefined();
+  if (!barMark?.getProduct) {
+    throw new Error('Expected bar mark product to exist');
+  }
+
+  return barMark.getProduct();
+};
+
+const getBarClipPathRects = (chart: VChart) =>
+  ((getBarMarkProduct(chart).attribute.path ?? []) as AnimatedGraphic[]).map(
+    path => path.baseAttributes ?? path.attribute
+  );
+
+const createStackCornerLegendSpec = () =>
+  ({
+    type: 'bar',
+    direction: 'vertical',
+    width: 500,
+    height: 300,
+    xField: ['date', '__DimGroup__'],
+    yField: '__MeaValue__',
+    seriesField: '__DimGroupID__',
+    padding: 0,
+    region: [
+      {
+        clip: true
+      }
+    ],
+    animation: true,
+    animationAppear: {
+      duration: APPEAR_DURATION,
+      easing: 'linear'
+    },
+    animationUpdate: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    stackCornerRadius: [4, 4, 0, 0],
+    color: {
+      type: 'ordinal',
+      domain: Object.keys(COLOR_BY_SERIES),
+      range: Object.values(COLOR_BY_SERIES),
+      specified: {}
+    },
+    data: {
+      values: [
+        {
+          date: '2019',
+          region: 'east',
+          __OriginalData__: {
+            date: '2019',
+            region: 'east',
+            profit: 10,
+            sales: 20
+          },
+          profit: 10,
+          __MeaId__: 'profit',
+          __MeaName__: '利润',
+          __MeaValue__: 10,
+          __DimGroup__: 'east-利润',
+          __DimGroupID__: 'east-利润profit'
+        },
+        {
+          date: '2019',
+          region: 'east',
+          __OriginalData__: {
+            date: '2019',
+            region: 'east',
+            profit: 10,
+            sales: 20
+          },
+          sales: 20,
+          __MeaId__: 'sales',
+          __MeaName__: '销售量',
+          __MeaValue__: 20,
+          __DimGroup__: 'east-销售量',
+          __DimGroupID__: 'east-销售量sales'
+        },
+        {
+          date: '2019',
+          region: 'north of east',
+          __OriginalData__: {
+            date: '2019',
+            region: 'north of east',
+            profit: 10,
+            sales: 20
+          },
+          profit: 10,
+          __MeaId__: 'profit',
+          __MeaName__: '利润',
+          __MeaValue__: 10,
+          __DimGroup__: 'north of east-利润',
+          __DimGroupID__: 'north of east-利润profit'
+        },
+        {
+          date: '2019',
+          region: 'north of east',
+          __OriginalData__: {
+            date: '2019',
+            region: 'north of east',
+            profit: 10,
+            sales: 20
+          },
+          sales: 20,
+          __MeaId__: 'sales',
+          __MeaName__: '销售量',
+          __MeaValue__: 20,
+          __DimGroup__: 'north of east-销售量',
+          __DimGroupID__: 'north of east-销售量sales'
+        }
+      ]
+    },
+    label: {
+      visible: true,
+      animationUpdate: {
+        duration: UPDATE_DURATION,
+        easing: 'linear'
+      }
+    },
+    legends: {
+      type: 'discrete',
+      visible: true,
+      maxCol: 1,
+      maxRow: 1,
+      autoPage: true,
+      orient: 'right',
+      position: 'start',
+      item: {
+        focus: true,
+        background: {
+          state: {
+            selectedHover: {
+              fill: '#646A73',
+              fillOpacity: 0.05
+            }
+          }
+        }
+      }
+    }
+  } as unknown as IBarChartSpec);
+
 describe('manual ticker animation regressions', () => {
   it('keeps default bar appear starts out of static truth', () => {
     const { container, dom } = createChartContainer();
@@ -400,4 +572,79 @@ describe('manual ticker animation regressions', () => {
       removeDom(container);
     }
   });
+
+  it('keeps stack corner clip paths aligned after interrupted legend update animations', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createStackCornerLegendSpec(), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const allSeries = Object.keys(COLOR_BY_SERIES);
+      const retainedSeries = allSeries.slice(0, 3);
+
+      chart.setLegendSelectedDataByIndex(0, retainedSeries);
+      chart.renderSync();
+
+      const firstUpdate = ticker.getTime();
+
+      ticker.tickAt(firstUpdate + UPDATE_DURATION / 2);
+
+      chart.setLegendSelectedDataByIndex(0, allSeries);
+      chart.renderSync();
+
+      const secondUpdate = ticker.getTime();
+
+      ticker.tickAt(secondUpdate + UPDATE_DURATION / 2);
+
+      chart.setLegendSelectedDataByIndex(0, retainedSeries);
+      chart.renderSync();
+
+      const thirdUpdate = ticker.getTime();
+      const expectedRects = retainedSeries.map(seriesName => {
+        const graphic = getVisibleBarByFill(chart, COLOR_BY_SERIES[seriesName]);
+        const finalAttribute = getGraphicFinalAttribute(graphic);
+
+        return {
+          x: finalAttribute.x,
+          width: finalAttribute.width,
+          y: finalAttribute.y,
+          y1: finalAttribute.y1
+        };
+      });
+      const clipPathRects = getBarClipPathRects(chart);
+
+      expect(clipPathRects.length).toBe(retainedSeries.length);
+      clipPathRects.forEach((clipPathRect, index) => {
+        expectClose(clipPathRect.x, expectedRects[index].x);
+        expectClose(clipPathRect.width, expectedRects[index].width);
+        expectClose(clipPathRect.y, expectedRects[index].y);
+        expectClose(clipPathRect.y1, expectedRects[index].y1);
+      });
+
+      ticker.tickAt(thirdUpdate + UPDATE_DURATION + 50);
+
+      const finalClipPathRects = getBarClipPathRects(chart);
+
+      expect(finalClipPathRects.length).toBe(retainedSeries.length);
+      retainedSeries.forEach((seriesName, index) => {
+        expectStaticRectLayout(getVisibleBarByFill(chart, COLOR_BY_SERIES[seriesName]), expectedRects[index]);
+        expectClose(finalClipPathRects[index].x, expectedRects[index].x);
+        expectClose(finalClipPathRects[index].width, expectedRects[index].width);
+        expectClose(finalClipPathRects[index].y, expectedRects[index].y);
+        expectClose(finalClipPathRects[index].y1, expectedRects[index].y1);
+      });
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
 });
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index 6052a4dc7f..5667cd6aeb 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,11 +126,11 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.8",
-    "@visactor/vrender-core": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
-    "@visactor/vrender-components": "1.1.0-alpha.8",
-    "@visactor/vrender-animate": "1.1.0-alpha.8",
+    "@visactor/vrender": "1.1.0-alpha.9",
+    "@visactor/vrender-core": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender-components": "1.1.0-alpha.9",
+    "@visactor/vrender-animate": "1.1.0-alpha.9",
     "@visactor/vutils-extension": "workspace:2.0.22"
   },
   "publishConfig": {
diff --git a/tools/story-player/package.json b/tools/story-player/package.json
index 64bea90749..69247dd3b7 100644
--- a/tools/story-player/package.json
+++ b/tools/story-player/package.json
@@ -56,10 +56,10 @@
     "vite": "3.2.6"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.8",
-    "@visactor/vrender-kits": "1.1.0-alpha.8",
+    "@visactor/vrender-core": "1.1.0-alpha.9",
+    "@visactor/vrender-kits": "1.1.0-alpha.9",
     "@visactor/vchart": "workspace:2.0.22",
-    "@visactor/vrender": "1.1.0-alpha.8",
+    "@visactor/vrender": "1.1.0-alpha.9",
     "@visactor/vutils": "~1.0.23"
   }
 }

From 645cbef2244f4e06f8adcee083e13e493b94e89a Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Wed, 6 May 2026 20:17:59 +0800
Subject: [PATCH 7/8] feat: upgrade vrender to fix clipPath

---
 common/config/rush/pnpm-lock.yaml             | 120 +++++++++---------
 docs/package.json                             |   4 +-
 packages/openinula-vchart/package.json        |   4 +-
 packages/react-vchart/package.json            |   4 +-
 packages/vchart-extension/package.json        |   8 +-
 .../unit/animation/manual-ticker.test.ts      |  74 +++++++++++
 packages/vchart/package.json                  |  10 +-
 tools/story-player/package.json               |   6 +-
 8 files changed, 152 insertions(+), 78 deletions(-)

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 15def21eab..5b8a30cce6 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -37,11 +37,11 @@ importers:
         specifier: 1.2.4-alpha.5
         version: 1.2.4-alpha.5
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vtable':
         specifier: 1.19.0-alpha.0
         version: 1.19.0-alpha.0
@@ -203,11 +203,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -294,11 +294,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart-extension
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -529,20 +529,20 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vscale':
         specifier: ~1.0.23
         version: 1.0.23
@@ -695,17 +695,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -1263,14 +1263,14 @@ importers:
         specifier: workspace:2.0.22
         version: link:../../packages/vchart
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.9
-        version: 1.1.0-alpha.9
+        specifier: 1.1.0-alpha.11
+        version: 1.1.0-alpha.11
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -3076,29 +3076,29 @@ packages:
   '@visactor/vrender-animate@1.0.0-alpha.18':
     resolution: {integrity: sha512-9kTtvp1ef+1t+AtUiza6A7qBQP7SmvOu3/ILGrqs/HGdZVj1XGjbYvD/X/zwKJ3LEb7gGV5fa8x95e4czTvRSA==}
 
-  '@visactor/vrender-animate@1.1.0-alpha.9':
-    resolution: {integrity: sha512-dVWOYQeZmttKLBlkl38aDSXzcz8Oqygc8M7RKSCP+jsb0s2kns9jaiaGaK2J1uEEtLxJ4Itz3CGedB+QaqtF9Q==}
+  '@visactor/vrender-animate@1.1.0-alpha.11':
+    resolution: {integrity: sha512-RgWD8BYip6QvrkQsXJWvHvboWoyuvHmycxDY4Pq5NKyWxatns5S4n1kwdeViRtBd8bKklVAc/UE4d3BbGv2zJA==}
 
   '@visactor/vrender-components@1.0.0-alpha.18':
     resolution: {integrity: sha512-7Euq+ZfswL74n2pgkaqZSsPxoSa5SPIGyXatN1eUrdzM2Z0kX6U0RcJg01fctvRs4op6WhcecRLqGvnHcBeb9Q==}
 
-  '@visactor/vrender-components@1.1.0-alpha.9':
-    resolution: {integrity: sha512-4PMPlLiy+Eyo9+Zhx2vKOw6WvpDQYWUaSrr4gRRKUaTmhEtJk7YNHlrhO37YBvv6iHLhKdhIQR23GvFL7AJ/Gg==}
+  '@visactor/vrender-components@1.1.0-alpha.11':
+    resolution: {integrity: sha512-yGxjh1vYyxiSQoPLxJYpRgLtcQlpT0k+p2IGKvtU7zt6RGFSDSThpSHnAwHcWkFKy6X/6r8P28YmoYl5hhLWSw==}
 
   '@visactor/vrender-core@1.0.0-alpha.18':
     resolution: {integrity: sha512-0ihtNvCyNkOsWPFgRqowHzq0IcQgS2Wl/nPpKbVtxWKveenwlhA+ZKoQvam6VJyBY7jeNe1pROy0mJMDyVAJQw==}
 
-  '@visactor/vrender-core@1.1.0-alpha.9':
-    resolution: {integrity: sha512-qs7iA2BKFWWQILTkWvLYjcjror2+oXMKsVE35UoqTSHMXgzqk7Em1oGAFgukEKRcYRqKMop6oNfUG8DbvH5dcg==}
+  '@visactor/vrender-core@1.1.0-alpha.11':
+    resolution: {integrity: sha512-BGTtnZAmRkPredqFwLnVxoRbb/OLRbVfAjR3gz8cWjrz6QwL8WJT7QgzQX/GvjZcIio6q01ONPejLWLnVmVWrQ==}
 
   '@visactor/vrender-kits@1.0.0-alpha.18':
     resolution: {integrity: sha512-Tvolkq+4G8qiPFZo0Aj8M//Yr6jR2h8FNkFEyWM9gbQbEiTkjpmHAJOYnoSsaPtPrcMSlG4EhJSFDk6ymANHVg==}
 
-  '@visactor/vrender-kits@1.1.0-alpha.9':
-    resolution: {integrity: sha512-dACr5rnEkzncekbfraYqcDK9LVYxcUnMN/73OSwrpeog2dbwFDZ/iBoET724NecpmGmi3WAEE+pINB6/acBgvA==}
+  '@visactor/vrender-kits@1.1.0-alpha.11':
+    resolution: {integrity: sha512-n/XGF5equ/7quxKUTFMlWGKxu6znp/sN8i/2rIaQ/HyZDQBj558LX4qhFLqWeWikHENv96CYFUrVhOfQXGeqQA==}
 
-  '@visactor/vrender@1.1.0-alpha.9':
-    resolution: {integrity: sha512-vaUTjFPwEsZS3iIrYY5VY5bCMa45hWT5JlO1OP9daKMxN+3nU67bRRCAT1vbOTG69RCcYyqarFsTMl3wS8BqEQ==}
+  '@visactor/vrender@1.1.0-alpha.11':
+    resolution: {integrity: sha512-ilKoCLi2Ax4oUmzCbFNmNdmEqcitk7ESlryGAQPUIO1DVJkmu8H2zzO4gUlunEKYkMhCSOj4a1bI8/7jUMO2eQ==}
 
   '@visactor/vscale@0.18.18':
     resolution: {integrity: sha512-iRG4kv+5Fv4KX3AxEfV95XU3I6OmF0QizyAhqHxKa7L1MaT+MRvDDk5zHWf1E8gialLbL2xDe3GnT6g/4u5jhA==}
@@ -15029,9 +15029,9 @@ snapshots:
       '@visactor/vrender-core': 1.0.0-alpha.18
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-animate@1.1.0-alpha.9':
+  '@visactor/vrender-animate@1.1.0-alpha.11':
     dependencies:
-      '@visactor/vrender-core': 1.1.0-alpha.9
+      '@visactor/vrender-core': 1.1.0-alpha.11
       '@visactor/vutils': 1.0.23
 
   '@visactor/vrender-components@1.0.0-alpha.18':
@@ -15042,11 +15042,11 @@ snapshots:
       '@visactor/vscale': 1.0.4
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-components@1.1.0-alpha.9':
+  '@visactor/vrender-components@1.1.0-alpha.11':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.9
-      '@visactor/vrender-core': 1.1.0-alpha.9
-      '@visactor/vrender-kits': 1.1.0-alpha.9
+      '@visactor/vrender-animate': 1.1.0-alpha.11
+      '@visactor/vrender-core': 1.1.0-alpha.11
+      '@visactor/vrender-kits': 1.1.0-alpha.11
       '@visactor/vscale': 1.0.23
       '@visactor/vutils': 1.0.23
 
@@ -15055,7 +15055,7 @@ snapshots:
       '@visactor/vutils': 1.0.4
       color-convert: 2.0.1
 
-  '@visactor/vrender-core@1.1.0-alpha.9':
+  '@visactor/vrender-core@1.1.0-alpha.11':
     dependencies:
       '@visactor/vutils': 1.0.23
       color-convert: 2.0.1
@@ -15069,21 +15069,21 @@ snapshots:
       lottie-web: 5.13.0
       roughjs: 4.5.2
 
-  '@visactor/vrender-kits@1.1.0-alpha.9':
+  '@visactor/vrender-kits@1.1.0-alpha.11':
     dependencies:
       '@resvg/resvg-js': 2.4.1
-      '@visactor/vrender-core': 1.1.0-alpha.9
+      '@visactor/vrender-core': 1.1.0-alpha.11
       '@visactor/vutils': 1.0.23
       gifuct-js: 2.1.2
       lottie-web: 5.13.0
       roughjs: 4.6.6
 
-  '@visactor/vrender@1.1.0-alpha.9':
+  '@visactor/vrender@1.1.0-alpha.11':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.9
-      '@visactor/vrender-components': 1.1.0-alpha.9
-      '@visactor/vrender-core': 1.1.0-alpha.9
-      '@visactor/vrender-kits': 1.1.0-alpha.9
+      '@visactor/vrender-animate': 1.1.0-alpha.11
+      '@visactor/vrender-components': 1.1.0-alpha.11
+      '@visactor/vrender-core': 1.1.0-alpha.11
+      '@visactor/vrender-kits': 1.1.0-alpha.11
 
   '@visactor/vscale@0.18.18':
     dependencies:
diff --git a/docs/package.json b/docs/package.json
index d8e19498e3..9d3b37d8cc 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,8 +19,8 @@
     "@visactor/vchart-theme": "~1.6.6",
     "@visactor/vmind": "1.2.4-alpha.5",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
     "@visactor/vtable": "1.19.0-alpha.0",
     "@visactor/vtable-editors": "1.19.0-alpha.0",
     "@visactor/vtable-gantt": "1.19.0-alpha.0",
diff --git a/packages/openinula-vchart/package.json b/packages/openinula-vchart/package.json
index cf107f14f2..645f23c411 100644
--- a/packages/openinula-vchart/package.json
+++ b/packages/openinula-vchart/package.json
@@ -30,8 +30,8 @@
   "dependencies": {
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender-core": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json
index 9f362934ab..4ef51209bd 100644
--- a/packages/react-vchart/package.json
+++ b/packages/react-vchart/package.json
@@ -36,8 +36,8 @@
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vchart-extension": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender-core": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json
index 994627a02c..b82bf84019 100644
--- a/packages/vchart-extension/package.json
+++ b/packages/vchart-extension/package.json
@@ -26,10 +26,10 @@
     "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
-    "@visactor/vrender-components": "1.1.0-alpha.9",
-    "@visactor/vrender-animate": "1.1.0-alpha.9",
+    "@visactor/vrender-core": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender-components": "1.1.0-alpha.11",
+    "@visactor/vrender-animate": "1.1.0-alpha.11",
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
     "@visactor/vdataset": "~1.0.23",
diff --git a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
index 20f0745176..0c0f4f6b90 100644
--- a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
+++ b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
@@ -185,6 +185,22 @@ const getBarClipPathRects = (chart: VChart) =>
     path => path.baseAttributes ?? path.attribute
   );
 
+const getBarClipPathGraphics = (chart: VChart) => (getBarMarkProduct(chart).attribute.path ?? []) as AnimatedGraphic[];
+
+const clickLegendItem = (chart: VChart, index: number) => {
+  const legendModel = chart.getComponents().find((component: any) => component.type === 'discreteLegend') as any;
+  const legendComponent = legendModel?._legendComponent;
+  const legendItem = legendComponent?._itemsContainer?.getChildren?.()[index];
+
+  if (!legendComponent?._onClick || !legendItem) {
+    throw new Error(`Expected legend item ${index} to exist`);
+  }
+
+  legendComponent._onClick({
+    target: legendItem
+  });
+};
+
 const createStackCornerLegendSpec = () =>
   ({
     type: 'bar',
@@ -647,4 +663,62 @@ describe('manual ticker animation regressions', () => {
       removeDom(container);
     }
   });
+
+  it('keeps stack corner clip paths synced during quick legend reselect update animations', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createStackCornerLegendSpec(), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const allSeries = Object.keys(COLOR_BY_SERIES);
+      const retainedSeries = allSeries.slice(0, 3);
+
+      clickLegendItem(chart, 3);
+      chart.renderSync();
+
+      const hideUpdate = ticker.getTime();
+
+      ticker.tickAt(hideUpdate + UPDATE_DURATION / 10);
+
+      clickLegendItem(chart, 3);
+      chart.renderSync();
+
+      const showUpdate = ticker.getTime();
+
+      ticker.tickAt(showUpdate + UPDATE_DURATION / 15);
+
+      const clipPaths = getBarClipPathGraphics(chart);
+      const isAnimatingBackToAll = retainedSeries.some(seriesName => {
+        const graphic = getVisibleBarByFill(chart, COLOR_BY_SERIES[seriesName]);
+        const finalAttribute = getGraphicFinalAttribute(graphic);
+
+        return graphic.attribute.x !== finalAttribute.x || graphic.attribute.width !== finalAttribute.width;
+      });
+
+      expect(clipPaths.length).toBe(allSeries.length);
+      expect(isAnimatingBackToAll).toBe(true);
+
+      retainedSeries.forEach((seriesName, index) => {
+        const graphic = getVisibleBarByFill(chart, COLOR_BY_SERIES[seriesName]);
+        const clipPath = clipPaths[index];
+
+        expectClose(clipPath.attribute.x, graphic.attribute.x);
+        expectClose(clipPath.attribute.width, graphic.attribute.width);
+        expectClose(clipPath.attribute.y, graphic.attribute.y);
+        expectClose(clipPath.attribute.y1, graphic.attribute.y1);
+      });
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
 });
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index 5667cd6aeb..796c772e43 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,11 +126,11 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.9",
-    "@visactor/vrender-core": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
-    "@visactor/vrender-components": "1.1.0-alpha.9",
-    "@visactor/vrender-animate": "1.1.0-alpha.9",
+    "@visactor/vrender": "1.1.0-alpha.11",
+    "@visactor/vrender-core": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender-components": "1.1.0-alpha.11",
+    "@visactor/vrender-animate": "1.1.0-alpha.11",
     "@visactor/vutils-extension": "workspace:2.0.22"
   },
   "publishConfig": {
diff --git a/tools/story-player/package.json b/tools/story-player/package.json
index 69247dd3b7..14e56dc3f4 100644
--- a/tools/story-player/package.json
+++ b/tools/story-player/package.json
@@ -56,10 +56,10 @@
     "vite": "3.2.6"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.9",
-    "@visactor/vrender-kits": "1.1.0-alpha.9",
+    "@visactor/vrender-core": "1.1.0-alpha.11",
+    "@visactor/vrender-kits": "1.1.0-alpha.11",
     "@visactor/vchart": "workspace:2.0.22",
-    "@visactor/vrender": "1.1.0-alpha.9",
+    "@visactor/vrender": "1.1.0-alpha.11",
     "@visactor/vutils": "~1.0.23"
   }
 }

From eb33d40aa11df13e701c5047bd7ffbb80128e58e Mon Sep 17 00:00:00 2001
From: "lixuefei.1313" <lixuef1313@163.com>
Date: Fri, 8 May 2026 21:38:34 +0800
Subject: [PATCH 8/8] fix: stabilize VChart animations on VRender alpha.15

Keep the alpha.15 dependency bump.

Add focused coverage for the two active update-animation bugs.

Constraint: Manual-ticker expansion still includes unfinished cases.

Rejected: Wait for all drafted cases | user asked to push current work.

Confidence: medium

Scope-risk: moderate

Tested: rush install --check-only

Tested: targeted manual-ticker Jest for two fixed bugs

Tested: update-spec Jest

Tested: touched-file eslint

Not-tested: Full manual-ticker has known unfinished failing cases.
---
 common/config/rush/pnpm-lock.yaml             |  120 +-
 docs/package.json                             |    4 +-
 packages/openinula-vchart/package.json        |    4 +-
 packages/react-vchart/package.json            |    4 +-
 packages/vchart-extension/package.json        |    8 +-
 .../unit/animation/manual-ticker.test.ts      | 1219 ++++++++++++++++-
 .../__tests__/unit/core/update-spec.test.ts   |  290 ++++
 packages/vchart/package.json                  |   10 +-
 packages/vchart/src/chart/base/base-chart.ts  |    7 +-
 packages/vchart/src/component/brush/brush.ts  |    6 +
 packages/vchart/src/mark/base/base-mark.ts    |    9 +-
 packages/vchart/src/series/bar/bar.ts         |   22 +-
 .../vchart/src/series/base/base-series.ts     |   32 +-
 packages/vchart/src/series/base/constant.ts   |    2 +
 .../vchart/src/series/box-plot/box-plot.ts    |    2 +
 tools/story-player/package.json               |    6 +-
 16 files changed, 1639 insertions(+), 106 deletions(-)

diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 5b8a30cce6..354fd7fa69 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -37,11 +37,11 @@ importers:
         specifier: 1.2.4-alpha.5
         version: 1.2.4-alpha.5
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vtable':
         specifier: 1.19.0-alpha.0
         version: 1.19.0-alpha.0
@@ -203,11 +203,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -294,11 +294,11 @@ importers:
         specifier: workspace:2.0.22
         version: link:../vchart-extension
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -529,20 +529,20 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vscale':
         specifier: ~1.0.23
         version: 1.0.23
@@ -695,17 +695,17 @@ importers:
         specifier: ~1.0.23
         version: 1.0.23
       '@visactor/vrender-animate':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-components':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -1263,14 +1263,14 @@ importers:
         specifier: workspace:2.0.22
         version: link:../../packages/vchart
       '@visactor/vrender':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-core':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vrender-kits':
-        specifier: 1.1.0-alpha.11
-        version: 1.1.0-alpha.11
+        specifier: 1.1.0-alpha.15
+        version: 1.1.0-alpha.15
       '@visactor/vutils':
         specifier: ~1.0.23
         version: 1.0.23
@@ -3076,29 +3076,29 @@ packages:
   '@visactor/vrender-animate@1.0.0-alpha.18':
     resolution: {integrity: sha512-9kTtvp1ef+1t+AtUiza6A7qBQP7SmvOu3/ILGrqs/HGdZVj1XGjbYvD/X/zwKJ3LEb7gGV5fa8x95e4czTvRSA==}
 
-  '@visactor/vrender-animate@1.1.0-alpha.11':
-    resolution: {integrity: sha512-RgWD8BYip6QvrkQsXJWvHvboWoyuvHmycxDY4Pq5NKyWxatns5S4n1kwdeViRtBd8bKklVAc/UE4d3BbGv2zJA==}
+  '@visactor/vrender-animate@1.1.0-alpha.15':
+    resolution: {integrity: sha512-P/ZA0IoVYd3YZiBRVogTcMTNe0hbXfWUEsad6zfheeB2uV9x5glNJr3vSWtmPk21dBnP72HY5qCuCoLktCR2aw==}
 
   '@visactor/vrender-components@1.0.0-alpha.18':
     resolution: {integrity: sha512-7Euq+ZfswL74n2pgkaqZSsPxoSa5SPIGyXatN1eUrdzM2Z0kX6U0RcJg01fctvRs4op6WhcecRLqGvnHcBeb9Q==}
 
-  '@visactor/vrender-components@1.1.0-alpha.11':
-    resolution: {integrity: sha512-yGxjh1vYyxiSQoPLxJYpRgLtcQlpT0k+p2IGKvtU7zt6RGFSDSThpSHnAwHcWkFKy6X/6r8P28YmoYl5hhLWSw==}
+  '@visactor/vrender-components@1.1.0-alpha.15':
+    resolution: {integrity: sha512-JyktNqO8LHxf/tqX/Qep3C+7InhpOh1PP34xmi+QikDklwP3OcXmEAvh9QUZDRW3fXHay+kj69BNBBv8EFreEw==}
 
   '@visactor/vrender-core@1.0.0-alpha.18':
     resolution: {integrity: sha512-0ihtNvCyNkOsWPFgRqowHzq0IcQgS2Wl/nPpKbVtxWKveenwlhA+ZKoQvam6VJyBY7jeNe1pROy0mJMDyVAJQw==}
 
-  '@visactor/vrender-core@1.1.0-alpha.11':
-    resolution: {integrity: sha512-BGTtnZAmRkPredqFwLnVxoRbb/OLRbVfAjR3gz8cWjrz6QwL8WJT7QgzQX/GvjZcIio6q01ONPejLWLnVmVWrQ==}
+  '@visactor/vrender-core@1.1.0-alpha.15':
+    resolution: {integrity: sha512-0fEUh01WVIa1pegOykTzShDVV6/Zi6eLlEtVwSetakn0ORpWcWp/35/lQE/UHDuY7en9L5kDmZs1Rx17nnYThA==}
 
   '@visactor/vrender-kits@1.0.0-alpha.18':
     resolution: {integrity: sha512-Tvolkq+4G8qiPFZo0Aj8M//Yr6jR2h8FNkFEyWM9gbQbEiTkjpmHAJOYnoSsaPtPrcMSlG4EhJSFDk6ymANHVg==}
 
-  '@visactor/vrender-kits@1.1.0-alpha.11':
-    resolution: {integrity: sha512-n/XGF5equ/7quxKUTFMlWGKxu6znp/sN8i/2rIaQ/HyZDQBj558LX4qhFLqWeWikHENv96CYFUrVhOfQXGeqQA==}
+  '@visactor/vrender-kits@1.1.0-alpha.15':
+    resolution: {integrity: sha512-ZW1URHOBaccXU6NEFjiN9CY1ZA2njYpowbxtEI63Uoe1wESvqw09ZmrzbFo7/z8EbcSg6VdZlyJq7y0VDnU2Xw==}
 
-  '@visactor/vrender@1.1.0-alpha.11':
-    resolution: {integrity: sha512-ilKoCLi2Ax4oUmzCbFNmNdmEqcitk7ESlryGAQPUIO1DVJkmu8H2zzO4gUlunEKYkMhCSOj4a1bI8/7jUMO2eQ==}
+  '@visactor/vrender@1.1.0-alpha.15':
+    resolution: {integrity: sha512-oJY3sx9gqnXYqs8zf5bJBWfh5RK5gplMg2X5GbpQzE5bs5xSYAqSaoNw3U2Bo3GrZAfHODd1c86dvpQ5JAvx4Q==}
 
   '@visactor/vscale@0.18.18':
     resolution: {integrity: sha512-iRG4kv+5Fv4KX3AxEfV95XU3I6OmF0QizyAhqHxKa7L1MaT+MRvDDk5zHWf1E8gialLbL2xDe3GnT6g/4u5jhA==}
@@ -15029,9 +15029,9 @@ snapshots:
       '@visactor/vrender-core': 1.0.0-alpha.18
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-animate@1.1.0-alpha.11':
+  '@visactor/vrender-animate@1.1.0-alpha.15':
     dependencies:
-      '@visactor/vrender-core': 1.1.0-alpha.11
+      '@visactor/vrender-core': 1.1.0-alpha.15
       '@visactor/vutils': 1.0.23
 
   '@visactor/vrender-components@1.0.0-alpha.18':
@@ -15042,11 +15042,11 @@ snapshots:
       '@visactor/vscale': 1.0.4
       '@visactor/vutils': 1.0.4
 
-  '@visactor/vrender-components@1.1.0-alpha.11':
+  '@visactor/vrender-components@1.1.0-alpha.15':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.11
-      '@visactor/vrender-core': 1.1.0-alpha.11
-      '@visactor/vrender-kits': 1.1.0-alpha.11
+      '@visactor/vrender-animate': 1.1.0-alpha.15
+      '@visactor/vrender-core': 1.1.0-alpha.15
+      '@visactor/vrender-kits': 1.1.0-alpha.15
       '@visactor/vscale': 1.0.23
       '@visactor/vutils': 1.0.23
 
@@ -15055,7 +15055,7 @@ snapshots:
       '@visactor/vutils': 1.0.4
       color-convert: 2.0.1
 
-  '@visactor/vrender-core@1.1.0-alpha.11':
+  '@visactor/vrender-core@1.1.0-alpha.15':
     dependencies:
       '@visactor/vutils': 1.0.23
       color-convert: 2.0.1
@@ -15069,21 +15069,21 @@ snapshots:
       lottie-web: 5.13.0
       roughjs: 4.5.2
 
-  '@visactor/vrender-kits@1.1.0-alpha.11':
+  '@visactor/vrender-kits@1.1.0-alpha.15':
     dependencies:
       '@resvg/resvg-js': 2.4.1
-      '@visactor/vrender-core': 1.1.0-alpha.11
+      '@visactor/vrender-core': 1.1.0-alpha.15
       '@visactor/vutils': 1.0.23
       gifuct-js: 2.1.2
       lottie-web: 5.13.0
       roughjs: 4.6.6
 
-  '@visactor/vrender@1.1.0-alpha.11':
+  '@visactor/vrender@1.1.0-alpha.15':
     dependencies:
-      '@visactor/vrender-animate': 1.1.0-alpha.11
-      '@visactor/vrender-components': 1.1.0-alpha.11
-      '@visactor/vrender-core': 1.1.0-alpha.11
-      '@visactor/vrender-kits': 1.1.0-alpha.11
+      '@visactor/vrender-animate': 1.1.0-alpha.15
+      '@visactor/vrender-components': 1.1.0-alpha.15
+      '@visactor/vrender-core': 1.1.0-alpha.15
+      '@visactor/vrender-kits': 1.1.0-alpha.15
 
   '@visactor/vscale@0.18.18':
     dependencies:
diff --git a/docs/package.json b/docs/package.json
index 9d3b37d8cc..00fc1e26bd 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,8 +19,8 @@
     "@visactor/vchart-theme": "~1.6.6",
     "@visactor/vmind": "1.2.4-alpha.5",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
     "@visactor/vtable": "1.19.0-alpha.0",
     "@visactor/vtable-editors": "1.19.0-alpha.0",
     "@visactor/vtable-gantt": "1.19.0-alpha.0",
diff --git a/packages/openinula-vchart/package.json b/packages/openinula-vchart/package.json
index 645f23c411..75365ac5e4 100644
--- a/packages/openinula-vchart/package.json
+++ b/packages/openinula-vchart/package.json
@@ -30,8 +30,8 @@
   "dependencies": {
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender-core": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json
index 4ef51209bd..70accd925c 100644
--- a/packages/react-vchart/package.json
+++ b/packages/react-vchart/package.json
@@ -36,8 +36,8 @@
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vchart-extension": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
-    "@visactor/vrender-core": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender-core": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
     "react-is": "^18.2.0"
   },
   "devDependencies": {
diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json
index b82bf84019..27247bcba2 100644
--- a/packages/vchart-extension/package.json
+++ b/packages/vchart-extension/package.json
@@ -26,10 +26,10 @@
     "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
-    "@visactor/vrender-components": "1.1.0-alpha.11",
-    "@visactor/vrender-animate": "1.1.0-alpha.11",
+    "@visactor/vrender-core": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
+    "@visactor/vrender-components": "1.1.0-alpha.15",
+    "@visactor/vrender-animate": "1.1.0-alpha.15",
     "@visactor/vchart": "workspace:2.0.22",
     "@visactor/vutils": "~1.0.23",
     "@visactor/vdataset": "~1.0.23",
diff --git a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
index 0c0f4f6b90..d400d92b39 100644
--- a/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
+++ b/packages/vchart/__tests__/unit/animation/manual-ticker.test.ts
@@ -1,13 +1,17 @@
 import VChart, {
   ManualTicker,
+  registerStateTransition,
   type IBarChartSpec,
   type IChart,
+  type ILineChartSpec,
   type IMark,
   type IMarkGraphic,
   type ISeries
 } from '../../../src';
 import { createDiv, removeDom } from '../../util/dom';
 
+registerStateTransition();
+
 type AnimatedGraphic = IMarkGraphic & {
   attribute: Record<string, any>;
   baseAttributes?: Record<string, any>;
@@ -24,6 +28,8 @@ type TraversableGraphic = AnimatedGraphic & {
 
 const APPEAR_DURATION = 300;
 const UPDATE_DURATION = 300;
+const MARKER_DURATION = 800;
+const MARKER_EXIT_DURATION = 1800;
 const COLOR_BY_SERIES: Record<string, string> = {
   'east-利润profit': '#8D72F6',
   'east-销售量sales': '#5766EC',
@@ -87,6 +93,44 @@ const getBarGraphics = (chart: VChart) => {
   return barMark.getGraphics() as AnimatedGraphic[];
 };
 
+const getLineGraphics = (chart: VChart) => {
+  const model = chart.getChart() as IChart;
+  const lineSeries = model.getAllSeries().find((series: ISeries) => series.type === 'line');
+
+  expect(lineSeries).toBeDefined();
+  if (!lineSeries) {
+    throw new Error('Expected line series to exist');
+  }
+
+  const lineMark = lineSeries.getMarks().find((mark: IMark) => mark.name === 'line');
+
+  expect(lineMark).toBeDefined();
+  if (!lineMark) {
+    throw new Error('Expected line mark to exist');
+  }
+
+  return lineMark.getGraphics() as AnimatedGraphic[];
+};
+
+const getPointGraphics = (chart: VChart) => {
+  const model = chart.getChart() as IChart;
+  const lineSeries = model.getAllSeries().find((series: ISeries) => series.type === 'line');
+
+  expect(lineSeries).toBeDefined();
+  if (!lineSeries) {
+    throw new Error('Expected line series to exist');
+  }
+
+  const pointMark = lineSeries.getMarks().find((mark: IMark) => mark.name === 'point');
+
+  expect(pointMark).toBeDefined();
+  if (!pointMark) {
+    throw new Error('Expected point mark to exist');
+  }
+
+  return pointMark.getGraphics() as AnimatedGraphic[];
+};
+
 const walkGraphics = (root: TraversableGraphic, visitor: (graphic: TraversableGraphic) => void) => {
   visitor(root);
   root.forEachChildren?.((child: TraversableGraphic) => {
@@ -130,6 +174,36 @@ const getVisibleBarByFill = (chart: VChart, fill: string) => {
   return bar;
 };
 
+const getBarGraphicByKey = (chart: VChart, key: string) => {
+  const bar = getBarGraphics(chart).find(graphic => graphic.context?.key === key);
+
+  expect(bar).toBeDefined();
+  if (!bar) {
+    throw new Error(`Expected bar with key ${key} to exist`);
+  }
+
+  return bar;
+};
+
+const getGraphicDatum = (graphic: AnimatedGraphic) => {
+  const data = graphic.context?.data;
+
+  return Array.isArray(data) ? data[0] : data;
+};
+
+const getStateDatum = (datum: any) => (Array.isArray(datum) ? datum[0] : datum);
+
+const getBarGraphicByDatum = (chart: VChart, predicate: (datum: any) => boolean) => {
+  const bar = getBarGraphics(chart).find(graphic => predicate(getGraphicDatum(graphic)));
+
+  expect(bar).toBeDefined();
+  if (!bar) {
+    throw new Error('Expected bar with matching datum to exist');
+  }
+
+  return bar;
+};
+
 const getBarCenterX = (graphic: AnimatedGraphic) => {
   const attrs = getGraphicFinalAttribute(graphic);
 
@@ -159,6 +233,28 @@ const expectStaticRectLayout = (
   expectClose(getGraphicFinalAttribute(graphic).y1, expected.y1);
 };
 
+const simplifyPoints = (points: any[] = []) =>
+  points.map(point => ({
+    x: point.x,
+    y: point.y,
+    context: point.context
+  }));
+
+const expectPointsLayout = (actual: any[], expected: ReturnType<typeof simplifyPoints>) => {
+  expect(actual.length).toBe(expected.length);
+  actual.forEach((point, index) => {
+    expectClose(point.x, expected[index].x);
+    expectClose(point.y, expected[index].y);
+    expect(point.context).toBe(expected[index].context);
+  });
+};
+
+const expectLinePointsLayout = (graphic: AnimatedGraphic, expected: ReturnType<typeof simplifyPoints>) => {
+  expectPointsLayout(graphic.attribute.points, expected);
+  expectPointsLayout(graphic.baseAttributes?.points, expected);
+  expectPointsLayout(getGraphicFinalAttribute(graphic).points, expected);
+};
+
 const getBarMarkProduct = (chart: VChart) => {
   const model = chart.getChart() as IChart;
   const barSeries = model.getAllSeries().find((series: ISeries) => series.type === 'bar');
@@ -201,6 +297,532 @@ const clickLegendItem = (chart: VChart, index: number) => {
   });
 };
 
+const getMarkerGraphic = (chart: VChart, componentType: 'markPoint' | 'markLine' | 'markArea') => {
+  const component = (chart.getChart() as any)?.getComponentsByKey(componentType)?.[0];
+  const graphic = component?.getVRenderComponents?.()[0] as TraversableGraphic | undefined;
+
+  expect(graphic).toBeDefined();
+  if (!graphic) {
+    throw new Error(`Expected ${componentType} component graphic to exist`);
+  }
+
+  return graphic;
+};
+
+const collectGraphics = (root: TraversableGraphic) => {
+  const graphics: TraversableGraphic[] = [];
+
+  walkGraphics(root, graphic => {
+    graphics.push(graphic);
+  });
+
+  return graphics;
+};
+
+const isGraphicAttached = (graphic: TraversableGraphic) => Boolean(graphic.parent || (graphic as any).stage);
+
+const isIntermediateOpacity = (value: unknown) => typeof value === 'number' && value > 0 && value < 1;
+
+const hasIntermediateFade = (graphic: TraversableGraphic) =>
+  isIntermediateOpacity(graphic.attribute?.fillOpacity) || isIntermediateOpacity(graphic.attribute?.strokeOpacity);
+
+const expectMarkerExitFade = (
+  chart: VChart,
+  ticker: ManualTicker,
+  markerGraphic: TraversableGraphic,
+  removeMarkerSpec: () => any
+) => {
+  const trackedGraphics = collectGraphics(markerGraphic).filter(graphic => graphic !== markerGraphic);
+
+  expect(trackedGraphics.length).toBeGreaterThan(0);
+
+  chart.updateSpecSync(removeMarkerSpec());
+
+  const exitStart = ticker.getTime();
+
+  ticker.tickAt(exitStart + MARKER_EXIT_DURATION / 2);
+
+  expect(trackedGraphics.some(isGraphicAttached)).toBe(true);
+  expect(trackedGraphics.some(graphic => isGraphicAttached(graphic) && hasIntermediateFade(graphic))).toBe(true);
+
+  ticker.tickAt(exitStart + MARKER_EXIT_DURATION + 50);
+
+  expect(trackedGraphics.some(isGraphicAttached)).toBe(false);
+};
+
+const createDocBarDatum = (country: string, value: number, measure = 'share_global_co2') => ({
+  country,
+  __OriginalData__: {
+    country,
+    [measure]: value
+  },
+  [measure]: value,
+  __MeaId__: measure,
+  __MeaName__: measure,
+  __MeaValue__: value,
+  __Dim_Y__: country,
+  __Dim_Color__: measure,
+  __Dim_Detail__: measure,
+  __Dim_ColorId__: measure
+});
+
+const createSameSeriesDocBarSpec = (values: Array<{ country: string; value: number }>) =>
+  ({
+    type: 'bar',
+    direction: 'horizontal',
+    width: 500,
+    height: 300,
+    yField: ['__Dim_Y__'],
+    xField: '__MeaValue__',
+    seriesField: '__Dim_ColorId__',
+    padding: 0,
+    region: [
+      {
+        clip: true
+      }
+    ],
+    animation: true,
+    animationAppear: {
+      duration: APPEAR_DURATION,
+      easing: 'linear'
+    },
+    animationUpdate: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    color: {
+      type: 'ordinal',
+      domain: ['share_global_co2'],
+      range: ['#F0A868']
+    },
+    background: 'transparent',
+    data: {
+      id: 'barParallel',
+      values: values.map(item => createDocBarDatum(item.country, item.value))
+    },
+    large: false,
+    label: {
+      visible: true,
+      animationUpdate: {
+        duration: UPDATE_DURATION,
+        easing: 'linear'
+      }
+    },
+    axes: [
+      { orient: 'bottom', visible: false },
+      { orient: 'left', visible: false }
+    ]
+  } as unknown as IBarChartSpec);
+
+const createSeriesChangeDocBarSpec = (mode: 'grouped' | 'single') => {
+  const countries = mode === 'grouped' ? ['United States', 'China', 'India'] : ['China', 'United States', 'India'];
+  const values = countries.flatMap((country, countryIndex) => {
+    const base = 30 - countryIndex * 5;
+
+    if (mode === 'single') {
+      return [createDocBarDatum(country, base, 'share_global_co2')];
+    }
+
+    return [
+      createDocBarDatum(country, base, 'share_global_co2'),
+      createDocBarDatum(country, base + 10, 'share_global_cumulative_co2')
+    ];
+  });
+
+  return {
+    ...createSameSeriesDocBarSpec([]),
+    yField: mode === 'grouped' ? ['__Dim_Y__', '__Dim_Detail__'] : ['__Dim_Y__'],
+    color: {
+      type: 'ordinal',
+      domain: ['share_global_co2', 'share_global_cumulative_co2'],
+      range: ['#F0A868', '#7BAA9A']
+    },
+    data: {
+      id: 'barParallel',
+      values
+    }
+  } as unknown as IBarChartSpec;
+};
+
+const createLineGrowthSpec = (values: Array<{ time: string; value: number }>) =>
+  ({
+    type: 'line',
+    width: 500,
+    height: 300,
+    xField: 'time',
+    yField: 'value',
+    padding: 0,
+    animation: true,
+    animationAppear: {
+      duration: APPEAR_DURATION,
+      easing: 'linear'
+    },
+    animationUpdate: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    data: {
+      values
+    },
+    point: {
+      visible: false
+    },
+    axes: [
+      { orient: 'left', visible: false },
+      { orient: 'bottom', visible: false }
+    ]
+  } as ILineChartSpec);
+
+const cosmeticLineValues = [
+  { type: 'Nail polish', country: 'Africa', value: 4229 },
+  { type: 'Nail polish', country: 'EU', value: 4376 },
+  { type: 'Nail polish', country: 'China', value: 3054 },
+  { type: 'Nail polish', country: 'USA', value: 12814 },
+  { type: 'Eyebrow pencil', country: 'Africa', value: 3932 },
+  { type: 'Eyebrow pencil', country: 'EU', value: 3987 },
+  { type: 'Eyebrow pencil', country: 'China', value: 5067 },
+  { type: 'Eyebrow pencil', country: 'USA', value: 13012 },
+  { type: 'Rouge', country: 'Africa', value: 5221 },
+  { type: 'Rouge', country: 'EU', value: 3574 },
+  { type: 'Rouge', country: 'China', value: 7004 },
+  { type: 'Rouge', country: 'USA', value: 11624 },
+  { type: 'Lipstick', country: 'Africa', value: 9256 },
+  { type: 'Lipstick', country: 'EU', value: 4376 },
+  { type: 'Lipstick', country: 'China', value: 9054 },
+  { type: 'Lipstick', country: 'USA', value: 8814 },
+  { type: 'Eyeshadows', country: 'Africa', value: 3308 },
+  { type: 'Eyeshadows', country: 'EU', value: 4572 },
+  { type: 'Eyeshadows', country: 'China', value: 12043 },
+  { type: 'Eyeshadows', country: 'USA', value: 12998 },
+  { type: 'Eyeliner', country: 'Africa', value: 5432 },
+  { type: 'Eyeliner', country: 'EU', value: 3417 },
+  { type: 'Eyeliner', country: 'China', value: 15067 },
+  { type: 'Eyeliner', country: 'USA', value: 12321 },
+  { type: 'Foundation', country: 'Africa', value: 13701 },
+  { type: 'Foundation', country: 'EU', value: 5231 },
+  { type: 'Foundation', country: 'China', value: 10119 },
+  { type: 'Foundation', country: 'USA', value: 10342 },
+  { type: 'Lip gloss', country: 'Africa', value: 4008 },
+  { type: 'Lip gloss', country: 'EU', value: 4572 },
+  { type: 'Lip gloss', country: 'China', value: 12043 },
+  { type: 'Lip gloss', country: 'USA', value: 22998 },
+  { type: 'Mascara', country: 'Africa', value: 18712 },
+  { type: 'Mascara', country: 'EU', value: 6134 },
+  { type: 'Mascara', country: 'China', value: 10419 },
+  { type: 'Mascara', country: 'USA', value: 11261 }
+];
+
+const createCosmeticPercentLineSpec = () =>
+  ({
+    type: 'line',
+    width: 500,
+    height: 300,
+    data: {
+      values: cosmeticLineValues
+    },
+    percent: true,
+    xField: 'type',
+    yField: 'value',
+    seriesField: 'country',
+    animation: true,
+    animationAppear: {
+      duration: APPEAR_DURATION,
+      easing: 'linear'
+    },
+    animationUpdate: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    legends: [{ visible: true, position: 'middle', orient: 'bottom' }],
+    axes: [
+      {
+        orient: 'left',
+        label: {
+          formatMethod: (val: string | string[]) => `${(+val * 100).toFixed(2)}%`
+        }
+      }
+    ]
+  } as ILineChartSpec);
+
+const createStateSwitchSpec = (valueOffset = 0) =>
+  ({
+    type: 'bar',
+    width: 400,
+    height: 300,
+    data: {
+      values: [
+        { date: '2019', value: 20 + valueOffset },
+        { date: '2020', value: 24 + valueOffset },
+        { date: '2021', value: 28 + valueOffset }
+      ]
+    },
+    xField: 'date',
+    yField: 'value',
+    animation: true,
+    animationAppear: {
+      duration: APPEAR_DURATION,
+      easing: 'linear'
+    },
+    animationUpdate: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    animationState: {
+      duration: UPDATE_DURATION,
+      easing: 'linear'
+    },
+    bar: {
+      state: {
+        custom1: {
+          fillOpacity: 0.2
+        }
+      }
+    },
+    axes: [
+      { orient: 'left', visible: false },
+      { orient: 'bottom', visible: false }
+    ]
+  } as unknown as IBarChartSpec);
+
+const createMarkerLineData = () => [
+  { year: '2019', value: 10 },
+  { year: '2020', value: 18 },
+  { year: '2021', value: 14 },
+  { year: '2022', value: 22 }
+];
+
+const createMarkPointExitSpec = (visible = true) =>
+  ({
+    type: 'line',
+    width: 500,
+    height: 300,
+    xField: 'year',
+    yField: 'value',
+    animation: true,
+    data: {
+      values: createMarkerLineData()
+    },
+    point: {
+      visible: false
+    },
+    markPoint: visible
+      ? [
+          {
+            animation: {
+              type: 'callIn',
+              duration: MARKER_DURATION,
+              easing: 'linear'
+            },
+            animationExit: {
+              type: 'fadeOut',
+              duration: MARKER_EXIT_DURATION,
+              easing: 'linear'
+            },
+            coordinate: {
+              year: '2022',
+              value: 22
+            },
+            itemContent: {
+              type: 'text',
+              text: {
+                text: '2022',
+                style: {
+                  fill: '#E8346D'
+                }
+              },
+              offsetY: 40
+            },
+            itemLine: {
+              type: 'type-do',
+              line: {
+                style: {
+                  stroke: '#E8346D'
+                }
+              }
+            }
+          }
+        ]
+      : []
+  } as any);
+
+const createDifferenceMarkLineSpec = (visible = true) =>
+  ({
+    type: 'bar',
+    width: 500,
+    height: 300,
+    data: [
+      {
+        id: 'barData',
+        values: [
+          { type: 'Autocracies', year: '1930', value: 129 },
+          { type: 'Autocracies', year: '2000', value: 89 },
+          { type: 'Democracies', year: '1930', value: 22 },
+          { type: 'Democracies', year: '2000', value: 87 }
+        ]
+      }
+    ],
+    xField: ['year', 'type'],
+    yField: 'value',
+    seriesField: 'type',
+    animation: true,
+    markLine: visible
+      ? [
+          {
+            type: 'type-step',
+            connectDirection: 'top',
+            expandDistance: 30,
+            coordinates: [
+              { type: 'Autocracies', year: '1930', value: 129 },
+              { type: 'Autocracies', year: '2000', value: 89 }
+            ],
+            coordinatesOffset: [
+              { x: 0, y: 0 },
+              { x: -5, y: 0 }
+            ],
+            line: {
+              style: {
+                lineDash: [0],
+                lineWidth: 2,
+                stroke: '#000',
+                cornerRadius: 4
+              }
+            },
+            label: {
+              text: '-45%',
+              position: 'middle',
+              style: {
+                fill: '#000'
+              }
+            },
+            endSymbol: {
+              size: 12,
+              refX: -4
+            },
+            animation: {
+              type: 'clipIn',
+              duration: MARKER_DURATION,
+              easing: 'linear'
+            },
+            animationExit: {
+              type: 'fadeOut',
+              duration: MARKER_EXIT_DURATION,
+              easing: 'linear'
+            }
+          }
+        ]
+      : []
+  } as any);
+
+const createMarkAreaExitSpec = (visible = true) =>
+  ({
+    type: 'line',
+    width: 500,
+    height: 300,
+    xField: 'date',
+    yField: 'price',
+    animation: true,
+    data: {
+      values: [
+        { date: 'Jan-20', price: 0.134 },
+        { date: 'Feb-20', price: 0.134 },
+        { date: 'Mar-20', price: 0.136 },
+        { date: 'Mar-23', price: 0.166 }
+      ]
+    },
+    point: {
+      visible: false
+    },
+    axes: [
+      { orient: 'left', min: 0.12, max: 0.18, visible: false },
+      { orient: 'bottom', visible: false }
+    ],
+    markArea: visible
+      ? [
+          {
+            coordinates: [
+              { date: 'Jan-20', price: 0.18 },
+              { date: 'Mar-23', price: 0.18 },
+              { date: 'Mar-23', price: 0.12 },
+              { date: 'Jan-20', price: 0.12 }
+            ],
+            animation: {
+              type: 'fadeIn',
+              duration: MARKER_DURATION,
+              easing: 'linear'
+            },
+            animationExit: {
+              type: 'fadeOut',
+              duration: MARKER_EXIT_DURATION,
+              easing: 'linear'
+            },
+            label: {
+              text: 'Electricite prices have surged since 2020',
+              position: 'insideTop'
+            }
+          }
+        ]
+      : []
+  } as any);
+
+const createRegularMarkLineExitSpec = (visible = true) =>
+  ({
+    type: 'scatter',
+    width: 500,
+    height: 300,
+    padding: [12, 20, 12, 12],
+    xField: 'x',
+    yField: 'y',
+    sizeField: 'z',
+    size: {
+      type: 'linear',
+      range: [20, 80]
+    },
+    animation: true,
+    data: {
+      id: 'data',
+      values: [
+        { x: 95, y: 95, z: 13.8, name: 'BE', country: 'Belgium' },
+        { x: 65.5, y: 126.4, z: 35.3, name: 'US', country: 'United States' },
+        { x: 63.4, y: 51.8, z: 15.4, name: 'PT', country: 'Portugal' }
+      ]
+    },
+    axes: [
+      { orient: 'bottom', type: 'linear', min: 60, max: 95, visible: false },
+      { orient: 'left', type: 'linear', min: 0, max: 200, visible: false }
+    ],
+    markLine: visible
+      ? [
+          {
+            x: 65,
+            label: {
+              visible: true,
+              position: 'end',
+              text: 'Safe fat intake 65g/day',
+              labelBackground: {
+                visible: false
+              }
+            },
+            line: {
+              style: {
+                stroke: '#000',
+                lineDash: [0]
+              }
+            },
+            animation: {
+              type: 'clipIn',
+              duration: MARKER_DURATION,
+              easing: 'linear'
+            },
+            animationExit: {
+              type: 'fadeOut',
+              duration: MARKER_EXIT_DURATION,
+              easing: 'linear'
+            }
+          }
+        ]
+      : []
+  } as any);
+
 const createStackCornerLegendSpec = () =>
   ({
     type: 'bar',
@@ -394,22 +1016,200 @@ describe('manual ticker animation regressions', () => {
     }
   });
 
-  it('keeps data label update final x at the filtered bar position', () => {
+  it('keeps horizontal bar sorted update final y at the reordered axis positions', () => {
     const { container, dom } = createChartContainer();
     const ticker = createManualTicker();
-    const chart = new VChart(
-      {
+    const createSpec = (values: Array<{ type: string; value: number }>) =>
+      ({
         type: 'bar',
-        direction: 'vertical',
+        direction: 'horizontal',
         width: 500,
         height: 300,
-        xField: ['date', '__DimGroup__'],
-        yField: '__MeaValue__',
-        seriesField: '__DimGroupID__',
         padding: 0,
-        region: [
+        data: [
           {
-            clip: true
+            values
+          }
+        ],
+        yField: 'type',
+        xField: 'value',
+        tooltip: false,
+        animation: true,
+        animationAppear: {
+          duration: APPEAR_DURATION,
+          easing: 'linear'
+        },
+        animationUpdate: {
+          duration: UPDATE_DURATION,
+          easing: 'linear'
+        }
+      } as unknown as IBarChartSpec);
+    const chart = new VChart(
+      createSpec([
+        { type: '1', value: 20 },
+        { type: '2', value: 30 },
+        { type: '3', value: 10 }
+      ]),
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const bar1Before = getBarGraphicByKey(chart, '1');
+      const bar2Before = getBarGraphicByKey(chart, '2');
+      const y1Before = getGraphicFinalAttribute(bar1Before).y;
+      const y2Before = getGraphicFinalAttribute(bar2Before).y;
+
+      chart.updateSpecSync(
+        createSpec([
+          { type: '2', value: 30 },
+          { type: '1', value: 20 },
+          { type: '3', value: 10 }
+        ])
+      );
+
+      const updateStart = ticker.getTime();
+      const bar1After = getBarGraphicByKey(chart, '1');
+      const bar2After = getBarGraphicByKey(chart, '2');
+      const y1After = getGraphicFinalAttribute(bar1After).y;
+      const y2After = getGraphicFinalAttribute(bar2After).y;
+
+      expectClose(y1After, y2Before);
+      expectClose(y2After, y1Before);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      expect(bar1After.attribute.y).toBeGreaterThan(Math.min(y1Before, y1After));
+      expect(bar1After.attribute.y).toBeLessThan(Math.max(y1Before, y1After));
+      expect(bar2After.attribute.y).toBeGreaterThan(Math.min(y2Before, y2After));
+      expect(bar2After.attribute.y).toBeLessThan(Math.max(y2Before, y2After));
+      expectClose(getGraphicFinalAttribute(bar1After).y, y1After);
+      expectClose(getGraphicFinalAttribute(bar2After).y, y2After);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      expectClose(bar1After.attribute.y, y1After);
+      expectClose(bar2After.attribute.y, y2After);
+      expectClose(bar1After.baseAttributes?.y, y1After);
+      expectClose(bar2After.baseAttributes?.y, y2After);
+      expectClose(getGraphicFinalAttribute(bar1After).y, y1After);
+      expectClose(getGraphicFinalAttribute(bar2After).y, y2After);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps line resize update final points at the resized layout', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      {
+        type: 'line',
+        direction: 'vertical',
+        width: 500,
+        height: 300,
+        xField: ['date'],
+        yField: '__MeaValue__',
+        padding: 0,
+        animation: true,
+        animationAppear: {
+          duration: APPEAR_DURATION,
+          easing: 'linear'
+        },
+        animationUpdate: {
+          duration: UPDATE_DURATION,
+          easing: 'linear'
+        },
+        background: 'transparent',
+        data: {
+          values: [
+            {
+              date: '2019',
+              __MeaValue__: 10
+            },
+            {
+              date: '2020',
+              __MeaValue__: 12
+            },
+            {
+              date: '2021',
+              __MeaValue__: 14
+            },
+            {
+              date: '2022',
+              __MeaValue__: 16
+            }
+          ]
+        }
+      } as ILineChartSpec,
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const lineGraphic = getLineGraphics(chart)[0];
+      const beforeResizePoints = simplifyPoints(getGraphicFinalAttribute(lineGraphic).points);
+
+      chart.resizeSync(700, 300);
+
+      const updateStart = ticker.getTime();
+      const resizedPoints = simplifyPoints(getGraphicFinalAttribute(lineGraphic).points);
+
+      expect(resizedPoints.some((point, index) => point.x !== beforeResizePoints[index].x)).toBe(true);
+      expectPointsLayout(getGraphicFinalAttribute(lineGraphic).points, resizedPoints);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      lineGraphic.attribute.points.forEach((point: any, index: number) => {
+        expect(point.x).toBeGreaterThanOrEqual(Math.min(beforeResizePoints[index].x, resizedPoints[index].x));
+        expect(point.x).toBeLessThanOrEqual(Math.max(beforeResizePoints[index].x, resizedPoints[index].x));
+        expectClose(point.y, resizedPoints[index].y);
+        expect(point.context).toBe(resizedPoints[index].context);
+      });
+      expectPointsLayout(getGraphicFinalAttribute(lineGraphic).points, resizedPoints);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      expectLinePointsLayout(lineGraphic, resizedPoints);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps data label update final x at the filtered bar position', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      {
+        type: 'bar',
+        direction: 'vertical',
+        width: 500,
+        height: 300,
+        xField: ['date', '__DimGroup__'],
+        yField: '__MeaValue__',
+        seriesField: '__DimGroupID__',
+        padding: 0,
+        region: [
+          {
+            clip: true
           }
         ],
         animation: true,
@@ -721,4 +1521,405 @@ describe('manual ticker animation regressions', () => {
       removeDom(container);
     }
   });
+
+  it('keeps same-series horizontal bar updates animating with label enabled', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      createSameSeriesDocBarSpec([
+        { country: 'United States', value: 12.911 },
+        { country: 'China', value: 31.953 },
+        { country: 'India', value: 8.04 },
+        { country: 'Russia', value: 4.55 }
+      ]),
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const chinaBefore = getBarGraphicByDatum(chart, datum => datum?.__Dim_Y__ === 'China');
+      const yBefore = getGraphicFinalAttribute(chinaBefore).y;
+
+      chart.updateSpecSync(
+        createSameSeriesDocBarSpec([
+          { country: 'China', value: 31.953 },
+          { country: 'United States', value: 12.911 },
+          { country: 'India', value: 8.04 },
+          { country: 'Russia', value: 4.55 },
+          { country: 'Brazil', value: 1.271 }
+        ])
+      );
+
+      const updateStart = ticker.getTime();
+      const chinaAfter = getBarGraphicByDatum(chart, datum => datum?.__Dim_Y__ === 'China');
+      const yAfter = getGraphicFinalAttribute(chinaAfter).y;
+
+      expect(yAfter).not.toBe(yBefore);
+      expectClose(getGraphicFinalAttribute(chinaAfter).y, yAfter);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      expect(chinaAfter.attribute.y).toBeGreaterThan(Math.min(yBefore, yAfter));
+      expect(chinaAfter.attribute.y).toBeLessThan(Math.max(yBefore, yAfter));
+      expectClose(getGraphicFinalAttribute(chinaAfter).y, yAfter);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      expectClose(chinaAfter.attribute.y, yAfter);
+      expectClose(chinaAfter.baseAttributes?.y, yAfter);
+      expectClose(getGraphicFinalAttribute(chinaAfter).y, yAfter);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('grows new line data points through update animation instead of placing them directly at final points', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(
+      createLineGrowthSpec([
+        { time: '2019', value: 10 },
+        { time: '2020', value: 12 },
+        { time: '2021', value: 14 }
+      ]),
+      {
+        dom,
+        ticker,
+        animation: true
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const lineGraphic = getLineGraphics(chart)[0];
+      const beforePoints = simplifyPoints(getGraphicFinalAttribute(lineGraphic).points);
+      const beforeTail = beforePoints[beforePoints.length - 1];
+
+      chart.updateSpecSync(
+        createLineGrowthSpec([
+          { time: '2019', value: 10 },
+          { time: '2020', value: 12 },
+          { time: '2021', value: 14 },
+          { time: '2022', value: 16 },
+          { time: '2023', value: 18 }
+        ])
+      );
+
+      const updateStart = ticker.getTime();
+      const finalPoints = simplifyPoints(getGraphicFinalAttribute(lineGraphic).points);
+
+      expect(finalPoints.length).toBe(5);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      const midPoints = simplifyPoints(lineGraphic.attribute.points);
+      const newMidPoints = midPoints.slice(beforePoints.length);
+      const newFinalPoints = finalPoints.slice(beforePoints.length);
+
+      expect(newMidPoints.length).toBe(newFinalPoints.length);
+      expect(
+        newMidPoints.some((point, index) => point.x !== newFinalPoints[index].x || point.y !== newFinalPoints[index].y)
+      ).toBe(true);
+      newMidPoints.forEach((point, index) => {
+        const finalPoint = newFinalPoints[index];
+
+        expect(point.x).toBeGreaterThanOrEqual(Math.min(beforeTail.x, finalPoint.x));
+        expect(point.x).toBeLessThanOrEqual(Math.max(beforeTail.x, finalPoint.x));
+        expect(point.y).toBeGreaterThanOrEqual(Math.min(beforeTail.y, finalPoint.y));
+        expect(point.y).toBeLessThanOrEqual(Math.max(beforeTail.y, finalPoint.y));
+      });
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      expectLinePointsLayout(lineGraphic, finalPoints);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps reselected percent line graphics visible after interrupted legend toggles', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createCosmeticPercentLineSpec(), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const allSeries = ['Africa', 'EU', 'China', 'USA'];
+      const retainedSeries = allSeries.filter(value => value !== 'USA');
+      const selectLegendData = (selectedData: string[]) => {
+        chart.setLegendSelectedDataByIndex(0, selectedData);
+        chart.renderSync();
+      };
+      const getLineGraphicByCountry = (country: string) => {
+        const lineGraphic = getLineGraphics(chart).find((graphic: AnimatedGraphic) => {
+          const datum = getGraphicDatum(graphic);
+
+          return datum?.country === country;
+        });
+
+        expect(lineGraphic).toBeDefined();
+        if (!lineGraphic) {
+          throw new Error(`Expected line graphic for ${country}`);
+        }
+        return lineGraphic;
+      };
+      const getPointGraphicByDatum = (country: string, type: string) => {
+        const pointGraphic = getPointGraphics(chart).find((graphic: AnimatedGraphic) => {
+          const datum = getGraphicDatum(graphic);
+
+          return datum?.country === country && datum?.type === type;
+        });
+
+        expect(pointGraphic).toBeDefined();
+        if (!pointGraphic) {
+          throw new Error(`Expected point graphic for ${country}/${type}`);
+        }
+        return pointGraphic;
+      };
+
+      selectLegendData(retainedSeries);
+      ticker.tickAt(ticker.getTime() + UPDATE_DURATION / 4);
+
+      selectLegendData(allSeries);
+      ticker.tickAt(ticker.getTime() + UPDATE_DURATION / 4);
+
+      selectLegendData(retainedSeries);
+      ticker.tickAt(ticker.getTime() + UPDATE_DURATION / 4);
+
+      const chinaPointBeforeReselect = getPointGraphicByDatum('China', 'Nail polish');
+      const chinaYBeforeReselect = chinaPointBeforeReselect.attribute.y;
+
+      selectLegendData(allSeries);
+
+      const reselectStart = ticker.getTime();
+      const chinaPointAfterReselect = getPointGraphicByDatum('China', 'Nail polish');
+      const chinaFinalY = getGraphicFinalAttribute(chinaPointAfterReselect).y;
+      const usaLine = getLineGraphicByCountry('USA');
+      const usaPoint = getPointGraphicByDatum('USA', 'Nail polish');
+
+      expectClose(chinaPointAfterReselect.attribute.y, chinaYBeforeReselect);
+      expect(chinaFinalY).not.toBe(chinaYBeforeReselect);
+      expect(getGraphicFinalAttribute(usaLine).points.length).toBe(9);
+      expect(getGraphicFinalAttribute(usaPoint).x).toBeDefined();
+      expect(getGraphicFinalAttribute(usaPoint).y).toBeDefined();
+
+      ticker.tickAt(reselectStart + UPDATE_DURATION / 4);
+
+      expect(usaLine.attribute.opacity).toBeGreaterThan(0);
+      expect(usaLine.attribute.opacity).toBeLessThan(1);
+
+      ticker.tickAt(reselectStart + UPDATE_DURATION + 50);
+
+      const finalUsaLine = getLineGraphicByCountry('USA');
+      const finalUsaPoint = getPointGraphicByDatum('USA', 'Nail polish');
+
+      expect(getGraphicFinalAttribute(finalUsaLine).points.length).toBe(9);
+      expect(finalUsaLine.attribute.points.length).toBe(9);
+      expect(finalUsaPoint.attribute.x).toBeDefined();
+      expect(finalUsaPoint.attribute.y).toBeDefined();
+      expectClose(chinaPointAfterReselect.attribute.y, chinaFinalY);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps retained bars moving when grouped series changes to single-series layout', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createSeriesChangeDocBarSpec('grouped'), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      const retainedBefore = getBarGraphicByDatum(
+        chart,
+        datum => datum?.__Dim_Y__ === 'China' && datum?.__Dim_ColorId__ === 'share_global_co2'
+      );
+      const yBefore = getGraphicFinalAttribute(retainedBefore).y;
+
+      chart.updateSpecSync(createSeriesChangeDocBarSpec('single'));
+
+      const updateStart = ticker.getTime();
+      const retainedAfter = getBarGraphicByDatum(
+        chart,
+        datum => datum?.__Dim_Y__ === 'China' && datum?.__Dim_ColorId__ === 'share_global_co2'
+      );
+      const yAfter = getGraphicFinalAttribute(retainedAfter).y;
+
+      expect(retainedAfter).toBe(retainedBefore);
+      expect(yAfter).not.toBe(yBefore);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION / 2);
+
+      expect(retainedAfter.attribute.y).toBeGreaterThan(Math.min(yBefore, yAfter));
+      expect(retainedAfter.attribute.y).toBeLessThan(Math.max(yBefore, yAfter));
+      expectClose(getGraphicFinalAttribute(retainedAfter).y, yAfter);
+
+      ticker.tickAt(updateStart + UPDATE_DURATION + 50);
+
+      expectClose(retainedAfter.attribute.y, yAfter);
+      expectClose(retainedAfter.baseAttributes?.y, yAfter);
+      expectClose(getGraphicFinalAttribute(retainedAfter).y, yAfter);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('keeps unchanged custom highlight opacity through updateSpec and state refresh', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createStateSwitchSpec(), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      ticker.tickAt(APPEAR_DURATION + 50);
+
+      chart.updateState({
+        custom1: {
+          filter: (datum: any) => getStateDatum(datum)?.date === '2021'
+        }
+      });
+
+      const firstStateStart = ticker.getTime();
+
+      ticker.tickAt(firstStateStart + UPDATE_DURATION + 50);
+
+      const highlightedBefore = getBarGraphicByDatum(chart, datum => datum?.date === '2021');
+
+      expect(highlightedBefore.currentStates).toContain('custom1');
+      expect(highlightedBefore.resolvedStatePatch?.fillOpacity).toBe(0.2);
+      expectClose(highlightedBefore.attribute.fillOpacity, 0.2);
+
+      chart.updateSpecSync(createStateSwitchSpec(5));
+      chart.updateState({
+        custom1: {
+          filter: (datum: any) => getStateDatum(datum)?.date === '2021'
+        }
+      });
+
+      const highlightedAfter = getBarGraphicByDatum(chart, datum => datum?.date === '2021');
+      const secondStateStart = ticker.getTime();
+
+      expect(highlightedAfter.currentStates).toContain('custom1');
+      expect(highlightedAfter.resolvedStatePatch?.fillOpacity).toBe(0.2);
+      expectClose(highlightedAfter.attribute.fillOpacity, 0.2);
+
+      ticker.tickAt(secondStateStart + UPDATE_DURATION / 2);
+
+      expectClose(highlightedAfter.attribute.fillOpacity, 0.2);
+
+      ticker.tickAt(secondStateStart + UPDATE_DURATION + 50);
+
+      expectClose(highlightedAfter.attribute.fillOpacity, 0.2);
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it('uses clipIn on difference markLine so the arrow line is geometrically revealed', () => {
+    const { container, dom } = createChartContainer();
+    const ticker = createManualTicker();
+    const chart = new VChart(createDifferenceMarkLineSpec(), {
+      dom,
+      ticker,
+      animation: true
+    });
+
+    chart.renderSync();
+
+    try {
+      const markerGraphic = getMarkerGraphic(chart, 'markLine');
+
+      ticker.tickAt(MARKER_DURATION / 2);
+
+      const clipRangeGraphics = collectGraphics(markerGraphic).filter(
+        graphic => typeof graphic.attribute?.clipRange === 'number'
+      );
+
+      expect(clipRangeGraphics.length).toBeGreaterThan(0);
+      expect(
+        clipRangeGraphics.some(graphic => graphic.attribute.clipRange > 0 && graphic.attribute.clipRange < 1)
+      ).toBe(true);
+
+      ticker.tickAt(MARKER_DURATION + 50);
+
+      clipRangeGraphics.forEach(graphic => {
+        expectClose(graphic.attribute.clipRange, 1);
+        expectClose(graphic.baseAttributes?.clipRange, 1);
+        expectClose(getGraphicFinalAttribute(graphic).clipRange, 1);
+      });
+    } finally {
+      chart.release();
+      ticker.release();
+      removeDom(container);
+    }
+  });
+
+  it.each([
+    ['markPoint', createMarkPointExitSpec, 'markPoint'],
+    ['difference markLine', createDifferenceMarkLineSpec, 'markLine'],
+    ['markArea', createMarkAreaExitSpec, 'markArea'],
+    ['regular markLine', createRegularMarkLineExitSpec, 'markLine']
+  ] as const)(
+    'runs %s exit fade-out instead of removing the marker immediately',
+    (_name, createSpec, componentType) => {
+      const { container, dom } = createChartContainer();
+      const ticker = createManualTicker();
+      const chart = new VChart(createSpec(true), {
+        dom,
+        ticker,
+        animation: true
+      });
+
+      chart.renderSync();
+
+      try {
+        ticker.tickAt(MARKER_DURATION + 50);
+
+        expectMarkerExitFade(chart, ticker, getMarkerGraphic(chart, componentType), () => createSpec(false));
+      } finally {
+        chart.release();
+        ticker.release();
+        removeDom(container);
+      }
+    }
+  );
 });
diff --git a/packages/vchart/__tests__/unit/core/update-spec.test.ts b/packages/vchart/__tests__/unit/core/update-spec.test.ts
index 4886fba5ab..4ed0a081b2 100644
--- a/packages/vchart/__tests__/unit/core/update-spec.test.ts
+++ b/packages/vchart/__tests__/unit/core/update-spec.test.ts
@@ -135,6 +135,296 @@ describe('vchart updateSpec test', () => {
   });
 });
 
+describe('vchart updateSpec mark style reInit test', () => {
+  it('should preserve component-injected brush state styles after data-only updateSpec', () => {
+    const container = createDiv();
+    const dom = createDiv(container);
+    const createSpec = (values: Array<{ type: string; value: number }>) =>
+      ({
+        type: 'bar',
+        width: 300,
+        height: 200,
+        data: [
+          {
+            id: 'bar',
+            values
+          }
+        ],
+        xField: 'type',
+        yField: 'value',
+        brush: {
+          visible: true,
+          brushType: 'rect',
+          inBrush: {
+            colorAlpha: 1
+          },
+          outOfBrush: {
+            colorAlpha: 0.2
+          }
+        }
+      } as IBarChartSpec);
+    const chart = new VChart(
+      createSpec([
+        { type: '1', value: 20 },
+        { type: '2', value: 30 }
+      ]),
+      {
+        dom,
+        animation: false
+      }
+    );
+
+    chart.renderSync();
+
+    const getBarMark = () => {
+      const barSeries = chart
+        .getChart()
+        ?.getAllSeries()
+        .find(series => series.type === 'bar');
+      const barMark = barSeries?.getMarks().find(mark => mark.name === 'bar') as any;
+
+      expect(barMark).toBeDefined();
+      return barMark;
+    };
+    const expectBrushStates = () => {
+      const barMark = getBarMark();
+
+      expect(barMark.stateStyle.inBrush.fillOpacity.style).toBe(1);
+      expect(barMark.stateStyle.outOfBrush.fillOpacity.style).toBe(0.2);
+    };
+
+    try {
+      expectBrushStates();
+
+      chart.updateSpecSync(
+        createSpec([
+          { type: '1', value: 25 },
+          { type: '2', value: 35 }
+        ])
+      );
+
+      expectBrushStates();
+    } finally {
+      chart.release();
+      removeDom(container);
+    }
+  });
+});
+
+describe('vchart updateSpec field update classification test', () => {
+  const getBarGraphicById = (chart: VChart, id: string) => {
+    const barSeries = chart
+      .getChart()
+      ?.getAllSeries()
+      .find(series => series.type === 'bar');
+    const barMark = barSeries?.getMarks().find(mark => mark.name === 'bar') as any;
+    const barGraphic = barMark?.getGraphics().find((graphic: any) => graphic.context?.data?.[0]?.id === id);
+
+    expect(barMark).toBeDefined();
+    expect(barGraphic).toBeDefined();
+    return barGraphic as any;
+  };
+  const getBarGraphicByDatum = (chart: VChart, predicate: (datum: any) => boolean) => {
+    const barSeries = chart
+      .getChart()
+      ?.getAllSeries()
+      .find(series => series.type === 'bar');
+    const barMark = barSeries?.getMarks().find(mark => mark.name === 'bar') as any;
+    const barGraphic = barMark?.getGraphics().find((graphic: any) => predicate(graphic.context?.data?.[0]));
+
+    expect(barMark).toBeDefined();
+    expect(barGraphic).toBeDefined();
+    return barGraphic as any;
+  };
+  const getBarHeight = (graphic: any) => Math.abs(graphic.attribute.y1 - graphic.attribute.y);
+
+  it('should keep seriesField in default data key when a single dimension value equals the series value', () => {
+    const container = createDiv();
+    const dom = createDiv(container);
+    const chart = new VChart(
+      {
+        type: 'bar',
+        width: 300,
+        height: 200,
+        data: [
+          {
+            id: 'bar',
+            values: [
+              { category: 'A', group: 'A', value: 10 },
+              { category: 'A', group: 'B', value: 20 }
+            ]
+          }
+        ],
+        xField: 'category',
+        yField: 'value',
+        seriesField: 'group',
+        axes: [
+          { orient: 'left', visible: false },
+          { orient: 'bottom', visible: false }
+        ]
+      } as IBarChartSpec,
+      {
+        dom
+      }
+    );
+
+    chart.renderSync();
+
+    try {
+      expect(getBarGraphicByDatum(chart, datum => datum?.group === 'A').context.key).toBe('A_A');
+      expect(getBarGraphicByDatum(chart, datum => datum?.group === 'B').context.key).toBe('A_B');
+    } finally {
+      chart.release();
+      removeDom(container);
+    }
+  });
+
+  it('should update top-level xField through recompile without remaking chart', () => {
+    const container = createDiv();
+    const dom = createDiv(container);
+    const createSpec = (xField: 'category' | 'nextCategory') =>
+      ({
+        type: 'bar',
+        width: 300,
+        height: 200,
+        dataKey: 'id',
+        data: [
+          {
+            id: 'bar',
+            values: [
+              { id: 'a', category: 'A', nextCategory: 'A', value: 10 },
+              { id: 'b', category: 'B', nextCategory: 'A', value: 20 },
+              { id: 'c', category: 'C', nextCategory: 'C', value: 30 }
+            ]
+          }
+        ],
+        xField,
+        yField: 'value',
+        axes: [
+          { orient: 'left', visible: false },
+          { orient: 'bottom', visible: false }
+        ]
+      } as IBarChartSpec);
+    const chart = new VChart(createSpec('category'), {
+      dom,
+      animation: false
+    });
+
+    chart.renderSync();
+
+    try {
+      const chartBefore = chart.getChart();
+      const barBefore = getBarGraphicById(chart, 'b');
+      const xBefore = barBefore.attribute.x;
+
+      chart.updateSpecSync(createSpec('nextCategory'));
+
+      const barAfter = getBarGraphicById(chart, 'b');
+
+      expect(chart.getChart()).toBe(chartBefore);
+      expect(barAfter.attribute.x).not.toBe(xBefore);
+    } finally {
+      chart.release();
+      removeDom(container);
+    }
+  });
+
+  it('should update top-level yField through recompile without remaking chart', () => {
+    const container = createDiv();
+    const dom = createDiv(container);
+    const createSpec = (yField: 'value' | 'nextValue') =>
+      ({
+        type: 'bar',
+        width: 300,
+        height: 200,
+        dataKey: 'id',
+        data: [
+          {
+            id: 'bar',
+            values: [
+              { id: 'a', category: 'A', value: 10, nextValue: 30 },
+              { id: 'b', category: 'B', value: 30, nextValue: 30 }
+            ]
+          }
+        ],
+        xField: 'category',
+        yField,
+        axes: [
+          { orient: 'left', visible: false },
+          { orient: 'bottom', visible: false }
+        ]
+      } as IBarChartSpec);
+    const chart = new VChart(createSpec('value'), {
+      dom,
+      animation: false
+    });
+
+    chart.renderSync();
+
+    try {
+      const chartBefore = chart.getChart();
+      const barBefore = getBarGraphicById(chart, 'a');
+      const yBefore = barBefore.attribute.y;
+      const heightBefore = getBarHeight(barBefore);
+
+      chart.updateSpecSync(createSpec('nextValue'));
+
+      const barAfter = getBarGraphicById(chart, 'a');
+
+      expect(chart.getChart()).toBe(chartBefore);
+      expect(barAfter.attribute.y).not.toBe(yBefore);
+      expect(getBarHeight(barAfter)).not.toBe(heightBefore);
+    } finally {
+      chart.release();
+      removeDom(container);
+    }
+  });
+
+  it('should remake when top-level seriesField changes because mark groupKey is initialized from seriesField', () => {
+    const container = createDiv();
+    const dom = createDiv(container);
+    const createSpec = (seriesField: 'group' | 'nextGroup') =>
+      ({
+        type: 'bar',
+        width: 300,
+        height: 200,
+        data: [
+          {
+            id: 'bar',
+            values: [
+              { category: 'A', value: 10, group: 'old-a', nextGroup: 'new-a' },
+              { category: 'B', value: 20, group: 'old-b', nextGroup: 'new-b' }
+            ]
+          }
+        ],
+        xField: 'category',
+        yField: 'value',
+        seriesField,
+        axes: [
+          { orient: 'left', visible: false },
+          { orient: 'bottom', visible: false }
+        ]
+      } as IBarChartSpec);
+    const chart = new VChart(createSpec('group'), {
+      dom,
+      animation: false
+    });
+
+    chart.renderSync();
+
+    try {
+      const chartBefore = chart.getChart();
+
+      chart.updateSpecSync(createSpec('nextGroup'));
+
+      expect(chart.getChart()).not.toBe(chartBefore);
+    } finally {
+      chart.release();
+      removeDom(container);
+    }
+  });
+});
+
 describe('vchart updateSpec of same spec', () => {
   let container: HTMLElement;
   let dom: HTMLElement;
diff --git a/packages/vchart/package.json b/packages/vchart/package.json
index 796c772e43..2ecda48d2a 100644
--- a/packages/vchart/package.json
+++ b/packages/vchart/package.json
@@ -126,11 +126,11 @@
     "@visactor/vdataset": "~1.0.23",
     "@visactor/vscale": "~1.0.23",
     "@visactor/vlayouts": "~1.0.23",
-    "@visactor/vrender": "1.1.0-alpha.11",
-    "@visactor/vrender-core": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
-    "@visactor/vrender-components": "1.1.0-alpha.11",
-    "@visactor/vrender-animate": "1.1.0-alpha.11",
+    "@visactor/vrender": "1.1.0-alpha.15",
+    "@visactor/vrender-core": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
+    "@visactor/vrender-components": "1.1.0-alpha.15",
+    "@visactor/vrender-animate": "1.1.0-alpha.15",
     "@visactor/vutils-extension": "workspace:2.0.22"
   },
   "publishConfig": {
diff --git a/packages/vchart/src/chart/base/base-chart.ts b/packages/vchart/src/chart/base/base-chart.ts
index 656d59e689..111154fff0 100644
--- a/packages/vchart/src/chart/base/base-chart.ts
+++ b/packages/vchart/src/chart/base/base-chart.ts
@@ -862,7 +862,12 @@ export class BaseChart<T extends IChartSpec> extends CompilableBase implements I
   }
 
   private _getSpecKeys(spec: T) {
-    const ignoreKeys: Record<string, boolean> = { width: true, height: true };
+    const ignoreKeys: Record<string, boolean> = {
+      width: true,
+      height: true,
+      xField: true,
+      yField: true
+    };
     return Object.keys(spec)
       .filter(key => !ignoreKeys[key])
       .sort();
diff --git a/packages/vchart/src/component/brush/brush.ts b/packages/vchart/src/component/brush/brush.ts
index 686a974702..9056ba39f2 100644
--- a/packages/vchart/src/component/brush/brush.ts
+++ b/packages/vchart/src/component/brush/brush.ts
@@ -112,6 +112,12 @@ export class Brush<T extends IBrushSpec = IBrushSpec> extends BaseComponent<T> i
       });
     });
   }
+
+  reInit(spec?: T) {
+    super.reInit(spec);
+    this.init();
+  }
+
   private _initNeedOperatedItem() {
     const seriesUserId = this._spec.seriesId;
     const seriesIndex = this._spec.seriesIndex;
diff --git a/packages/vchart/src/mark/base/base-mark.ts b/packages/vchart/src/mark/base/base-mark.ts
index 25cc20920f..0f8fa7dc02 100644
--- a/packages/vchart/src/mark/base/base-mark.ts
+++ b/packages/vchart/src/mark/base/base-mark.ts
@@ -1657,7 +1657,7 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
           // 表示正在被复用，需要重设属性的
           // TODO 理论上复用后只会走一次enter，所以这里lastAttrs不需要后续清除，这里需要硬拷贝(通过initAttributes重设属性也行)
           g.context.lastAttrs = g.attribute;
-          g.initAttributes({});
+          g.initAttributes(finalAttrs);
           // 为了避免exit一些和enter不一样的属性，所以这里要重置属性
           // const finalAttrs = g.getFinalAttribute();
           // finalAttrs && g.initAttributes({ ...finalAttrs });
@@ -1889,9 +1889,10 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
             }
           }));
           g.applyAnimationState(['exit'], [exitConfigList.length === 1 ? exitConfigList[0] : exitConfigList], () => {
-            // 有可能又被复用了，所以这里需要判断，如果还是在exiting阶段的话才删除
-            // TODO 这里如果频繁执行的话，可能会误判
-            doRemove(g, key);
+            // 有可能又被复用了，所以这里需要判断，如果还是在 exiting 阶段的话才删除
+            if (g.context.diffState === DiffState.exit && g.isExiting && this._graphicMap.get(key) === g) {
+              doRemove(g, key);
+            }
           });
         }
       } else {
diff --git a/packages/vchart/src/series/bar/bar.ts b/packages/vchart/src/series/bar/bar.ts
index 05c600305a..e02dca20c2 100644
--- a/packages/vchart/src/series/bar/bar.ts
+++ b/packages/vchart/src/series/bar/bar.ts
@@ -105,6 +105,22 @@ export class BarSeries<T extends IBarSeriesSpec = IBarSeriesSpec> extends Cartes
         AttributeLevel.Series
       );
     }
+
+    this.initRectMarkStyle();
+  }
+
+  protected initRectMarkStyle() {
+    if (!this._barMark) {
+      return;
+    }
+
+    const bandAxisHelper = this.direction === Direction.vertical ? this._xAxisHelper : this._yAxisHelper;
+    const scale = bandAxisHelper?.getScale?.(0);
+    if (!scale) {
+      return;
+    }
+
+    scale.type === 'band' ? this.initBandRectMarkStyle() : this.initLinearRectMarkStyle();
   }
 
   initLabelMarkStyle(textMark: ITextMark) {
@@ -256,11 +272,7 @@ export class BarSeries<T extends IBarSeriesSpec = IBarSeriesSpec> extends Cartes
 
   init(option: IModelInitOption): void {
     super.init(option);
-    if (this.direction === 'vertical') {
-      this._xAxisHelper?.getScale(0).type === 'band' ? this.initBandRectMarkStyle() : this.initLinearRectMarkStyle();
-    } else {
-      this._yAxisHelper?.getScale(0).type === 'band' ? this.initBandRectMarkStyle() : this.initLinearRectMarkStyle();
-    }
+    this.initRectMarkStyle();
   }
 
   private _shouldDoPreCalculate() {
diff --git a/packages/vchart/src/series/base/base-series.ts b/packages/vchart/src/series/base/base-series.ts
index c639b4420a..4203fddb08 100644
--- a/packages/vchart/src/series/base/base-series.ts
+++ b/packages/vchart/src/series/base/base-series.ts
@@ -111,7 +111,7 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel<T> imp
 
   declare getSpecInfo: () => ISeriesSpecInfo;
 
-  declare protected _option: ISeriesOption;
+  protected declare _option: ISeriesOption;
 
   // 坐标系信息
   readonly coordinate: CoordinateType = 'none';
@@ -240,7 +240,7 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel<T> imp
   }
   protected _dataSet: DataSet;
 
-  declare protected _tooltipHelper: ISeriesTooltipHelper | undefined;
+  protected declare _tooltipHelper: ISeriesTooltipHelper | undefined;
   get tooltipHelper() {
     if (!this._tooltipHelper) {
       this.initTooltip();
@@ -842,8 +842,8 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel<T> imp
     const triggerOff = isValid(finalSelectSpec.triggerOff)
       ? finalSelectSpec.triggerOff
       : isMultiple
-        ? ['empty']
-        : ['empty', finalSelectSpec.trigger];
+      ? ['empty']
+      : ['empty', finalSelectSpec.trigger];
     return {
       type: TRIGGER_TYPE_ENUM.ELEMENT_SELECT as string,
       trigger: finalSelectSpec.trigger as GraphicEventType,
@@ -1282,7 +1282,7 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel<T> imp
       attributeContext: this._markAttributeContext,
       componentType: option.componentType,
       noSeparateStyle,
-      parent: parent !== false ? (parent ?? this._rootMark) : null
+      parent: parent !== false ? parent ?? this._rootMark : null
     });
 
     if (isValid(m)) {
@@ -1341,13 +1341,27 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel<T> imp
       return key;
     }
 
+    const seriesField = this.getSeriesField();
     const dimensionFields = this.getDimensionField();
-    key = dimensionFields.map(field => datum[field]).join('_');
+    const seriesFieldValue = seriesField ? datum[seriesField] : undefined;
+    let hasSeriesFieldInDimension = false;
+    let lastDimensionValue: unknown;
 
-    const seriesField = this.getSeriesField();
+    for (let i = 0; i < dimensionFields.length; i++) {
+      const field = dimensionFields[i];
+      const dimensionValue = datum[field];
+
+      key += `${i > 0 ? '_' : ''}${isNil(dimensionValue) ? '' : dimensionValue}`;
+      hasSeriesFieldInDimension ||= field === seriesField;
+      lastDimensionValue = dimensionValue;
+    }
 
-    if (seriesField && !dimensionFields.includes(seriesField)) {
-      key += `_${datum[seriesField]}`;
+    if (
+      seriesField &&
+      !hasSeriesFieldInDimension &&
+      !(dimensionFields.length > 1 && lastDimensionValue === seriesFieldValue)
+    ) {
+      key += `_${seriesFieldValue}`;
     }
 
     return key;
diff --git a/packages/vchart/src/series/base/constant.ts b/packages/vchart/src/series/base/constant.ts
index 2f606ebe6e..e924de6b2c 100644
--- a/packages/vchart/src/series/base/constant.ts
+++ b/packages/vchart/src/series/base/constant.ts
@@ -17,6 +17,8 @@ export const defaultSeriesIgnoreCheckKeys: { [key: string]: true } = {
 };
 
 export const defaultSeriesCompileCheckKeys: { [key: string]: true } = {
+  xField: true,
+  yField: true,
   invalidType: true,
   animation: true,
   animationAppear: true,
diff --git a/packages/vchart/src/series/box-plot/box-plot.ts b/packages/vchart/src/series/box-plot/box-plot.ts
index 0ef3be123b..75d5a0b040 100644
--- a/packages/vchart/src/series/box-plot/box-plot.ts
+++ b/packages/vchart/src/series/box-plot/box-plot.ts
@@ -196,6 +196,8 @@ export class BoxPlotSeries<T extends IBoxPlotSeriesSpec = IBoxPlotSeriesSpec> ex
         AttributeLevel.Series
       );
     }
+
+    this.initBoxPlotMarkStyle();
   }
 
   initBoxPlotMarkStyle(): void {
diff --git a/tools/story-player/package.json b/tools/story-player/package.json
index 14e56dc3f4..2d6d381f91 100644
--- a/tools/story-player/package.json
+++ b/tools/story-player/package.json
@@ -56,10 +56,10 @@
     "vite": "3.2.6"
   },
   "dependencies": {
-    "@visactor/vrender-core": "1.1.0-alpha.11",
-    "@visactor/vrender-kits": "1.1.0-alpha.11",
+    "@visactor/vrender-core": "1.1.0-alpha.15",
+    "@visactor/vrender-kits": "1.1.0-alpha.15",
     "@visactor/vchart": "workspace:2.0.22",
-    "@visactor/vrender": "1.1.0-alpha.11",
+    "@visactor/vrender": "1.1.0-alpha.15",
     "@visactor/vutils": "~1.0.23"
   }
 }
