diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-02.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-02.json new file mode 100644 index 0000000000..27706ef1dd --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-02.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: bar always display when set bar minHeight. fix#3284", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-48.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-48.json new file mode 100644 index 0000000000..0587f30230 --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-48.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: dot series display bug. fix #3283", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-58.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-58.json new file mode 100644 index 0000000000..1e3e9acb24 --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-08-58.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: brush range not correct. fix #3091", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-09-23.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-09-23.json new file mode 100644 index 0000000000..92fe17c16f --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-09-23.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: datazoom layout when set scale. fix #2574 & #2424", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-29.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-29.json new file mode 100644 index 0000000000..1677b98292 --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-29.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: domain will follow axis. fix #2770", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-34.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-34.json new file mode 100644 index 0000000000..0ebfbb9923 --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-07-02-11-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: update perview when update data. fix #3331", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-49.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-49.json new file mode 100644 index 0000000000..13d6b8e258 --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-49.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: bind right axis when swtich orient. fix#3373", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-50.json b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-50.json new file mode 100644 index 0000000000..ff3dc8978c --- /dev/null +++ b/common/changes/@visactor/vchart/refactor-datazoom-enhance-and-bugfix_2025-11-18-03-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "performance: enhance datazoom and scrollbar performance. close#2525 & #2781", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 68aa342ed7..bee462a2b3 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -22,8 +22,8 @@ importers: '@visactor/vchart-extension': workspace:2.0.7 '@visactor/vchart-theme': ~1.6.6 '@visactor/vmind': 1.2.4-alpha.5 - '@visactor/vrender': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vtable': 1.19.0-alpha.0 '@visactor/vtable-calendar': 1.19.0-alpha.0 '@visactor/vtable-editors': 1.19.0-alpha.0 @@ -59,8 +59,8 @@ importers: '@visactor/vchart-extension': link:../packages/vchart-extension '@visactor/vchart-theme': 1.6.9 '@visactor/vmind': 1.2.4-alpha.5 - '@visactor/vrender': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vtable': 1.19.0-alpha.0 '@visactor/vtable-calendar': 1.19.0-alpha.0 '@visactor/vtable-editors': 1.19.0-alpha.0 @@ -144,8 +144,8 @@ importers: '@types/offscreencanvas': 2019.6.4 '@types/react-is': ^17.0.3 '@visactor/vchart': workspace:2.0.7 - '@visactor/vrender-core': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender-core': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vutils': ~1.0.12 '@vitejs/plugin-react': 3.1.0 eslint: ~8.18.0 @@ -164,8 +164,8 @@ importers: vite: 3.2.6 dependencies: '@visactor/vchart': link:../vchart - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vutils': 1.0.12 react-is: 18.3.1 devDependencies: @@ -208,8 +208,8 @@ importers: '@types/react-is': ^17.0.3 '@visactor/vchart': workspace:2.0.7 '@visactor/vchart-extension': workspace:2.0.7 - '@visactor/vrender-core': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender-core': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vutils': ~1.0.12 '@vitejs/plugin-react': 3.1.0 eslint: ~8.18.0 @@ -230,8 +230,8 @@ importers: dependencies: '@visactor/vchart': link:../vchart '@visactor/vchart-extension': link:../vchart-extension - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vutils': 1.0.12 react-is: 18.3.1 devDependencies: @@ -371,10 +371,10 @@ importers: '@types/offscreencanvas': 2019.6.4 '@visactor/vdataset': ~1.0.12 '@visactor/vlayouts': ~1.0.12 - '@visactor/vrender-animate': ~1.0.24 - '@visactor/vrender-components': ~1.0.24 - '@visactor/vrender-core': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender-animate': ~1.0.26 + '@visactor/vrender-components': ~1.0.26 + '@visactor/vrender-core': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vscale': ~1.0.12 '@visactor/vutils': ~1.0.12 '@visactor/vutils-extension': workspace:2.0.7 @@ -413,10 +413,10 @@ importers: dependencies: '@visactor/vdataset': 1.0.12 '@visactor/vlayouts': 1.0.12 - '@visactor/vrender-animate': 1.0.24 - '@visactor/vrender-components': 1.0.24 - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-animate': 1.0.26 + '@visactor/vrender-components': 1.0.26 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vscale': 1.0.12 '@visactor/vutils': 1.0.12 '@visactor/vutils-extension': link:../vutils-extension @@ -478,10 +478,10 @@ importers: '@visactor/vchart': workspace:2.0.7 '@visactor/vdataset': ~1.0.12 '@visactor/vlayouts': ~1.0.12 - '@visactor/vrender-animate': ~1.0.24 - '@visactor/vrender-components': ~1.0.24 - '@visactor/vrender-core': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender-animate': ~1.0.26 + '@visactor/vrender-components': ~1.0.26 + '@visactor/vrender-core': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vutils': ~1.0.12 '@vitejs/plugin-react': 3.1.0 canvas: 2.11.2 @@ -503,10 +503,10 @@ importers: '@visactor/vchart': link:../vchart '@visactor/vdataset': 1.0.12 '@visactor/vlayouts': 1.0.12 - '@visactor/vrender-animate': 1.0.24 - '@visactor/vrender-components': 1.0.24 - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-animate': 1.0.26 + '@visactor/vrender-components': 1.0.26 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vutils': 1.0.12 devDependencies: '@internal/bundler': link:../../tools/bundler @@ -878,9 +878,9 @@ importers: '@typescript-eslint/eslint-plugin': 5.30.0 '@typescript-eslint/parser': 5.30.0 '@visactor/vchart': workspace:2.0.7 - '@visactor/vrender': ~1.0.24 - '@visactor/vrender-core': ~1.0.24 - '@visactor/vrender-kits': ~1.0.24 + '@visactor/vrender': ~1.0.26 + '@visactor/vrender-core': ~1.0.26 + '@visactor/vrender-kits': ~1.0.26 '@visactor/vutils': ~1.0.12 cross-env: ^7.0.3 eslint: ~8.18.0 @@ -893,9 +893,9 @@ importers: vite: 3.2.6 dependencies: '@visactor/vchart': link:../../packages/vchart - '@visactor/vrender': 1.0.24 - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender': 1.0.26 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vutils': 1.0.12 devDependencies: '@internal/bundler': link:../bundler @@ -4884,10 +4884,10 @@ packages: '@visactor/vutils': 1.0.4 dev: false - /@visactor/vrender-animate/1.0.24: - resolution: {integrity: sha512-XGTzM0r9bObs6MQ9u0IJ29Oxr1h9eKW6QzSppMnhXtQhiPGFzppp6SiRosI+Gjq0FAR/vmHTeu2C6tWZPDwrcg==} + /@visactor/vrender-animate/1.0.26: + resolution: {integrity: sha512-e0AZnc18tGJwElQ2yKqSNTLSHgcM1ML0HR3oK3p2uzzrBRMCWaYvcC1lRdbWyaVicbAAEFMtpI6sbvbtpiXe3A==} dependencies: - '@visactor/vrender-core': 1.0.24 + '@visactor/vrender-core': 1.0.26 '@visactor/vutils': 1.0.12 dev: false @@ -4901,12 +4901,12 @@ packages: '@visactor/vutils': 1.0.4 dev: false - /@visactor/vrender-components/1.0.24: - resolution: {integrity: sha512-GwtRWUuaVw7HJM/GTA3XY/6kjyHzCi10yE4tUSuvrytF2yLdOO+yG920B1nV+rBZGpKgyTpdJmszngQ1RZN4BQ==} + /@visactor/vrender-components/1.0.26: + resolution: {integrity: sha512-IRY3oke+vOLTZVu6M0dxiQ9QEH+Bb+3KA3MwiRWKfBBmtx/RkOUcSNBAXAqw4R/Hue3wM5YyxyWe0Jfdhag5MQ==} dependencies: - '@visactor/vrender-animate': 1.0.24 - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-animate': 1.0.26 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 '@visactor/vscale': 1.0.12 '@visactor/vutils': 1.0.12 dev: false @@ -4918,8 +4918,8 @@ packages: color-convert: 2.0.1 dev: false - /@visactor/vrender-core/1.0.24: - resolution: {integrity: sha512-npcXOil6cyP2pLXk1L9XwVyHDGw7eNnjUEtpwUBn34pyI+d1IWJ8hi19IaBSsl3uzc/qfu9MPXiiHGGoTLzH0A==} + /@visactor/vrender-core/1.0.26: + resolution: {integrity: sha512-0acvC8J1ARoWPJvojOLjxUbpnJnQW0FNZ1in2w6yHad5pX+p9GCh8B7HCRVrucw2kM0vPRKzsWURHxLfWsug9w==} dependencies: '@visactor/vutils': 1.0.12 color-convert: 2.0.1 @@ -4936,24 +4936,24 @@ packages: roughjs: 4.5.2 dev: false - /@visactor/vrender-kits/1.0.24: - resolution: {integrity: sha512-4gaZtCxHXPx4njJq417UfiPp9WbtUsPOJ+NyenhqLghdQXcqI8t1GfrXg2QiGcmqSSn8XDsHsQL6poZR1+wdVw==} + /@visactor/vrender-kits/1.0.26: + resolution: {integrity: sha512-fnv7USvSOD9bJlPGMoQmTmgI+Rf5xegx1W0Sw+QTKAiQVQoXyGJTbpCY8Epfy1iwPO+7O2OdT7Xu3G6Pj5sY1Q==} dependencies: '@resvg/resvg-js': 2.4.1 - '@visactor/vrender-core': 1.0.24 + '@visactor/vrender-core': 1.0.26 '@visactor/vutils': 1.0.12 gifuct-js: 2.1.2 lottie-web: 5.13.0 - roughjs: 4.5.2 + roughjs: 4.6.6 dev: false - /@visactor/vrender/1.0.24: - resolution: {integrity: sha512-RkTdX6H5Z67KxgsAIBX+m9P6BYe+kIEgR44dOZhE3JB2w8RG1m3gzjX5GScaRJAn0M+lv/lirf8aIsqjtbW9gw==} + /@visactor/vrender/1.0.26: + resolution: {integrity: sha512-sPxogYn/OLnMLJZJPI87w40drL5TotOWEkEd1vSD0kFKNSWSEs82QS4HUpUkZinPIHcC4BLtFf5mZonsB3k7lg==} dependencies: - '@visactor/vrender-animate': 1.0.24 - '@visactor/vrender-components': 1.0.24 - '@visactor/vrender-core': 1.0.24 - '@visactor/vrender-kits': 1.0.24 + '@visactor/vrender-animate': 1.0.26 + '@visactor/vrender-components': 1.0.26 + '@visactor/vrender-core': 1.0.26 + '@visactor/vrender-kits': 1.0.26 dev: false /@visactor/vscale/0.18.18: @@ -12335,6 +12335,10 @@ packages: duplexer: 0.1.2 dev: true + /hachure-fill/0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + dev: false + /handle-thing/2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: true @@ -19661,6 +19665,15 @@ packages: points-on-path: 0.2.1 dev: false + /roughjs/4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + dev: false + /rsvp/4.8.5: resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} engines: {node: 6.* || >= 7.*} diff --git a/docs/package.json b/docs/package.json index 3068972807..fb76b837db 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.12", - "@visactor/vrender": "~1.0.24", - "@visactor/vrender-kits": "~1.0.24", + "@visactor/vrender": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", "@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 ed05eaa5ed..b321efd75a 100644 --- a/packages/openinula-vchart/package.json +++ b/packages/openinula-vchart/package.json @@ -30,8 +30,8 @@ "dependencies": { "@visactor/vchart": "workspace:2.0.7", "@visactor/vutils": "~1.0.12", - "@visactor/vrender-core": "~1.0.24", - "@visactor/vrender-kits": "~1.0.24", + "@visactor/vrender-core": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", "react-is": "^18.2.0" }, "devDependencies": { diff --git a/packages/react-vchart/package.json b/packages/react-vchart/package.json index b6a4ff7c6d..c5fdcd0a30 100644 --- a/packages/react-vchart/package.json +++ b/packages/react-vchart/package.json @@ -31,8 +31,8 @@ "@visactor/vchart": "workspace:2.0.7", "@visactor/vchart-extension": "workspace:2.0.7", "@visactor/vutils": "~1.0.12", - "@visactor/vrender-core": "~1.0.24", - "@visactor/vrender-kits": "~1.0.24", + "@visactor/vrender-core": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", "react-is": "^18.2.0" }, "devDependencies": { diff --git a/packages/vchart-extension/package.json b/packages/vchart-extension/package.json index 3e741eb1da..2a42065594 100644 --- a/packages/vchart-extension/package.json +++ b/packages/vchart-extension/package.json @@ -21,10 +21,10 @@ "start": "ts-node __tests__/runtime/browser/scripts/initVite.ts && vite serve __tests__/runtime/browser" }, "dependencies": { - "@visactor/vrender-core": "~1.0.24", - "@visactor/vrender-kits": "~1.0.24", - "@visactor/vrender-components": "~1.0.24", - "@visactor/vrender-animate": "~1.0.24", + "@visactor/vrender-core": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", + "@visactor/vrender-components": "~1.0.26", + "@visactor/vrender-animate": "~1.0.26", "@visactor/vchart": "workspace:2.0.7", "@visactor/vutils": "~1.0.12", "@visactor/vdataset": "~1.0.12", diff --git a/packages/vchart/__tests__/unit/chart/treemap.test.ts b/packages/vchart/__tests__/unit/chart/treemap.test.ts index 5ea06804ba..f28c8b3a62 100644 --- a/packages/vchart/__tests__/unit/chart/treemap.test.ts +++ b/packages/vchart/__tests__/unit/chart/treemap.test.ts @@ -96,7 +96,8 @@ describe('treemap chart test', () => { test('treemap compiler', async () => { const cs = new VChart(spec, { mode: 'desktop-browser', - renderCanvas: canvasDom + renderCanvas: canvasDom, + animation: false }); await cs.renderAsync(); const series: TreemapSeries = cs.getChart().getAllSeries()[0] as TreemapSeries; @@ -128,7 +129,8 @@ describe('treemap chart test', () => { }, { mode: 'desktop-browser', - renderCanvas: canvasDom + renderCanvas: canvasDom, + animation: false } ); await vchart.renderAsync(); diff --git a/packages/vchart/__tests__/unit/chart/word-cloud.test.ts b/packages/vchart/__tests__/unit/chart/word-cloud.test.ts index ab1916f1af..4c418ea465 100644 --- a/packages/vchart/__tests__/unit/chart/word-cloud.test.ts +++ b/packages/vchart/__tests__/unit/chart/word-cloud.test.ts @@ -108,7 +108,8 @@ describe('wordCloud chart test', () => { test('wordCloud compiler', async () => { const cs = new VChart(spec, { mode: 'desktop-browser', - renderCanvas: canvasDom + renderCanvas: canvasDom, + animation: false }); await cs.renderAsync(); const series: WordCloudSeries = cs.getChart().getAllSeries()[0] as WordCloudSeries; @@ -178,7 +179,8 @@ describe('wordCloud chart test', () => { }, { mode: 'desktop-browser', - renderCanvas: canvasDom + renderCanvas: canvasDom, + animation: false } ); await cs.renderAsync(); diff --git a/packages/vchart/__tests__/unit/component/cartesian/axis/axis-sync-break.test.ts b/packages/vchart/__tests__/unit/component/cartesian/axis/axis-sync-break.test.ts index f0935e5042..8193d67c99 100644 --- a/packages/vchart/__tests__/unit/component/cartesian/axis/axis-sync-break.test.ts +++ b/packages/vchart/__tests__/unit/component/cartesian/axis/axis-sync-break.test.ts @@ -1496,7 +1496,8 @@ describe('VChart', () => { }; vchart = new VChart(spec, { renderCanvas: canvasDom, - autoFit: true + autoFit: true, + animation: false }); vchart.renderSync(); @@ -2993,7 +2994,8 @@ describe('VChart', () => { }; vchart = new VChart(spec, { renderCanvas: canvasDom, - autoFit: true + autoFit: true, + animation: false }); vchart.renderSync(); diff --git a/packages/vchart/__tests__/unit/component/cartesian/axis/inner-offset.test.ts b/packages/vchart/__tests__/unit/component/cartesian/axis/inner-offset.test.ts index 6eaf779be7..7c5b89bd71 100644 --- a/packages/vchart/__tests__/unit/component/cartesian/axis/inner-offset.test.ts +++ b/packages/vchart/__tests__/unit/component/cartesian/axis/inner-offset.test.ts @@ -141,7 +141,8 @@ describe('VChart', () => { }; vchart = new VChart(spec, { renderCanvas: canvasDom, - autoFit: true + autoFit: true, + animation: false }); await vchart.renderAsync(); diff --git a/packages/vchart/__tests__/unit/core/update-spec.test.ts b/packages/vchart/__tests__/unit/core/update-spec.test.ts index 3d84b9853d..4886fba5ab 100644 --- a/packages/vchart/__tests__/unit/core/update-spec.test.ts +++ b/packages/vchart/__tests__/unit/core/update-spec.test.ts @@ -66,7 +66,8 @@ describe('vchart updateSpec test', () => { markLine: [] } as any; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); }); @@ -991,7 +992,8 @@ describe('vchart updateSpec of same spec', () => { hash: '3136c561bad4328a39917f23d8606675' } as unknown as IBarChartSpec; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec(spec, false); @@ -1045,7 +1047,8 @@ describe('vchart updateSpec of same spec', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec(spec, false); @@ -1094,7 +1097,8 @@ describe('vchart updateSpec of same spec', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec(spec, false); @@ -1186,7 +1190,8 @@ describe('vchart updateSpec of same spec', () => { }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec(spec, false); @@ -1261,7 +1266,8 @@ describe('vchart updateSpec of different about label', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1326,7 +1332,8 @@ describe('vchart updateSpec of different about label', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1395,7 +1402,8 @@ describe('vchart updateSpec of different about label', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1473,7 +1481,8 @@ describe('vchart updateSpec of different about label', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1559,7 +1568,8 @@ describe('vchart updateSpec of different about label', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1634,7 +1644,8 @@ describe('vchart updateSpec of different about label', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1739,7 +1750,8 @@ describe('vchart updateSpec should not throw error', () => { ] }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1856,7 +1868,8 @@ describe('vchart updateSpec of totalLabel', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -1978,7 +1991,8 @@ describe('vchart updateSpec of width, height', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -2085,7 +2099,8 @@ describe('vchart updateSpec of different axes', () => { }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -2165,7 +2180,8 @@ describe('vchart updateSpec of different title', () => { seriesField: 'from' }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -2221,7 +2237,8 @@ describe('vchart updateSpec of different title', () => { seriesField: 'from' }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -2315,7 +2332,8 @@ describe('vchart updateSpec of different indicator', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( @@ -2389,7 +2407,8 @@ describe('vchart updateSpec of different indicator', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); const updateRes = (vchart as any)._updateSpec( diff --git a/packages/vchart/__tests__/unit/core/vchart-event.test.ts b/packages/vchart/__tests__/unit/core/vchart-event.test.ts index ddc16aca2e..993e3c13d8 100644 --- a/packages/vchart/__tests__/unit/core/vchart-event.test.ts +++ b/packages/vchart/__tests__/unit/core/vchart-event.test.ts @@ -96,7 +96,8 @@ describe('vchart event test', () => { } }; vchart = new VChart(spec, { - dom + dom, + animation: false }); vchart.renderSync(); }); diff --git a/packages/vchart/__tests__/unit/core/vchart.test.ts b/packages/vchart/__tests__/unit/core/vchart.test.ts index d51e0fba7f..7f0f2b05c7 100644 --- a/packages/vchart/__tests__/unit/core/vchart.test.ts +++ b/packages/vchart/__tests__/unit/core/vchart.test.ts @@ -163,7 +163,8 @@ describe('VChart', () => { vchart = new VChart(spec, { renderCanvas: canvasDom, background: 'yellow', - autoFit: true + autoFit: true, + animation: false }); expect((vchart as any)._currentSize).toEqual({ @@ -221,7 +222,8 @@ describe('VChart', () => { ]; vchart = new VChart(spec, { renderCanvas: canvasDom, - background: 'yellow' + background: 'yellow', + animation: false }); vchart.updateData('areaData', data); vchart.renderSync(); @@ -271,7 +273,8 @@ describe('VChart', () => { ]; vchart = new VChart(spec, { renderCanvas: canvasDom, - background: 'yellow' + background: 'yellow', + animation: false }); vchart.renderSync(); @@ -686,7 +689,8 @@ describe('VChart', () => { ] }; vchart = new VChart(spec as any, { - dom + dom, + animation: false }); vchart.renderSync(); }); diff --git a/packages/vchart/__tests__/unit/function/formatter.test.ts b/packages/vchart/__tests__/unit/function/formatter.test.ts index 392c0d27da..cffea02d17 100644 --- a/packages/vchart/__tests__/unit/function/formatter.test.ts +++ b/packages/vchart/__tests__/unit/function/formatter.test.ts @@ -48,7 +48,8 @@ describe('formatter calc function test', () => { const chart = new VChart(spec as unknown as IBarChartSpec, { renderCanvas: canvasDom, - onError: () => {} + onError: () => {}, + animation: false }); chart.renderSync(); diff --git a/packages/vchart/__tests__/unit/function/register-function.test.ts b/packages/vchart/__tests__/unit/function/register-function.test.ts index 6071239723..b22cec7078 100644 --- a/packages/vchart/__tests__/unit/function/register-function.test.ts +++ b/packages/vchart/__tests__/unit/function/register-function.test.ts @@ -143,7 +143,8 @@ describe('register function test', () => { chart = new VChart(spec as unknown as IBarChartSpec, { renderCanvas: canvasDom, - onError: () => {} + onError: () => {}, + animation: false }); chart.renderSync(); @@ -167,7 +168,8 @@ describe('register function test', () => { test('instance function register', () => { chart2 = new VChart(spec2 as unknown as IBarChartSpec, { renderCanvas: canvasDom, - onError: () => {} + onError: () => {}, + animation: false }); // 实例注册函数 diff --git a/packages/vchart/__tests__/unit/theme/line.test.ts b/packages/vchart/__tests__/unit/theme/line.test.ts index 1b18d1b3d4..6bb3bb0a88 100644 --- a/packages/vchart/__tests__/unit/theme/line.test.ts +++ b/packages/vchart/__tests__/unit/theme/line.test.ts @@ -54,7 +54,8 @@ describe('theme switch test', () => { { renderCanvas: canvasDom, background: 'yellow', - autoFit: true + autoFit: true, + animation: false } ); @@ -130,7 +131,8 @@ describe('theme switch test', () => { { renderCanvas: canvasDom, background: 'yellow', - autoFit: true + autoFit: true, + animation: false } ); @@ -172,7 +174,8 @@ describe('theme switch test', () => { { renderCanvas: canvasDom, background: 'yellow', - autoFit: true + autoFit: true, + animation: false } ); diff --git a/packages/vchart/package.json b/packages/vchart/package.json index 19ef4e7ae9..90377e4a65 100644 --- a/packages/vchart/package.json +++ b/packages/vchart/package.json @@ -122,10 +122,10 @@ "@visactor/vdataset": "~1.0.12", "@visactor/vscale": "~1.0.12", "@visactor/vlayouts": "~1.0.12", - "@visactor/vrender-core": "~1.0.24", - "@visactor/vrender-kits": "~1.0.24", - "@visactor/vrender-components": "~1.0.24", - "@visactor/vrender-animate": "~1.0.24", + "@visactor/vrender-core": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", + "@visactor/vrender-components": "~1.0.26", + "@visactor/vrender-animate": "~1.0.26", "@visactor/vutils-extension": "workspace:2.0.7" }, "publishConfig": { diff --git a/packages/vchart/src/component/axis/base-axis.ts b/packages/vchart/src/component/axis/base-axis.ts index bed91919bb..a36a64e595 100644 --- a/packages/vchart/src/component/axis/base-axis.ts +++ b/packages/vchart/src/component/axis/base-axis.ts @@ -115,7 +115,7 @@ export abstract class AxisComponent { - data.push(s.getRawDataStatisticsByField(f, false) as { min: number; max: number; values: any[] }); + data.push( + s.getRawDataStatisticsByField(f, !!isContinuous(this._scale.type)) as { + min: number; + max: number; + values: any[]; + } + ); }); } else if (viewData && viewData.latestData && viewData.latestData.length) { const seriesData = s.getViewDataStatistics?.(); diff --git a/packages/vchart/src/component/axis/cartesian/axis.ts b/packages/vchart/src/component/axis/cartesian/axis.ts index 20ee712daa..86fd2efb7f 100644 --- a/packages/vchart/src/component/axis/cartesian/axis.ts +++ b/packages/vchart/src/component/axis/cartesian/axis.ts @@ -273,7 +273,7 @@ export abstract class CartesianAxis void; } export interface IAxisItem { diff --git a/packages/vchart/src/component/axis/mixin/band-axis-mixin.ts b/packages/vchart/src/component/axis/mixin/band-axis-mixin.ts index 1cfc12b636..636098e74a 100644 --- a/packages/vchart/src/component/axis/mixin/band-axis-mixin.ts +++ b/packages/vchart/src/component/axis/mixin/band-axis-mixin.ts @@ -99,6 +99,10 @@ export class BandAxisMixin { } protected _rawDomainIndex: { [key: string | number | symbol]: number }[] = []; + protected _rawDomain: StringOrNumber[][] = []; + getRawDomain() { + return this._rawDomain; + } dataToPosition(values: any[], cfg: IAxisLocationCfg = {}): number { if (values.length === 0 || this._scales.length === 0) { @@ -255,6 +259,7 @@ export class BandAxisMixin { protected _updateRawDomain() { // 默认值设置了无效? this._rawDomainIndex = []; + this._rawDomain = []; const userDomain = this._spec.domain; for (let i = 0; i < this._scales.length; i++) { @@ -265,12 +270,15 @@ export class BandAxisMixin { const data = this.collectData(i, true); const domain = this.computeBandDomain(data); this._rawDomainIndex[i] = {}; + this._rawDomain[i] = domain; domain.forEach((d, _i) => (this._rawDomainIndex[i][d] = _i)); } } + this.event.emit(ChartEvent.scaleRawDomainUpdate, { model: this as unknown as IModel }); } protected _clearRawDomain() { this._rawDomainIndex = []; + this._rawDomain = []; } } diff --git a/packages/vchart/src/component/axis/mixin/linear-axis-mixin.ts b/packages/vchart/src/component/axis/mixin/linear-axis-mixin.ts index 777c37c019..a73a417db3 100644 --- a/packages/vchart/src/component/axis/mixin/linear-axis-mixin.ts +++ b/packages/vchart/src/component/axis/mixin/linear-axis-mixin.ts @@ -10,6 +10,7 @@ import type { IOrientType } from '../../../typings/space'; import type { IComponentOption } from '../../interface/common'; import type { StringOrNumber } from '../../../typings'; import { breakData } from './util/break-data'; +import type { IModel } from '../../../model/interface'; export const e10 = Math.sqrt(50); export const e5 = Math.sqrt(10); @@ -37,7 +38,7 @@ export interface LinearAxisMixin { _tick: ITick | undefined; isSeriesDataEnable: any; computeDomain: any; - collectData: (depth?: number) => { min: number; max: number; values: any[] }[]; + collectData: (depth?: number, rawData?: boolean) => { min: number; max: number; values: any[] }[]; /** * 这个变量在其他break相关组件和扩展中都有使用 */ @@ -55,6 +56,10 @@ export interface LinearAxisMixin { export class LinearAxisMixin { protected _extend: { [key: string]: number } = {}; + protected _rawDomain: StringOrNumber[] = []; + getRawDomain() { + return this._rawDomain; + } niceLabelFormatter: (value: StringOrNumber) => StringOrNumber = null; @@ -367,6 +372,9 @@ export class LinearAxisMixin { if (!this.isSeriesDataEnable()) { return; } + if (!this._rawDomain?.length && this._scale) { + this._updateRawDomain(); + } const data = this.collectData(); const domain: number[] = this.computeLinearDomain(data) as number[]; this.updateScaleDomainByModel(domain); @@ -415,4 +423,15 @@ export class LinearAxisMixin { return value; }; } + + protected _updateRawDomain() { + const data = this.collectData(0, true); + const domain: number[] = this.computeLinearDomain(data) as number[]; + this._rawDomain = domain; + this.event.emit(ChartEvent.scaleRawDomainUpdate, { model: this as unknown as IModel }); + } + + protected _clearRawDomain() { + this._rawDomain = []; + } } diff --git a/packages/vchart/src/component/axis/polar/axis.ts b/packages/vchart/src/component/axis/polar/axis.ts index a0a3f16d6d..5fff3e6eab 100644 --- a/packages/vchart/src/component/axis/polar/axis.ts +++ b/packages/vchart/src/component/axis/polar/axis.ts @@ -230,7 +230,7 @@ export abstract class PolarAxis ex return this.computeBandDomain(data); } - protected updateScaleRange() { + updateScaleRange() { const isChanged = super.updateScaleRange(); this.updateGroupScaleRange(); diff --git a/packages/vchart/src/component/brush/brush.ts b/packages/vchart/src/component/brush/brush.ts index 21a77390e7..5ee7fa599b 100644 --- a/packages/vchart/src/component/brush/brush.ts +++ b/packages/vchart/src/component/brush/brush.ts @@ -7,7 +7,7 @@ import { ComponentTypeEnum } from '../interface/type'; import { Brush as BrushComponent, IOperateType as BrushEvent } from '@visactor/vrender-components'; import type { IBounds, IPointLike, Maybe } from '@visactor/vutils'; // eslint-disable-next-line no-duplicate-imports -import { array, polygonIntersectPolygon, isValid, last, cloneDeep } from '@visactor/vutils'; +import { array, polygonIntersectPolygon, isValid, last } from '@visactor/vutils'; import type { IModelRenderOption, IModelSpecInfo } from '../../model/interface'; import type { IRegion } from '../../region/interface'; import type { IGraphic, IGroup, INode, IPolygon } from '@visactor/vrender-core'; @@ -22,6 +22,8 @@ import type { DataZoom } from '../data-zoom'; import type { AxisComponent } from '../axis/base-axis'; import { getSpecInfo } from '../util'; import { brush } from '../../theme/builtin/common/component/brush'; +import { isReverse, statePointToData } from '../data-zoom/util'; +import type { CartesianAxis } from '../axis/cartesian'; const IN_BRUSH_STATE = 'inBrush'; const OUT_BRUSH_STATE = 'outOfBrush'; @@ -669,18 +671,16 @@ export class Brush extends BaseComponent i const axisRangeExpand = this._spec.axisRangeExpand ?? 0; const { x1, x2, y1, y2 } = operateMaskBounds; const regionStartAttr = isHorizontal ? 'x' : 'y'; + const regionSizeAttr = isHorizontal ? 'width' : 'height'; const boundsStart = isHorizontal ? x1 : y1; const boundsEnd = isHorizontal ? x2 : y2; if (this._axisDataZoomMap[axis.id]) { const dataZoom = this._axisDataZoomMap[axis.id]; - const releatedAxis = dataZoom.relatedAxisComponent as AxisComponent; - const startValue = releatedAxis - .getScale() - .invert(boundsStart - region.getLayoutStartPoint()[regionStartAttr]); - const endValue = releatedAxis.getScale().invert(boundsEnd - region.getLayoutStartPoint()[regionStartAttr]); - const startPercent = dataZoom.dataToStatePoint(startValue); - const endPercent = dataZoom.dataToStatePoint(endValue); + const startPercent = + (boundsStart - region.getLayoutStartPoint()[regionStartAttr]) / region.getLayoutRect()[regionSizeAttr]; + const endPercent = + (boundsEnd - region.getLayoutStartPoint()[regionStartAttr]) / region.getLayoutRect()[regionSizeAttr]; const newStartPercent = this._stateClamp(startPercent - axisRangeExpand); const newEndPercent = this._stateClamp(endPercent + axisRangeExpand); dataZoom.setStartAndEnd(Math.min(newStartPercent, newEndPercent), Math.max(newStartPercent, newEndPercent), [ @@ -692,8 +692,16 @@ export class Brush extends BaseComponent i operateComponent: dataZoom, start: newStartPercent, end: newEndPercent, - startValue: dataZoom.statePointToData(newStartPercent), - endValue: dataZoom.statePointToData(newEndPercent) + startValue: statePointToData( + newStartPercent, + dataZoom.stateScale, + isReverse(dataZoom.relatedAxisComponent as CartesianAxis, dataZoom.isHorizontal) + ), + endValue: statePointToData( + newEndPercent, + dataZoom.stateScale, + isReverse(dataZoom.relatedAxisComponent as CartesianAxis, dataZoom.isHorizontal) + ) }); } else { const range = axis.getScale().range(); diff --git a/packages/vchart/src/component/data-zoom/data-filter-base-component.ts b/packages/vchart/src/component/data-zoom/data-filter-base-component.ts index 83a7e0aece..92ead75276 100644 --- a/packages/vchart/src/component/data-zoom/data-filter-base-component.ts +++ b/packages/vchart/src/component/data-zoom/data-filter-base-component.ts @@ -1,84 +1,97 @@ import type { ICartesianSeries, IPolarSeries, ISeries } from '../../series/interface'; -// eslint-disable-next-line no-duplicate-imports import { eachSeries } from '../../util/model'; -// eslint-disable-next-line no-duplicate-imports import { BaseComponent } from '../base/base-component'; import type { IEffect, IModelInitOption } from '../../model/interface'; import { ComponentTypeEnum, type IComponent, type IComponentOption } from '../interface'; -import { dataFilterComputeDomain, dataFilterWithNewDomain, lockStatisticsFilter } from './util'; -import type { AdaptiveSpec, ILayoutRect, ILayoutType, IOrientType, IRect, StringOrNumber } from '../../typings'; +import { + dataFilterComputeDomain, + dataFilterWithNewDomain, + dataToStatePoint, + getAxisBandSize, + isReverse, + lockStatisticsFilter, + modeCheck, + parseDomainFromStateAndValue, + parseDomainFromState, + statePointToData +} from './util'; +import type { + AdaptiveSpec, + ILayoutPoint, + ILayoutRect, + ILayoutType, + IOrientType, + IRect, + StringOrNumber +} from '../../typings'; import { registerDataSetInstanceParser, registerDataSetInstanceTransform } from '../../data/register'; -import { BandScale, isContinuous, isDiscrete } from '@visactor/vscale'; -// eslint-disable-next-line no-duplicate-imports -import type { IBandLikeScale, IBaseScale } from '@visactor/vscale'; -// eslint-disable-next-line no-duplicate-imports +import type { IContinuousScale } from '@visactor/vscale'; +import { + BandScale, + isContinuous, + isDiscrete, + LinearScale, + type IBandLikeScale, + type IBaseScale +} from '@visactor/vscale'; import { Direction } from '../../typings/space'; import type { CartesianAxis, ICartesianBandAxisSpec } from '../axis/cartesian'; +import type { IAxis } from '../axis'; import { getDirectionByOrient, getOrient } from '../axis/cartesian/util/common'; -import type { IBoundsLike } from '@visactor/vutils'; -// eslint-disable-next-line no-duplicate-imports import { mixin, - clamp, isNil, - merge, isEqual, isValid, array, minInArray, maxInArray, - abs, last, - throttle + type IBoundsLike, + isValidNumber } from '@visactor/vutils'; -// eslint-disable-next-line no-duplicate-imports -import type { IFilterMode } from './interface'; -import type { - IDataFilterComponent, - IDataFilterComponentSpec, - IRoamDragSpec, - IRoamScrollSpec, - IRoamZoomSpec -} from './interface'; +import type { IDataFilterComponent, IDataFilterComponentSpec, IFilterMode } from './interface'; import { dataViewParser, DataView } from '@visactor/vdataset'; import { CompilableData } from '../../compile/data/compilable-data'; -import type { BaseEventParams } from '../../event/interface'; -import type { IZoomable } from '../../interaction/zoom/zoomable'; -// eslint-disable-next-line no-duplicate-imports import { Zoomable } from '../../interaction/zoom/zoomable'; -import type { AbstractComponent, DataZoom } from '@visactor/vrender-components'; -import type { IDelayType } from '../../typings/event'; +import type { AbstractComponent, DataZoom as DataZoomComponent } from '@visactor/vrender-components'; import { TransformLevel } from '../../data/initialize'; import type { IDataZoomSpec } from './data-zoom/interface'; import type { IGraphic, IGroup } from '@visactor/vrender-core'; import { AttributeLevel } from '../../constant/attribute'; import type { IGroupMark } from '../../mark/interface/mark'; +import { DataFilterEvent } from './data-filter-event'; +import { ChartEvent } from '../../constant/event'; export abstract class DataFilterBaseComponent extends BaseComponent> implements IDataFilterComponent { + protected _dataFilterEvent: DataFilterEvent; layoutType: ILayoutType | 'none' = 'none'; protected _component: AbstractComponent; protected _orient: IOrientType = 'left'; protected _isHorizontal: boolean; - - protected _throttledHide: () => void; + get isHorizontal() { + return this._isHorizontal; + } // 是否为自动模式 protected _auto?: boolean; protected _fixedBandSize?: number; protected _cacheRect?: ILayoutRect; + protected _cacheLayoutStartPoint?: ILayoutPoint; protected _cacheVisibility?: boolean = undefined; protected _dataUpdating: boolean = false; // 数据 protected _stateScale: IBaseScale; - - protected _relatedAxisComponent!: IComponent; - protected _originalStateFields: Record; + get stateScale() { + return this._stateScale; + } + protected _hasInitStateScale: boolean = false; // 与系列的关联关系 // 优先级:id > index @@ -88,6 +101,11 @@ export abstract class DataFilterBaseComponent; // 起点数据 protected _startValue!: number | string; @@ -116,27 +134,7 @@ export abstract class DataFilterBaseComponent { + // eslint-disable-next-line no-console + console.log('scaleRawDomainUpdate', (model as unknown as { getRawDomain: () => any }).getRawDomain()); + }); + } protected _handleChange(start: number, end: number, updateComponent?: boolean) { const zoomLock = this._spec?.zoomLock ?? false; if ( @@ -199,15 +201,6 @@ export abstract class DataFilterBaseComponent; - if (!axis) { - return false; - } - const axisScale = axis.getScale() as IBandLikeScale; - return axisScale.range()[0] > axisScale.range()[1] && (!axis.getInverse() || this._isHorizontal); - } - protected _updateRangeFactor(tag?: 'startHandler' | 'endHandler') { // 轴的range有时是相反的 // 比如相同的region范围, 有的场景range为[0, 500], 有的场景range为[500, 0] @@ -216,7 +209,7 @@ export abstract class DataFilterBaseComponent; const axisScale = axis.getScale() as IBandLikeScale; - const reverse = this._isReverse(); + const reverse = isReverse(axis, this._isHorizontal); const newRangeFactor: [number, number] = reverse ? [1 - this._end, 1 - this._start] : [this._start, this._end]; if (reverse) { @@ -255,6 +248,14 @@ export abstract class DataFilterBaseComponent { + this._startValue = startValue; + this._endValue = endValue; + this._newDomain = parseDomainFromState(this._startValue, this._endValue, this._stateScale); + this.effect.onZoomChange?.(tag); + return true; + }; + effect: IEffect = { onZoomChange: (tag?: 'startHandler' | 'endHandler') => { const axis = this._relatedAxisComponent as CartesianAxis; @@ -262,7 +263,7 @@ export abstract class DataFilterBaseComponent { + return { + start: this._start, + end: this._end + }; + }, + () => this._regions, + (() => this._option).bind(this), + () => this.event + ); } - /** - * the hook after this component is created - */ + /*** start: component lifecycle ***/ created() { super.created(); - // related axis this._setAxisFromSpec(); - // related regions this._setRegionsFromSpec(); this._initEvent(); - // data for background this._initData(); - // init the state scale this._initStateScale(); - // set state: _start, _end, _startValue, _endValue, _newDomain from spec this._setStateFromSpec(); } @@ -336,6 +347,122 @@ export abstract class DataFilterBaseComponent, prevSpec: AdaptiveSpec) { + const result = super._compareSpec(spec, prevSpec); + if (!result.reMake && !isEqual(prevSpec, spec)) { + result.reRender = true; + result.reMake = true; + } + + return result; + } + + reInit(spec?: AdaptiveSpec) { + super.reInit(spec); + + this._marks.forEach(g => { + (g).getMarks().forEach(m => { + this.initMarkStyleWithSpec(m, (this._spec as any)[m.name]); + }); + }); + } + + onLayoutStart(layoutRect: IRect, viewRect: ILayoutRect): void { + super.onLayoutStart(layoutRect, viewRect); + const isShown = this._autoUpdate(layoutRect); + this._autoVisible(isShown); + this._dataUpdating = false; + } + + updateLayoutAttribute(): void { + this._visible && this._createOrUpdateComponent(); + + // 第一次渲染, 根据配置强制触发start和end变更 + if (!this._hasInitStateScale) { + if (this._start !== 0 || this._end !== 1) { + this._newDomain = parseDomainFromStateAndValue( + this._spec.start, + this._startValue, + this._spec.end, + this._endValue, + this._stateScale + ); + this.effect.onZoomChange(); + } + this._hasInitStateScale = true; + } + } + + protected _initAfterLayout() { + // clear stateScale, 避免在_initStateScale中使用到旧的scale domain + this._stateScale = null; + this._initStateScale(); + // 这时轴的 scale range 是对的 + this._updateScaleRange(); + this._setStateFromAxis(); + } + + protected _beforeLayoutEnd() { + if (!this._hasInitStateScale) { + this._initAfterLayout(); + } else { + this._updateScaleRange(); + } + } + + onLayoutEnd(): void { + this._beforeLayoutEnd(); + // 布局结束后, start和end会发生变化, 因此需要再次更新visible + const isShown = !(this._start === 0 && this._end === 1); + this._autoVisible(isShown); + // onLayoutEnd 做的事情 + // updateLayoutAttribute + // 更新 _newDomain + // 创建 更新vrender组件属性 + super.onLayoutEnd(); + // 这里修改了轴的scaleRange, 因此需要更新轴的视图 + (this._relatedAxisComponent as IAxis)?.updateScaleRange(); + } + + /** + * bounds预计算 + * @param rect + * @returns + */ + getBoundsInRect(rect: ILayoutRect): IBoundsLike { + const result: IBoundsLike = { x1: this.getLayoutStartPoint().x, y1: this.getLayoutStartPoint().y, x2: 0, y2: 0 }; + + if (this._isHorizontal) { + result.y2 = result.y1 + this._height; + result.x2 = result.x1 + rect.width; + } else { + result.x2 = result.x1 + this._width; + result.y2 = result.y1 + rect.height; + } + return result; + } + /*** end: component lifecycle **/ + + /*** start: set attributes & bind related axis and region ***/ + setAttrFromSpec() { + super.setAttrFromSpec(); + // interaction相关 + this._dataFilterEvent.setEventAttrFromSpec(); + // style相关 + this._field = this._spec.field; + this._width = this._computeWidth(); + this._height = this._computeHeight(); + this._visible = this._spec.visible ?? true; + } protected _setAxisFromSpec() { if (isValid(this._spec.axisId)) { this._relatedAxisComponent = this._option.getComponentByUserId(this._spec.axisId); @@ -344,10 +471,12 @@ export abstract class DataFilterBaseComponent (cm as any)._orient === this._orient); + const sameOrientAxis = axes.find( + (cm: any) => getDirectionByOrient((cm as any)._orient) === getDirectionByOrient(this._orient) + ); if (sameOrientAxis) { this._relatedAxisComponent = sameOrientAxis; @@ -403,44 +532,15 @@ export abstract class DataFilterBaseComponent d[this._stateField]); - - if (isContinuous) { - const domainNum = domain.map((n: any) => n * 1); - return domain.length ? [minInArray(domainNum), maxInArray(domainNum)] : [-Infinity, Infinity]; - } - - return domain; - } - - protected _initEvent() { - this._initCommonEvent(); - } + /*** end: set attributes & bind related axis and region ***/ + /*** start: data change and reset view ***/ protected _initData() { const dataCollection: any[] = []; + const seriesCollection: any[] = []; const stateFields: string[] = []; const valueFields: string[] = []; let isCategoryState: boolean; - if (this._relatedAxisComponent) { const originalStateFields = {}; eachSeries( @@ -471,9 +571,11 @@ export abstract class DataFilterBaseComponent { dataCollection.push(s.getRawData()); + seriesCollection.push(s); stateFields.push(this._field); if (this._valueField) { @@ -538,6 +642,7 @@ export abstract class DataFilterBaseComponent).getScale(); - const isContinuousScale = isContinuous(scale.type); - const domain = this._computeDomainOfStateScale(isContinuousScale); - - this._stateScale = scale.clone(); - if (isContinuousScale) { - const domainNum = domain.map((n: any) => n * 1); - this._stateScale - .domain(domain.length ? [minInArray(domainNum), maxInArray(domainNum)] : [0, 1], true) - .range(defaultRange); - } else { - this._stateScale.domain(domain, true).range(defaultRange); - } - } else { - this._stateScale = new BandScale(); - this._stateScale.domain(this._computeDomainOfStateScale(), true).range(defaultRange); - } - } - - init(option: IModelInitOption): void { - super.init(option); - // 添加 transform - this._addTransformToSeries(); - // 增加datazoom 数据统计 - // 只有在轴没有被设置数据时才有用 - // this.addZoomStatistics(); - - if (this._start !== 0 || this._end !== 1) { - this.effect.onZoomChange(); - } - } - protected _addTransformToSeries() { if (!this._relatedAxisComponent || this._filterMode !== 'axis') { registerDataSetInstanceTransform(this._option.dataSet, 'dataFilterWithNewDomain', dataFilterWithNewDomain); @@ -757,170 +706,134 @@ export abstract class DataFilterBaseComponent, prevSpec: AdaptiveSpec) { - const result = super._compareSpec(spec, prevSpec); - if (!result.reMake && !isEqual(prevSpec, spec)) { - result.reRender = true; - result.reMake = true; + onDataUpdate(): void { + const domain = this._computeDomainOfStateScale(isContinuous(this._stateScale.type)); + this._stateScale.domain(domain, false); + this._handleChange(this._start, this._end, true); + // auto 模式下需要重新布局 + if (this._spec.auto && !isEqual(this._domainCache, domain)) { + this._domainCache = domain; + this._dataUpdating = true; + this.getChart()?.setLayoutTag(true, null, false); } - - return result; } - - reInit(spec?: AdaptiveSpec) { - super.reInit(spec); - - this._marks.forEach(g => { - (g).getMarks().forEach(m => { - this.initMarkStyleWithSpec(m, (this._spec as any)[m.name]); - }); - }); - } - - protected _parseDomainFromState(startValue: number | string, endValue: number | string) { - if (isContinuous(this._stateScale.type)) { - return [Math.min(endValue as number, startValue as number), Math.max(endValue as number, startValue as number)]; - } - const allDomain = this._stateScale.domain(); - const startIndex = allDomain.indexOf(startValue); - const endIndex = allDomain.indexOf(endValue); - return allDomain.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex) + 1); + private _parseFieldOfSeries(s: ISeries) { + return this._originalStateFields?.[s.id]; } + /*** end: data change and reset view ***/ - protected _handleStateChange = (startValue: number, endValue: number, tag?: string) => { - this._startValue = startValue; - this._endValue = endValue; - - this._newDomain = this._parseDomainFromState(this._startValue, this._endValue); - - this.effect.onZoomChange?.(tag); - return true; - }; - - protected _handleChartZoom = ( - params: { zoomDelta: number; zoomX?: number; zoomY?: number }, - e?: BaseEventParams['event'] - ) => { - if (!this._activeRoam || (this._zoomAttr.filter && !this._zoomAttr.filter(params, e))) { - return; - } - - const { zoomDelta, zoomX, zoomY } = params; - const { x, y } = this._regions[0].getLayoutStartPoint(); - const { width, height } = this._regions[0].getLayoutRect(); + /*** start: scale of filter ***/ - const delta = Math.abs(this._start - this._end); - const zoomRate = (this._spec.roamZoom as IRoamZoomSpec)?.rate ?? 1; - // zoomDelta > 1表示放大, zoomDelta < 1表示缩小 - if (delta >= 1 && zoomDelta < 1) { - return; - } - if (delta <= 0.01 && zoomDelta > 1) { - return; - } - const focusLoc = this._isHorizontal ? zoomX : zoomY; - const totalValue = delta * (zoomDelta - 1) * zoomRate; - let startValue = totalValue / 2; - let endValue = totalValue / 2; - if (focusLoc) { - const startLoc = this._isHorizontal ? x : y; - const endLoc = this._isHorizontal ? width : height; - startValue = (Math.abs(startLoc - focusLoc) / Math.abs(endLoc - startLoc)) * totalValue; - endValue = (Math.abs(endLoc - focusLoc) / Math.abs(endLoc - startLoc)) * totalValue; - } - const start = clamp(this._start + startValue, 0, 1); - const end = clamp(this._end - endValue, 0, 1); - - this._handleChange(Math.min(start, end), Math.max(start, end), true); - }; + protected _setStateFromSpec() { + this._auto = !!this._spec.auto; - protected _handleChartScroll = (params: { scrollX: number; scrollY: number }, e: BaseEventParams['event']) => { - if (!this._activeRoam || (this._scrollAttr.filter && !this._scrollAttr.filter(params, e))) { - return false; - } - const { scrollX, scrollY } = params; - let value = this._isHorizontal ? scrollX : scrollY; - // 判断这次是否应该要滚动,最少 - const active = this._isHorizontal ? abs(scrollX / scrollY) >= 0.5 : abs(scrollY / scrollX) >= 0.5; - if (!this._scrollAttr.reverse) { - value = -value; + let start; + let end; + if (this._spec.rangeMode) { + const [startMode, endMode] = this._spec.rangeMode; + // 只有mode与配置相符时,才会生效 + // 比如rangeMode为['value', 'percent'],那么start为dataValue, end为[0, 1] + if (modeCheck('start', startMode, this._spec) && modeCheck('end', endMode, this._spec)) { + start = + startMode === 'percent' + ? this._spec.start + : dataToStatePoint(this._spec.startValue, this._stateScale, this._isHorizontal); + end = + endMode === 'percent' + ? this._spec.end + : dataToStatePoint(this._spec.endValue, this._stateScale, this._isHorizontal); + } + } else { + start = this._spec.start + ? this._spec.start + : this._spec.startValue + ? dataToStatePoint(this._spec.startValue, this._stateScale, this._isHorizontal) + : 0; + end = this._spec.end + ? this._spec.end + : this._spec.endValue + ? dataToStatePoint(this._spec.endValue, this._stateScale, this._isHorizontal) + : 1; } - - if (active) { - this._handleChartMove(value, this._scrollAttr.rate ?? 1); + this._start = Math.max(0, Math.min(1, start)); + this._end = Math.max(0, Math.min(1, end)); + } + protected _setStateFromAxis() { + // 此时 state scale 进行了更新,stateFromSpec 中补分逻辑需要使用scale计算,重新获取一遍 + this._setStateFromSpec(); + const axis = this._relatedAxisComponent as CartesianAxis; + this._startValue = statePointToData(this._start, this._stateScale, isReverse(axis, this._isHorizontal)); + this._endValue = statePointToData(this._end, this._stateScale, isReverse(axis, this._isHorizontal)); + this._minSpan = this._spec.minSpan ?? 0; + this._maxSpan = this._spec.maxSpan ?? 1; + if (isContinuous(this._stateScale.type) && this._stateScale.domain()[0] !== last(this._stateScale.domain())) { + if (this._spec.minValueSpan) { + this._minSpan = this._spec.minValueSpan / (last(this._stateScale.domain()) - this._stateScale.domain()[0]); + } + if (this._spec.maxValueSpan) { + this._maxSpan = this._spec.maxValueSpan / (last(this._stateScale.domain()) - this._stateScale.domain()[0]); + } } - - // 判断是否滚动到最顶部或最底部 - // 如果滚动到最顶部或最底部,则不应该stopBubble - const hasChange = this._start !== 0 && this._end !== 1; - - return active && hasChange; - }; - - protected _handleChartDrag = (delta: [number, number], e: BaseEventParams['event']) => { - if (!this._activeRoam || (this._dragAttr.filter && !this._dragAttr.filter(delta, e))) { + this._minSpan = Math.max(0, this._minSpan); + this._maxSpan = Math.min(this._maxSpan, 1); + if (!axis) { return; } - if ((this._spec.roamDrag as IRoamDragSpec)?.autoVisible) { - this.show(); - } - const [dx, dy] = delta; - let value = this._isHorizontal ? dx : dy; - if (this._dragAttr.reverse) { - value = -value; + // eslint-disable-next-line max-len + if ((!axis || this._filterMode !== 'axis') && (this._start !== 0 || this._end !== 1)) { + this._newDomain = parseDomainFromState(this._startValue, this._endValue, this._stateScale); } - this._handleChartMove(value, this._dragAttr.rate ?? 1); - }; - - protected _handleChartMove = (value: number, rate: number) => { - const totalValue = this._isHorizontal ? this.getLayoutRect().width : this.getLayoutRect().height; - if (Math.abs(value) >= 1e-6) { - if (value > 0 && this._end < 1) { - const moveDelta = Math.min(1 - this._end, value / totalValue) * rate; - this._handleChange(this._start + moveDelta, this._end + moveDelta, true); - } else if (value < 0 && this._start > 0) { - const moveDelta = Math.max(-this._start, value / totalValue) * rate; - this._handleChange(this._start + moveDelta, this._end + moveDelta, true); + } + protected _initStateScale() { + const defaultRange = [0, 1]; + if (this._relatedAxisComponent) { + const scale = (this._relatedAxisComponent as CartesianAxis).getScale().clone() as + | IContinuousScale + | IBandLikeScale; + // 需要清除rangeFactor 缓存,否则会导致计算错误 + this._stateScale = scale; + (scale as IBandLikeScale).maxBandwidth?.('auto', true); + (scale as IBandLikeScale).minBandwidth?.('auto', true); + (scale as IBandLikeScale).bandwidth?.('auto', true); + scale.rangeFactor(defaultRange as [number, number], true).range(defaultRange); + } else { + let fieldLinear = true; + if (this._field) { + eachSeries( + this._regions, + s => { + const stats = s.getRawDataStatisticsByField(this._field); + if (!isValidNumber(stats?.min) || !isValidNumber(stats?.max)) { + fieldLinear = false; + } + }, + { + userId: this._seriesUserId, + specIndex: this._seriesIndex + } + ); } - } - return false; - }; - protected _initCommonEvent() { - const delayType: IDelayType = this._spec?.delayType ?? 'throttle'; - const delayTime = isValid(this._spec?.delayType) ? (this._spec?.delayTime ?? 30) : 0; - const realTime = this._spec?.realTime ?? true; - const option = { delayType, delayTime, realTime, allowComponentZoom: true }; - if (this._zoomAttr.enable) { - (this as unknown as IZoomable).initZoomEventOfRegions(this._regions, null, this._handleChartZoom, option); - } - if (this._scrollAttr.enable) { - (this as unknown as IZoomable).initScrollEventOfRegions(this._regions, null, this._handleChartScroll, option); - } - if (this._dragAttr.enable) { - (this as unknown as IZoomable).initDragEventOfRegions(this._regions, null, this._handleChartDrag, option); - } - if ((this._spec.roamDrag as IRoamDragSpec)?.autoVisible) { - const dragEnd = 'panend'; - this._throttledHide = throttle(() => this.hide(), 300); - this.event.on(dragEnd, () => { - this._throttledHide(); - }); + this._stateScale = fieldLinear ? new LinearScale() : new BandScale(); + this._stateScale.domain(this._computeDomainOfStateScale(fieldLinear), true).range(defaultRange); } } - updateLayoutAttribute(): void { - // create or update component - if (this._visible) { - this._createOrUpdateComponent(); + protected _computeDomainOfStateScale(isContinuous?: boolean) { + if ((this._spec as IDataZoomSpec).customDomain) { + return (this._spec as IDataZoomSpec).customDomain; } - super.updateLayoutAttribute(); + const domain = this._data.getLatestData().map((d: any) => d[this._stateField]); + if (isContinuous) { + const domainNum = domain.map((n: any) => n * 1); + return domain.length ? [minInArray(domainNum), maxInArray(domainNum)] : [-Infinity, Infinity]; + } + + return domain; } + /*** end: scale of filter ***/ + /*** start: auto model process ***/ protected _autoVisible(isShown: boolean) { if (!this._auto) { return; @@ -940,39 +853,6 @@ export abstract class DataFilterBaseComponent; const axisSpec = axis?.getSpec() as ICartesianBandAxisSpec | undefined; const axisScale = axis?.getScale() as IBandLikeScale; - const bandSizeResult = this._getAxisBandSize(axisSpec); + const bandSizeResult = getAxisBandSize(axisSpec); if ( !this._dataUpdating && @@ -1047,14 +917,11 @@ export abstract class DataFilterBaseComponent void; + + protected getLayoutRect: () => ILayoutRect; + protected getState: () => { start: number; end: number }; + protected getRegions: () => IRegion[]; + protected getOption: () => IComponentOption; + protected getEvent: () => IEvent; + protected _isHorizontal: boolean; + protected _regions: IRegion[]; + + protected _activeRoam: boolean = true; + protected _zoomAttr: IRoamZoomSpec = { + enable: true, + rate: 1, + focus: true + }; + protected _dragAttr: IRoamDragSpec = { + enable: true, + rate: 1, + reverse: true + }; + protected _scrollAttr: IRoamScrollSpec = { + enable: true, + rate: 1, + reverse: true + }; + + enableInteraction() { + this._activeRoam = true; + } + disableInteraction() { + this._activeRoam = false; + } + zoomIn(location?: { x: number; y: number }) { + this.handleChartZoom({ + zoomDelta: 1.2, // 经验值 + zoomX: location?.x, + zoomY: location?.y + }); + } + + zoomOut(location?: { x: number; y: number }) { + this.handleChartZoom({ + zoomDelta: 0.8, // 经验值 + zoomX: location?.x, + zoomY: location?.y + }); + } + + constructor( + type: 'dataZoom' | 'scrollBar', + spec: IDataZoomSpec, + handleChange: (start: number, end: number, updateComponent?: boolean) => void, + getLayoutRect: () => ILayoutRect, + getState: () => { start: number; end: number }, + getRegions: () => IRegion[], + getOption: () => IComponentOption, + getEvent: () => IEvent + ) { + this._type = type; + this._spec = spec; + this._handleChange = handleChange; + this.getLayoutRect = getLayoutRect; + this.getState = getState; + this.getRegions = getRegions; + this._regions = getRegions(); + this.getOption = getOption; + this._option = getOption(); + this.getEvent = getEvent; + + this._isHorizontal = getDirectionByOrient(getOrient(spec as any)) === Direction.horizontal; + } + + setEventAttrFromSpec() { + if (this._spec.roamZoom === true || this._spec.roamZoom) { + this._zoomAttr = merge({}, this._zoomAttr, this._spec.roamZoom); + } else { + this._zoomAttr.enable = false; + } + + if (this._spec.roamDrag === true || this._spec.roamDrag) { + this._dragAttr = merge({}, this._dragAttr, this._spec.roamDrag); + } else { + this._dragAttr.enable = false; + } + + if (this._spec.roamScroll === true || this._spec.roamScroll) { + this._scrollAttr = merge({}, this._scrollAttr, this._spec.roamScroll); + } else { + this._scrollAttr.enable = false; + } + + if (isBoolean((this._spec as any).roam)) { + this._zoomAttr.enable = this._type === 'scrollBar' ? false : (this._spec as any).roam; + this._dragAttr.enable = (this._spec as any).roam; + this._scrollAttr.enable = (this._spec as any).roam; + } + + if (this._zoomAttr.enable || this._dragAttr.enable || this._scrollAttr.enable) { + (this as unknown as IZoomable).initZoomable(this.getEvent(), this._option.mode); + } + } + + initZoomEvent = () => { + const delayType: IDelayType = this._spec?.delayType ?? 'throttle'; + const delayTime = isValid(this._spec?.delayType) ? (this._spec?.delayTime ?? 30) : 0; + const realTime = this._spec?.realTime ?? true; + const option = { delayType, delayTime, realTime, allowComponentZoom: true }; + if (this._zoomAttr.enable) { + (this as unknown as IZoomable).initZoomEventOfRegions(this.getRegions(), null, this.handleChartZoom, option); + } + if (this._scrollAttr.enable) { + (this as unknown as IZoomable).initScrollEventOfRegions(this.getRegions(), null, this.handleChartScroll, option); + } + if (this._dragAttr.enable) { + (this as unknown as IZoomable).initDragEventOfRegions(this.getRegions(), null, this.handleChartDrag, option); + } + }; + + /// add event listener + handleChartZoom = (params: { zoomDelta: number; zoomX?: number; zoomY?: number }, e?: BaseEventParams['event']) => { + if (!this._activeRoam || (this._zoomAttr.filter && !this._zoomAttr.filter(params, e))) { + return; + } + + const { zoomDelta, zoomX, zoomY } = params; + const { x, y } = this.getRegions()[0].getLayoutStartPoint(); + const { width, height } = this.getRegions()[0].getLayoutRect(); + + const delta = Math.abs(this.getState().start - this.getState().end); + const zoomRate = (this._spec.roamZoom as IRoamZoomSpec)?.rate ?? 1; + // zoomDelta > 1表示放大, zoomDelta < 1表示缩小 + if (delta >= 1 && zoomDelta < 1) { + return; + } + if (delta <= 0.01 && zoomDelta > 1) { + return; + } + const focusLoc = this._isHorizontal ? zoomX : zoomY; + const totalValue = delta * (zoomDelta - 1) * zoomRate; + let startValue = totalValue / 2; + let endValue = totalValue / 2; + if (focusLoc) { + const startLoc = this._isHorizontal ? x : y; + const endLoc = this._isHorizontal ? width : height; + startValue = (Math.abs(startLoc - focusLoc) / Math.abs(endLoc - startLoc)) * totalValue; + endValue = (Math.abs(endLoc - focusLoc) / Math.abs(endLoc - startLoc)) * totalValue; + } + const start = clamp(this.getState().start + startValue, 0, 1); + const end = clamp(this.getState().end - endValue, 0, 1); + + this._handleChange(Math.min(start, end), Math.max(start, end), true); + }; + + handleChartScroll = (params: { scrollX: number; scrollY: number }, e: BaseEventParams['event']) => { + if (!this._activeRoam || (this._scrollAttr.filter && !this._scrollAttr.filter(params, e))) { + return false; + } + const { scrollX, scrollY } = params; + let value = this._isHorizontal ? scrollX : scrollY; + // 判断这次是否应该要滚动,最少 + const active = this._isHorizontal ? abs(scrollX / scrollY) >= 0.5 : abs(scrollY / scrollX) >= 0.5; + if (!this._scrollAttr.reverse) { + value = -value; + } + + if (active) { + this.handleChartMove(value, this._scrollAttr.rate ?? 1); + } + + // 判断是否滚动到最顶部或最底部 + // 如果滚动到最顶部或最底部,则不应该stopBubble + const hasChange = this.getState().start !== 0 && this.getState().end !== 1; + + return active && hasChange; + }; + + handleChartDrag = (delta: [number, number], e: BaseEventParams['event']) => { + if (!this._activeRoam || (this._dragAttr.filter && !this._dragAttr.filter(delta, e))) { + return; + } + const [dx, dy] = delta; + let value = this._isHorizontal ? dx : dy; + if (this._dragAttr.reverse) { + value = -value; + } + this.handleChartMove(value, this._dragAttr.rate ?? 1); + }; + + handleChartMove = (value: number, rate: number) => { + const totalValue = this._isHorizontal ? this.getLayoutRect().width : this.getLayoutRect().height; + if (Math.abs(value) >= 1e-6) { + if (value > 0 && this.getState().end < 1) { + const moveDelta = Math.min(1 - this.getState().end, value / totalValue) * rate; + this._handleChange(this.getState().start + moveDelta, this.getState().end + moveDelta, true); + } else if (value < 0 && this.getState().start > 0) { + const moveDelta = Math.max(-this.getState().start, value / totalValue) * rate; + this._handleChange(this.getState().start + moveDelta, this.getState().end + moveDelta, true); + } + } + return false; + }; +} + +mixin(DataFilterEvent, Zoomable); diff --git a/packages/vchart/src/component/data-zoom/data-zoom/data-zoom.ts b/packages/vchart/src/component/data-zoom/data-zoom/data-zoom.ts index 6dda3d667e..74be85dae7 100644 --- a/packages/vchart/src/component/data-zoom/data-zoom/data-zoom.ts +++ b/packages/vchart/src/component/data-zoom/data-zoom/data-zoom.ts @@ -1,6 +1,4 @@ -// eslint-disable-next-line no-duplicate-imports import { - isBoolean, isFunction, isNil, isNumber, @@ -8,32 +6,27 @@ import { last, maxInArray, minInArray, - uniqArray + uniqArray, + type IBoundsLike } from '@visactor/vutils'; import { mergeSpec } from '@visactor/vutils-extension'; -import type { IComponentOption } from '../../interface'; -// eslint-disable-next-line no-duplicate-imports -import { ComponentTypeEnum } from '../../interface/type'; +import { ComponentTypeEnum, type IComponentOption } from '../../interface'; import { DataFilterBaseComponent } from '../data-filter-base-component'; -// eslint-disable-next-line no-duplicate-imports -import type { DataZoomAttributes } from '@visactor/vrender-components'; -// eslint-disable-next-line no-duplicate-imports -import { DataZoom as DataZoomComponent } from '@visactor/vrender-components'; +import { DataZoom as DataZoomComponent, type DataZoomAttributes } from '@visactor/vrender-components'; import { transformToGraphic } from '../../../util/style'; import type { IRectGraphicAttribute, INode, ISymbolGraphicAttribute, IGroup, IGraphic } from '@visactor/vrender-core'; -import type { Datum, ILayoutType } from '../../../typings'; -import type { ILinearScale, IBaseScale } from '@visactor/vscale'; -// eslint-disable-next-line no-duplicate-imports -import { LinearScale, isContinuous, isDiscrete } from '@visactor/vscale'; +import type { Datum, ILayoutRect, ILayoutType } from '../../../typings'; +import { LinearScale, isContinuous, isDiscrete, type ILinearScale, type IBaseScale } from '@visactor/vscale'; import { LayoutLevel, LayoutZIndex } from '../../../constant/layout'; import { ChartEvent } from '../../../constant/event'; import type { IDataZoomSpec } from './interface'; import { Factory } from '../../../core/factory'; -import type { IZoomable } from '../../../interaction/zoom'; import type { CartesianAxis } from '../../axis/cartesian'; import { DataZoomSpecTransformer } from './data-zoom-transformer'; import { getFormatFunction } from '../../util'; import { dataZoom } from '../../../theme/builtin/common/component/data-zoom'; +import { isReverse, statePointToData } from '../util'; +import { LayoutModel } from '../../../model/layout-model'; export class DataZoom extends DataFilterBaseComponent { static type = ComponentTypeEnum.dataZoom; @@ -64,6 +57,10 @@ export class DataZoom extends DataFilte protected _isReverseCache: boolean = false; + protected _cacheRect?: ILayoutRect; + + protected _previewStateScale: IBaseScale; + constructor(spec: T, options: IComponentOption) { super(spec, options); @@ -71,60 +68,158 @@ export class DataZoom extends DataFilte this._filterMode = spec.filterMode ?? 'filter'; } + /*** start: init event and event dispatch ***/ + protected _handleChange(start: number, end: number, updateComponent?: boolean, tag?: string) { + super._handleChange(start, end, updateComponent); + + if (this._shouldChange) { + if (updateComponent && this._component) { + this._component.setStartAndEnd(start, end); + } else { + const axis = this._relatedAxisComponent as CartesianAxis; + + const startValue = statePointToData(start, this._stateScale, isReverse(axis, this._isHorizontal)); + const endValue = statePointToData(end, this._stateScale, isReverse(axis, this._isHorizontal)); + if (!isValid(startValue) || !isValid(endValue)) { + return; + } + this._start = start; + this._end = end; + const hasChange = isFunction(this._spec.updateDataAfterChange) + ? this._spec.updateDataAfterChange(start, end, startValue, endValue) + : this._handleStateChange(startValue, endValue, tag); + if (hasChange) { + this.event.emit(ChartEvent.dataZoomChange, { + model: this, + value: { + filterData: this._filterMode !== 'axis', + start, + end, + startValue: this._startValue, + endValue: this._endValue, + newDomain: this._newDomain + } + }); + } + } + } + } + + protected _handleDataCollectionChange() { + const data = this._data.getDataView(); + data.reRunAllTransform(); + + const domain = this._computeDomainOfValueScale(); + + if (domain) { + if (!this._valueScale) { + this._valueScale = new LinearScale(); + } + this._valueScale.domain(domain); + this._updateValueScaleRange(); + if (this._component) { + this._createOrUpdateComponent(true); + } + } + } + /*** end: init event and event dispatch ***/ + + /*** start: component lifecycle ***/ created() { super.created(); this._initValueScale(); } - setAttrFromSpec() { - super.setAttrFromSpec(); + updateLayoutAttribute(): void { + if (this._cacheVisibility !== false) { + super.updateLayoutAttribute(); + } + } + + protected _beforeLayoutEnd() { + super._beforeLayoutEnd(); + const axis = this._relatedAxisComponent as CartesianAxis; + // 初始时reverse判断并不准确,导致start和end颠倒, 保险起见在layoutend之后触发该逻辑 + // FIXME: 牺牲了一定性能,有待优化 + if ((isReverse(axis, this._isHorizontal) && !this._isReverseCache) || this._auto) { + // auto则代表需要根据bandsize同步更新范围 + this._isReverseCache = isReverse(axis, this._isHorizontal); + this.effect.onZoomChange(); + } + } + + clear(): void { + if (this._component) { + const container = this.getContainer(); + this._component.removeAllChild(); + if (container) { + container.removeChild(this._component as unknown as INode); + } - if (isBoolean((this._spec as any).roam)) { - this._zoomAttr.enable = (this._spec as any).roam; - this._dragAttr.enable = (this._spec as any).roam; - this._scrollAttr.enable = (this._spec as any).roam; + this._component = null; } + super.clear(); + } - if (this._zoomAttr.enable || this._dragAttr.enable || this._scrollAttr.enable) { - (this as unknown as IZoomable).initZoomable(this.event, this._option.mode); + getBoundsInRect(rect: ILayoutRect): IBoundsLike { + const result: IBoundsLike = { x1: this.getLayoutStartPoint().x, y1: this.getLayoutStartPoint().y, x2: 0, y2: 0 }; + const startHandlerScaleXSize = this._startHandlerSize * (this._spec.startHandler.style.scaleX ?? 1); + const startHandlerScaleYSize = this._startHandlerSize * (this._spec.startHandler.style.scaleY ?? 1); + const endHandlerScaleXSize = this._endHandlerSize * (this._spec.endHandler.style.scaleX ?? 1); + const endHandlerScaleYSize = this._endHandlerSize * (this._spec.endHandler.style.scaleY ?? 1); + const extendWidth = !this._visible + ? 0 + : this._isHorizontal + ? (startHandlerScaleXSize - this._startHandlerSize) / 2 + (endHandlerScaleXSize - this._endHandlerSize) / 2 + : (Math.max(startHandlerScaleXSize, endHandlerScaleXSize) - this._width) / 2; + const extendHeight = !this._visible + ? 0 + : this._isHorizontal + ? (Math.max(startHandlerScaleYSize, endHandlerScaleYSize) - this._height) / 2 + : (startHandlerScaleYSize - this._startHandlerSize) / 2 + (endHandlerScaleYSize - this._endHandlerSize) / 2; + if (this._isHorizontal) { + result.y2 = result.y1 + this._height + extendHeight; + result.x2 = result.x1 + rect.width + extendWidth; + } else { + result.x2 = result.x1 + this._width + extendWidth; + result.y2 = result.y1 + rect.height + extendHeight; } + return result; + } + /*** end: component lifecycle ***/ + + /*** start: set attributes & bind related axis and region ***/ + setAttrFromSpec() { + super.setAttrFromSpec(); + + // 用户配置, 如:80 -> 给各个部分赋予默认值 back 80, handler 80 -> 根据实际值computeSize -> 作为组件属性输入 -> 组件根据实际高度给背景分配默认高度 + // 用户不配置 -> 各部分给默认定值 -> ...(同上) + const componentSize = this._isHorizontal ? Number(this._spec.height) : Number(this._spec.width); // size相关 this._backgroundSize = this._spec.background?.size ?? 30; this._middleHandlerSize = this._computeMiddleHandlerSize(); - this._width = this._computeWidth(); - this._height = this._computeHeight(); // startHandler和endHandler size如果没有配置,则默认跟随background宽 or 高 if (isNil(this._spec?.startHandler?.style?.size)) { - this._spec.startHandler.style.size = this._isHorizontal - ? this._height - this._middleHandlerSize - : this._width - this._middleHandlerSize; + this._spec.startHandler.style.size = isNaN(componentSize) + ? this._backgroundSize + : componentSize - this._middleHandlerSize; } if (isNil(this._spec?.endHandler?.style?.size)) { - this._spec.endHandler.style.size = this._isHorizontal - ? this._height - this._middleHandlerSize - : this._width - this._middleHandlerSize; - } - const startHandlerVisble = this._spec.startHandler.style.visible ?? true; - const endHandlerVisble = this._spec.endHandler.style.visible ?? true; - this._startHandlerSize = startHandlerVisble ? this._spec.startHandler.style.size : 0; - this._endHandlerSize = endHandlerVisble ? this._spec.endHandler.style.size : 0; - } - - /** LifeCycle API**/ - onLayoutEnd(): void { - this._updateScaleRange(); - // 初始时reverse判断并不准确,导致start和end颠倒, 保险起见在layoutend之后触发该逻辑 - // FIXME: 牺牲了一定性能,有待优化 - if (this._isReverse() && !this._isReverseCache) { - this._isReverseCache = this._isReverse(); - this.effect.onZoomChange(); - } - if (this._cacheVisibility !== false) { - super.onLayoutEnd(); + this._spec.endHandler.style.size = isNaN(componentSize) + ? this._backgroundSize + : componentSize - this._middleHandlerSize; } + const startHandlerVisible = this._spec.startHandler.style.visible ?? true; + const endHandlerVisible = this._spec.endHandler.style.visible ?? true; + this._startHandlerSize = startHandlerVisible ? this._spec.startHandler.style.size : 0; + this._endHandlerSize = endHandlerVisible ? this._spec.endHandler.style.size : 0; + this._width = this._computeWidth(); + this._height = this._computeHeight(); } + /*** end: set attributes & bind related axis and region ***/ + /*** start: scale of background chart ***/ protected _initValueScale() { const domain = this._computeDomainOfValueScale(); @@ -136,8 +231,13 @@ export class DataZoom extends DataFilte } protected _updateScaleRange() { + this._updateStateScaleRange(); + this._updateValueScaleRange(); + } + + protected _updateStateScaleRange() { const handlerSize = this._startHandlerSize + this._endHandlerSize; - if (!this._stateScale || !this._valueScale) { + if (!this._stateScale) { return; } @@ -159,33 +259,35 @@ export class DataZoom extends DataFilte stateScaleRange = this._visible ? [this._startHandlerSize / 2, compWidth - handlerSize + this._startHandlerSize / 2] : defaultRange; - this._stateScale.range(stateScaleRange); - this._valueScale.range([compHeight - this._middleHandlerSize, 0]); } else { stateScaleRange = this._visible ? [this._startHandlerSize / 2, compHeight - handlerSize + this._startHandlerSize / 2] : defaultRange; + } + this._stateScale.range(stateScaleRange); + this._previewStateScale?.range( + isReverse(this._relatedAxisComponent as CartesianAxis, this._isHorizontal) + ? stateScaleRange.reverse() + : stateScaleRange + ); + } - this._stateScale.range(stateScaleRange); + protected _updateValueScaleRange() { + if (!this._valueScale) { + return; + } + const compWidth = this._computeWidth(); + const compHeight = this._computeHeight(); + if (this._isHorizontal) { + this._valueScale.range([compHeight - this._middleHandlerSize, 0]); + } else { if (this.layoutOrient === 'left') { this._valueScale.range([compWidth - this._middleHandlerSize, 0]); } else { this._valueScale.range([0, compWidth - this._middleHandlerSize]); } } - if (this._component && this._cacheVisibility !== false) { - this._component.setAttributes({ - size: { - width: compWidth, - height: compHeight - }, - position: { - x: this.getLayoutStartPoint().x, - y: this.getLayoutStartPoint().y - } - }); - } } protected _computeDomainOfValueScale() { @@ -195,47 +297,6 @@ export class DataZoom extends DataFilte return domain.length ? [minInArray(domainNum), maxInArray(domainNum)] : null; } - protected _computeMiddleHandlerSize(): number { - let size = 0; - if (this._spec?.middleHandler?.visible) { - const middleHandlerIconSize = this._spec.middleHandler.icon.style.size ?? 8; - const middleHandlerBackSize = this._spec.middleHandler.background.size ?? 40; - size += Math.max(middleHandlerIconSize as number, middleHandlerBackSize); - } - return size; - } - - protected _computeWidth(): number { - if (this._visible === false) { - return 0; - } - - if (isNumber(this._spec.width)) { - return this._spec.width; - } - - if (this._isHorizontal) { - return this.getLayoutRect().width; - } - - return this._backgroundSize + this._middleHandlerSize; - } - - protected _computeHeight(): number { - if (this._visible === false) { - return 0; - } - - if (isNumber(this._spec.height)) { - return this._spec.height; - } - - if (this._isHorizontal) { - return this._backgroundSize + this._middleHandlerSize; - } - return this.getLayoutRect().height; - } - protected _isScaleValid(scale: IBaseScale | ILinearScale) { if (!scale || !scale.domain()) { return false; @@ -250,8 +311,20 @@ export class DataZoom extends DataFilte return true; } + protected _getXScale() { + const bindScale = (this._relatedAxisComponent as CartesianAxis).getScale(); + if (bindScale.type === this.stateScale.type && this._isHorizontal) { + return this.stateScale; + } + return this._isHorizontal ? this._stateScale : this._valueScale; + } + + protected _getYScale() { + return this._isHorizontal ? this._valueScale : this._stateScale; + } + protected _dataToPositionX = (datum: Datum): number => { - const offsetLeft = this._orient === 'left' ? this._middleHandlerSize : 0; + const offsetLeft = !this._isHorizontal ? this._middleHandlerSize : 0; const offsetHandler = this._isHorizontal ? this._startHandlerSize / 2 : 0; const xScale = this._isHorizontal ? this._stateScale : this._valueScale; const xField = this._isHorizontal ? this._stateField : this._valueField; @@ -259,7 +332,7 @@ export class DataZoom extends DataFilte }; protected _dataToPositionX2 = (datum: Datum): number => { - const offsetLeft = this._orient === 'left' ? this._middleHandlerSize : 0; + const offsetLeft = !this._isHorizontal ? this._middleHandlerSize : 0; const offsetHandler = this._isHorizontal ? this._startHandlerSize / 2 : 0; const xScale = this._isHorizontal ? this._stateScale : this._valueScale; const min = xScale.domain()[0]; @@ -269,7 +342,7 @@ export class DataZoom extends DataFilte protected _dataToPositionY = (datum: Datum): number => { const offsetTop = this._isHorizontal ? this._middleHandlerSize : 0; const offsetHandler = this._isHorizontal ? 0 : this._startHandlerSize / 2; - const yScale = this._isHorizontal ? this._valueScale : this._stateScale; + const yScale = this._isHorizontal ? this._valueScale : this._getPreviewStateScale(); const yField = this._isHorizontal ? this._valueField : this._stateField; return yScale.scale(datum[yField]) + this.getLayoutStartPoint().y + offsetTop + offsetHandler; }; @@ -277,11 +350,62 @@ export class DataZoom extends DataFilte protected _dataToPositionY2 = (datum: Datum): number => { const offsetTop = this._isHorizontal ? this._middleHandlerSize : 0; const offsetHandler = this._isHorizontal ? 0 : this._startHandlerSize / 2; - const yScale = this._isHorizontal ? this._valueScale : this._stateScale; + const yScale = this._isHorizontal ? this._valueScale : this._getPreviewStateScale(); const min = yScale.domain()[0]; return yScale.scale(min) + this.getLayoutStartPoint().y + offsetTop + offsetHandler; }; + /*** end: scale of background chart ***/ + + /** start: component layout attr ***/ + protected _computeMiddleHandlerSize(): number { + let size = 0; + if (this._spec?.middleHandler?.visible) { + const middleHandlerIconSize = this._spec.middleHandler.icon.style.size ?? 8; + const middleHandlerBackSize = this._spec.middleHandler.background.size ?? 40; + size += Math.max(middleHandlerIconSize as number, middleHandlerBackSize); + } + return size; + } + + protected _computeWidth(): number { + if (this._visible === false) { + return 0; + } + + if (isNumber(this._spec.width)) { + return this._spec.width; + } + + if (this._isHorizontal) { + return this.getLayoutRect().width; + } + + return ( + Math.max(this._startHandlerSize || 0, this._endHandlerSize || 0, this._backgroundSize || 0) + + this._middleHandlerSize + ); + } + + protected _computeHeight(): number { + if (this._visible === false) { + return 0; + } + + if (isNumber(this._spec.height)) { + return this._spec.height; + } + + if (this._isHorizontal) { + return ( + Math.max(this._startHandlerSize || 0, this._endHandlerSize || 0, this._backgroundSize || 0) + + this._middleHandlerSize + ); + } + return this.getLayoutRect().height; + } + /** end: component layout attr ***/ + /** start: datazoom component attr ***/ private _getAttrs(isNeedPreview: boolean) { const spec = this._spec ?? ({} as T); return { @@ -303,17 +427,31 @@ export class DataZoom extends DataFilte minSpan: this._minSpan, maxSpan: this._maxSpan, delayType: spec.delayType, - delayTime: isValid(spec.delayType) ? spec.delayTime ?? 30 : 0, + delayTime: isValid(spec.delayType) ? (spec.delayTime ?? 30) : 0, realTime: spec.realTime ?? true, previewData: isNeedPreview && this._data.getLatestData(), previewPointsX: isNeedPreview && this._dataToPositionX, previewPointsY: isNeedPreview && this._dataToPositionY, tolerance: this._spec.tolerance, + isReverse: isReverse(this._relatedAxisComponent as CartesianAxis, this._isHorizontal), ...(this._getComponentAttrs(isNeedPreview) as any) } as DataZoomAttributes; } - protected _createOrUpdateComponent() { + private _getLayoutAttrs() { + return { + position: { + x: this.getLayoutStartPoint().x, + y: this.getLayoutStartPoint().y + }, + size: { + width: this._computeWidth(), + height: this._computeHeight() + } + }; + } + + protected _createOrUpdateComponent(changeData?: boolean) { if (this._visible) { const xScale = this._isHorizontal ? this._stateScale : this._valueScale; const yScale = this._isHorizontal ? this._valueScale : this._stateScale; @@ -321,71 +459,42 @@ export class DataZoom extends DataFilte this._isScaleValid(xScale) && this._isScaleValid(yScale) && this._spec.showBackgroundChart !== false; const attrs = this._getAttrs(isNeedPreview); + const axis = this._relatedAxisComponent as CartesianAxis; + if (this._component) { this._component.setAttributes(attrs); + if (changeData) { + this._component.setPreviewData(this._data.getDataView().latestData); + if (isNeedPreview) { + if (this._isHorizontal) { + this._component.setPreviewPointsY1(this._dataToPositionY2); + } else { + this._component.setPreviewPointsX1(this._dataToPositionX2); + } + this._component.setStatePointToData((state: number) => + statePointToData(state, this._stateScale, isReverse(axis, this._isHorizontal)) + ); + } + } } else { const container = this.getContainer(); this._component = new DataZoomComponent(attrs); + this._component.setPreviewData(this._data.getDataView().latestData); if (this._isHorizontal) { isNeedPreview && this._component.setPreviewPointsY1(this._dataToPositionY2); } else { isNeedPreview && this._component.setPreviewPointsX1(this._dataToPositionX2); } - this._component.setStatePointToData((state: number) => this.statePointToData(state)); + this._component.setStatePointToData((state: number) => + statePointToData(state, this._stateScale, isReverse(axis, this._isHorizontal)) + ); - this._component.addEventListener('change', (e: any) => { + this._component.addEventListener('dataZoomChange', (e: any) => { const { start, end, tag } = e.detail; this._handleChange(start, end, undefined, tag); }); container.add(this._component as unknown as INode); - - this._updateScaleRange(); - } - } - } - - protected _handleChange(start: number, end: number, updateComponent?: boolean, tag?: string) { - super._handleChange(start, end, updateComponent); - - if (this._shouldChange) { - if (updateComponent && this._component) { - this._component.setStartAndEnd(start, end); - } - - this._start = start; - this._end = end; - const startValue = this.statePointToData(start); - const endValue = this.statePointToData(end); - const hasChange = isFunction(this._spec.updateDataAfterChange) - ? this._spec.updateDataAfterChange(start, end, startValue, endValue) - : this._handleStateChange(startValue, endValue, tag); - if (hasChange) { - this.event.emit(ChartEvent.dataZoomChange, { - model: this, - value: { - filterData: this._filterMode !== 'axis', - start, - end, - startValue: this._startValue, - endValue: this._endValue, - newDomain: this._newDomain - } - }); - } - } - } - - protected _handleDataCollectionChange() { - const data = this._data.getDataView(); - data.reRunAllTransform(); - this._component?.setPreviewData(data.latestData); - - if (this._valueScale) { - const domain = this._computeDomainOfValueScale(); - - if (domain) { - this._valueScale.domain(domain); } } } @@ -461,22 +570,31 @@ export class DataZoom extends DataFilte const { formatFunc } = getFormatFunction(formatMethod, formatter); return formatFunc ? (text: any) => formatFunc(text, { label: text }, formatter) : undefined; } + /** end: datazoom component attr ***/ protected _getNeedClearVRenderComponents(): IGraphic[] { return [this._component] as unknown as IGroup[]; } - clear(): void { - if (this._component) { - const container = this.getContainer(); - this._component.removeAllChild(); - if (container) { - container.removeChild(this._component as unknown as INode); - } + onDataUpdate() { + super.onDataUpdate(); + // 预览scale + if (this._previewStateScale !== this._stateScale) { + this._previewStateScale = null; + } + } - this._component = null; + protected _getPreviewStateScale() { + if (!this._previewStateScale) { + // 当轴inverse时,需要反转预览scale + if (isReverse(this._relatedAxisComponent as CartesianAxis, this._isHorizontal)) { + this._previewStateScale = this._stateScale.clone(); + this._previewStateScale.range(this._stateScale.range().reverse()); + } else { + this._previewStateScale = this._stateScale; + } } - super.clear(); + return this._previewStateScale; } } diff --git a/packages/vchart/src/component/data-zoom/scroll-bar/scroll-bar.ts b/packages/vchart/src/component/data-zoom/scroll-bar/scroll-bar.ts index 7f460d7538..f918273b4e 100644 --- a/packages/vchart/src/component/data-zoom/scroll-bar/scroll-bar.ts +++ b/packages/vchart/src/component/data-zoom/scroll-bar/scroll-bar.ts @@ -1,4 +1,4 @@ -import { isBoolean, isEmpty, isFunction, isNil, isNumber, isValid } from '@visactor/vutils'; +import { isEmpty, isFunction, isNil, isNumber, isValid } from '@visactor/vutils'; import type { IComponentOption } from '../../interface'; // eslint-disable-next-line no-duplicate-imports import { ComponentTypeEnum } from '../../interface/type'; @@ -13,10 +13,10 @@ import { ChartEvent } from '../../../constant/event'; import { SCROLL_BAR_DEFAULT_SIZE } from '../../../constant/scroll-bar'; import type { IScrollBarSpec } from './interface'; import { Factory } from '../../../core/factory'; -import type { IZoomable } from '../../../interaction/zoom'; import type { ILayoutType } from '../../../typings/layout'; import { isClose } from '../../../util'; import { scrollBar } from '../../../theme/builtin/common/component/scroll-bar'; +import { statePointToData } from '../util'; // import { SCROLLBAR_EVENT, SCROLLBAR_END_EVENT } from '@visactor/vrender-components/es/constant'; // 由vrender透出, 接入新版本后需修改 @@ -38,7 +38,7 @@ export class ScrollBar extends DataFi layoutLevel: number = LayoutLevel.DataZoom; layoutType: ILayoutType = 'region-relative'; - // datazoom组件 + // scrollbar组件 protected _component!: ScrollBarComponent; constructor(spec: T, options: IComponentOption) { @@ -46,27 +46,68 @@ export class ScrollBar extends DataFi this._filterMode = spec.filterMode ?? 'axis'; } - setAttrFromSpec() { - super.setAttrFromSpec(); - // roam兼容逻辑 - if (isBoolean((this._spec as any).roam)) { - this._zoomAttr.enable = false; // 对于之前的逻辑而言,只要配置了roam,zoom始终不打开 - this._dragAttr.enable = (this._spec as any).roam; - this._scrollAttr.enable = (this._spec as any).roam; + /*** start: init event and event dispatch ***/ + protected _handleChange(start: number, end: number, updateComponent?: boolean) { + super._handleChange(start, end, updateComponent); + // filter out scroll event with same scroll value + const isSameScrollValue = isClose(this._start, start) && isClose(this._end, end); + // realTime为false时,start和end始终没有变化过, 但是需要触发change事件 + if (this._shouldChange && (!isSameScrollValue || this._spec.realTime === false)) { + if (updateComponent && this._component) { + this._component.setAttribute('range', [start, end]); + } + + this._start = start; + this._end = end; + const startValue = statePointToData(start, this._stateScale, false); + const endValue = statePointToData(end, this._stateScale, false); + const hasChange = isFunction(this._spec.updateDataAfterChange) + ? this._spec.updateDataAfterChange(start, end, startValue, endValue) + : this._handleStateChange(startValue, endValue); + if (hasChange) { + this.event.emit(ChartEvent.scrollBarChange, { + model: this, + value: { + filterData: this._filterMode !== 'axis', + start: this._start, + end: this._end, + startValue: this._startValue, + endValue: this._endValue, + newDomain: this._newDomain + } + }); + } } - if (this._zoomAttr.enable || this._dragAttr.enable || this._scrollAttr.enable) { - (this as unknown as IZoomable).initZoomable(this.event, this._option.mode); + } + + protected _handleDataCollectionChange() { + if (this._spec.auto) { + const data = this._data.getDataView(); + data.reRunAllTransform(); } } + /*** end: init event and event dispatch ***/ - /** LifeCycle API**/ - onLayoutEnd(): void { - this._updateScaleRange(); + /*** start: component lifecycle ***/ + + protected _beforeLayoutEnd() { + super._beforeLayoutEnd(); this.effect.onZoomChange?.(); + } + + onLayoutEnd(): void { + // 保证自己的宽高正确 + this._updateComponentBounds(); super.onLayoutEnd(); } + /*** end: component lifecycle ***/ + /*** start: scale ***/ protected _updateScaleRange() { + // do nothing + } + + protected _updateComponentBounds() { if (this._component) { this._component.setAttributes({ x: this.getLayoutStartPoint().x, @@ -77,7 +118,13 @@ export class ScrollBar extends DataFi } } + /*** end: scale ***/ + + /** start: component layout attr ***/ protected _computeWidth(): number { + if (this._visible === false) { + return 0; + } if (isNumber(this._spec.width)) { return this._spec.width; } @@ -90,6 +137,9 @@ export class ScrollBar extends DataFi } protected _computeHeight(): number { + if (this._visible === false) { + return 0; + } if (isNumber(this._spec.height)) { return this._spec.height; } @@ -101,7 +151,9 @@ export class ScrollBar extends DataFi return SCROLL_BAR_DEFAULT_SIZE; } + /** end: component layout attr ***/ + /** start: scrollbar component attr ***/ private _getAttrs() { return { zIndex: this.layoutZIndex, @@ -112,7 +164,7 @@ export class ScrollBar extends DataFi range: [this._start, this._end], direction: this._isHorizontal ? 'horizontal' : 'vertical', delayType: this._spec?.delayType, - delayTime: isValid(this._spec?.delayType) ? this._spec?.delayTime ?? 30 : 0, + delayTime: isValid(this._spec?.delayType) ? (this._spec?.delayTime ?? 30) : 0, realTime: this._spec?.realTime ?? true, ...this._getComponentAttrs() } as ScrollBarAttributes; @@ -138,46 +190,6 @@ export class ScrollBar extends DataFi } } - protected _handleChange(start: number, end: number, updateComponent?: boolean) { - super._handleChange(start, end, updateComponent); - // filter out scroll event with same scroll value - const isSameScrollValue = isClose(this._start, start) && isClose(this._end, end); - // realTime为false时,start和end始终没有变化过, 但是需要触发change事件 - if (this._shouldChange && (!isSameScrollValue || this._spec.realTime === false)) { - if (updateComponent && this._component) { - this._component.setAttribute('range', [start, end]); - } - - this._start = start; - this._end = end; - const startValue = this.statePointToData(start); - const endValue = this.statePointToData(end); - const hasChange = isFunction(this._spec.updateDataAfterChange) - ? this._spec.updateDataAfterChange(start, end, startValue, endValue) - : this._handleStateChange(this.statePointToData(start), this.statePointToData(end)); - if (hasChange) { - this.event.emit(ChartEvent.scrollBarChange, { - model: this, - value: { - filterData: this._filterMode !== 'axis', - start: this._start, - end: this._end, - startValue: this._startValue, - endValue: this._endValue, - newDomain: this._newDomain - } - }); - } - } - } - - protected _handleDataCollectionChange() { - if (this._spec.auto) { - const data = this._data.getDataView(); - data.reRunAllTransform(); - } - } - protected _getComponentAttrs() { const { rail, slider, innerPadding } = this._spec; const attrs: Partial = {}; @@ -195,6 +207,7 @@ export class ScrollBar extends DataFi attrs.disableTriggerEvent = this._option.disableTriggerEvent; return attrs; } + /** end: scrollbar component attr ***/ protected _getNeedClearVRenderComponents(): IGraphic[] { return [this._component] as unknown as IGroup[]; diff --git a/packages/vchart/src/component/data-zoom/util.ts b/packages/vchart/src/component/data-zoom/util.ts index 81d16cf54c..92438100a8 100644 --- a/packages/vchart/src/component/data-zoom/util.ts +++ b/packages/vchart/src/component/data-zoom/util.ts @@ -1,7 +1,9 @@ -import { isArray, last } from '@visactor/vutils'; +import { isArray, isValid, last } from '@visactor/vutils'; import { array, isNil } from '../../util'; import type { DataView } from '@visactor/vdataset'; - +import type { IBandLikeScale, IBaseScale } from '@visactor/vscale'; +import { isContinuous } from '@visactor/vscale'; +import type { CartesianAxis, ICartesianBandAxisSpec } from '../axis/cartesian'; export interface IDataFilterWithNewDomainOption { getNewDomain: () => any[]; isContinuous: () => boolean; @@ -94,6 +96,7 @@ export interface IDataFilterComputeDomainOption { stateFields: string[]; valueFields: string[]; isCategoryState?: boolean; + seriesCollection: any[]; method: 'sum'; // todo: 也许可以提供多种数据统计方法 @chensiji }; output: { @@ -103,17 +106,25 @@ export interface IDataFilterComputeDomainOption { } export const dataFilterComputeDomain = (data: Array, op: IDataFilterComputeDomainOption) => { - const { stateFields, valueFields, dataCollection, isCategoryState } = op.input; + const { stateFields, valueFields, dataCollection, isCategoryState, seriesCollection } = op.input; const { stateField, valueField } = op.output; const resultObj: any = {}; + const resultKeys: any[] = []; const resultData: any[] = []; const stateValues: any[] = []; let hasLockDomain = false; - + let isAllLinearValue = false; dataCollection.forEach((dv: DataView, i) => { if (isNil(stateFields[i])) { return; } + const series = seriesCollection[i]; + if (series) { + const statistics = series.getRawDataStatisticsByField(stateFields[i]); + if (isValid(statistics?.max) && isValid(statistics?.min)) { + isAllLinearValue = true; + } + } // 按照用户指定的domain进行排序(这里不通过getRawDataStatistics来取是因为时机不对,此时getRawDataStatistics还没有正确结果) const stateFieldInfo = dv.getFields()?.[stateFields[i]]; if (stateFieldInfo && stateFieldInfo.lockStatisticsByDomain) { @@ -122,6 +133,7 @@ export const dataFilterComputeDomain = (data: Array, op: IDataFilterCompute if (isNil(resultObj[d])) { stateValues.push(d); resultObj[d] = 0; + resultKeys.push(d); } }); } @@ -132,6 +144,7 @@ export const dataFilterComputeDomain = (data: Array, op: IDataFilterCompute if (isNil(resultObj[d[state]])) { stateValues.push(d[state]); resultObj[d[state]] = 0; + resultKeys.push(d[state]); } if (!isNil(valueFields[i])) { // 传进来的d[yFields[i]]可能是stringnumber @@ -145,9 +158,9 @@ export const dataFilterComputeDomain = (data: Array, op: IDataFilterCompute const sortedStateValues = hasLockDomain ? stateValues - : isCategoryState === false + : isCategoryState === false || isAllLinearValue ? stateValues.sort((a, b) => a - b) - : Object.keys(resultObj); + : resultKeys; sortedStateValues.forEach(state => { const res = { [stateField]: state }; @@ -161,3 +174,97 @@ export const dataFilterComputeDomain = (data: Array, op: IDataFilterCompute return resultData; }; + +export const statePointToData = (state: number, scale: IBaseScale, reverse: boolean) => { + const domain = scale.domain(); + + // continuous scale: 本来可以用scale invert,但scale invert在大数据场景下性能不太好,所以这里自行计算 + if (isContinuous(scale.type)) { + if (reverse) { + return domain[0] + (last(domain) - domain[0]) * (1 - state); + } + return domain[0] + (last(domain) - domain[0]) * state; + } + + // discete scale: 根据bandSize计算不准确, bandSize不是最新的, 导致index计算错误, 所以仍然使用invert + let range = scale.range(); + if (reverse) { + range = range.slice().reverse(); + } + const posInRange: number = range[0] + (last(range) - range[0]) * state; + return scale.invert(posInRange); +}; + +export const dataToStatePoint = (data: number | string, scale: IBaseScale, isHorizontal: boolean) => { + const pos = scale.scale(data); + let range = scale.range(); + + if (!isHorizontal && isContinuous(scale.type)) { + range = range.slice().reverse(); + } + + return Math.max(0, Math.min(1, (pos - range[0]) / (last(range) - range[0]))); +}; + +export const isReverse = (axisComponent: CartesianAxis, isHorizontal: boolean) => { + const axis = axisComponent; + if (!axis) { + return false; + } + const axisScale = axis.getScale() as IBandLikeScale; + return axisScale.range()[0] > axisScale.range()[1] && (!axis.getInverse() || isHorizontal); +}; + +export const getAxisBandSize = (axisSpec?: ICartesianBandAxisSpec) => { + const bandSize = axisSpec?.bandSize; + const maxBandSize = axisSpec?.maxBandSize; + const minBandSize = axisSpec?.minBandSize; + if (bandSize || minBandSize || maxBandSize) { + return { bandSize, maxBandSize, minBandSize }; + } + return undefined; +}; + +export const modeCheck = (statePoint: 'start' | 'end', mode: string, spec: any): any => { + if (statePoint === 'start') { + return (mode === 'percent' && isValid(spec.start)) || (mode === 'value' && isValid(spec.startValue)); + } + return (mode === 'percent' && isValid(spec.end)) || (mode === 'value' && isValid(spec.endValue)); +}; + +export const parseDomainFromState = (startValue: number | string, endValue: number | string, scale: IBaseScale) => { + if (isContinuous(scale.type)) { + return [Math.min(endValue as number, startValue as number), Math.max(endValue as number, startValue as number)]; + } + const allDomain = scale.domain(); + const startIndex = allDomain.indexOf(startValue); + const endIndex = allDomain.indexOf(endValue); + return allDomain.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex) + 1); +}; + +export const parseDomainFromStateAndValue = ( + start: number, + startValue: number | string, + end: number, + endValue: number | string, + scale: IBaseScale +) => { + if (isContinuous(scale.type)) { + const domain = scale.domain(); + const min = domain[0]; + const total = last(domain) - min; + const resultStart = isValid(start) ? min + total * start : +startValue; + const resultEnd = isValid(end) ? min + total * end : +endValue; + return [Math.min(resultEnd, resultStart), Math.max(resultEnd, resultStart)]; + } + const allDomain = scale.domain(); + const range = scale.range(); + const rangeSize = range[range.length - 1] - range[0]; + const startIndex = isValid(start) + ? allDomain.indexOf(scale.invert(rangeSize * start + range[0])) + : allDomain.indexOf(startValue); + const endIndex = isValid(end) + ? allDomain.indexOf(scale.invert(rangeSize * end + range[0])) + : allDomain.indexOf(endValue); + return allDomain.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex) + 1); +}; diff --git a/packages/vchart/src/constant/event.ts b/packages/vchart/src/constant/event.ts index 2b670956e2..3ff0436e97 100644 --- a/packages/vchart/src/constant/event.ts +++ b/packages/vchart/src/constant/event.ts @@ -145,6 +145,7 @@ export enum ChartEvent { /** series end */ // scale scaleDomainUpdate = 'scaleDomainUpdate', + scaleRawDomainUpdate = 'scaleRawDomainUpdate', scaleUpdate = 'scaleUpdate', // datazoom dataZoomChange = 'dataZoomChange', diff --git a/packages/vchart/src/mark/base/base-mark.ts b/packages/vchart/src/mark/base/base-mark.ts index 726957c844..38efb1a8d8 100644 --- a/packages/vchart/src/mark/base/base-mark.ts +++ b/packages/vchart/src/mark/base/base-mark.ts @@ -1571,7 +1571,7 @@ export class BaseMark extends GrammarItem implements IMar // g = this._createGraphic(finalAttrs) as IMarkGraphic; // 如果有动画,设置一下最终attribute if (hasAnimation) { - g.setFinalAttributes(finalAttrs); + g.setFinalAttributes?.(finalAttrs); } g.context = mockGraphic.context; g.context.diffAttrs = finalAttrs; diff --git a/packages/vchart/src/series/bar/bar.ts b/packages/vchart/src/series/bar/bar.ts index 9869043b11..c29a3f0846 100644 --- a/packages/vchart/src/series/bar/bar.ts +++ b/packages/vchart/src/series/bar/bar.ts @@ -14,7 +14,7 @@ import { } from '../../constant/data'; import { AttributeLevel } from '../../constant/attribute'; import type { Datum, DirectionType } from '../../typings'; -import { valueInScaleRange } from '../../util/scale'; +import { isValueInScaleDomain, valueInScaleRange } from '../../util/scale'; import { getRegionStackGroup } from '../../util/data'; import { getActualNumValue } from '../../util/space'; import { registerBarAnimation } from './animation'; @@ -332,9 +332,10 @@ export class BarSeries extends Cartes const barMinHeight = this._spec.barMinHeight; const y1 = valueInScaleRange(this[startMethod](datum), seriesScale, useWholeRange); const y = valueInScaleRange(this[endMethod](datum), seriesScale, useWholeRange); - let height = Math.abs(y1 - y); - if (height < barMinHeight) { + if (height <= 0 && !isValueInScaleDomain(datum[this.getStackValueField()], seriesScale)) { + height = 0; + } else if (height < barMinHeight) { height = barMinHeight; } diff --git a/packages/vchart/src/series/dot/dot.ts b/packages/vchart/src/series/dot/dot.ts index bd106bd014..fe8603a998 100644 --- a/packages/vchart/src/series/dot/dot.ts +++ b/packages/vchart/src/series/dot/dot.ts @@ -37,7 +37,6 @@ export class DotSeries extends Cartes private _xDimensionStatisticsDomain: any[]; - // csj-Q: 是否需要把这些属性写成接口? protected _seriesGroupField?: string; getSeriesGroupField() { return this._seriesField; @@ -192,11 +191,11 @@ export class DotSeries extends Cartes this.setMarkStyle( clipMark, { - x: -this._spec.leftAppendPadding, + x: -(this._spec.leftAppendPadding ?? 0), y: 0, // 本应使用this.getLayoutRect().width, 但这该返回值为0。考虑到横向不需要裁剪,故先采用一个较大值 width: 10000, - height: this._spec.clipHeight + height: () => this._spec.clipHeight ?? this._region.getLayoutRect().height }, 'normal', AttributeLevel.Series @@ -390,8 +389,8 @@ export class DotSeries extends Cartes return this._seriesGroupField ? this.getViewDataStatistics()?.latestData[this._seriesGroupField].values : this._seriesField - ? this.getViewDataStatistics()?.latestData[this._seriesField].values - : []; + ? this.getViewDataStatistics()?.latestData[this._seriesField].values + : []; } /** @@ -413,10 +412,10 @@ export class DotSeries extends Cartes const colorDomain = this._dotTypeField ? this.getViewDataStatistics()?.latestData[this._dotTypeField].values : this._seriesGroupField - ? this.getViewDataStatistics()?.latestData[this._seriesGroupField].values - : this._seriesField - ? this.getViewDataStatistics()?.latestData[this._seriesField].values - : []; + ? this.getViewDataStatistics()?.latestData[this._seriesGroupField].values + : this._seriesField + ? this.getViewDataStatistics()?.latestData[this._seriesField].values + : []; const colorRange = this._getDataScheme(); return new ColorOrdinalScale().domain(colorDomain).range(colorRange); } diff --git a/packages/vchart/src/series/util/stack.ts b/packages/vchart/src/series/util/stack.ts index dfd6c326c9..653ee73084 100644 --- a/packages/vchart/src/series/util/stack.ts +++ b/packages/vchart/src/series/util/stack.ts @@ -1,6 +1,6 @@ import type { IBaseScale } from '@visactor/vscale'; import type { IStackCacheNode } from '../../util/data'; -import { valueInScaleRange } from '../../util/scale'; +import { isValueInScaleDomain, valueInScaleRange } from '../../util/scale'; import type { ISeries } from '../interface/series'; export function stackWithMinHeight( @@ -83,7 +83,9 @@ function computeOneDatumY( } let height = Math.abs(y1 - y); - if (height < barMinHeight) { + if (height <= 0 && !isValueInScaleDomain(obj[s.getStackValueField()], seriesScale)) { + height = 0; + } else if (height < barMinHeight) { height = barMinHeight; } diff --git a/tools/story-player/package.json b/tools/story-player/package.json index 8c6dbc7619..e2e7407cce 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.24", - "@visactor/vrender-kits": "~1.0.24", + "@visactor/vrender-core": "~1.0.26", + "@visactor/vrender-kits": "~1.0.26", "@visactor/vchart": "workspace:2.0.7", - "@visactor/vrender": "~1.0.24", + "@visactor/vrender": "~1.0.26", "@visactor/vutils": "~1.0.12" } } \ No newline at end of file