diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..de326de0 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,17 @@ +# Add 'vmind' label to any change within the 'vmind' package +vmind: + - packages/vmind/** + + +# Add 'calculator' label to any change within the 'calculator' package +calculator: + - packages/calculator/** + + +# Add 'chart-advisor' label to any change within the 'calculator' package +chart-advisor: + - packages/chart-advisor/** + + + + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69f606c3..530d4f8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,9 @@ jobs: release_version: ${{ steps.semver_parser.outputs.full }} write_next_bump: true + - name: Generate changelog by rush version + run: node common/scripts/install-run-rush.js version --bump + - name: Update version run: node common/scripts/install-run-rush.js version --bump diff --git a/.gitignore b/.gitignore index 1222cc77..279a800e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ dist esm es cjs +lib build build-es5 *.zip @@ -103,3 +104,6 @@ docs/public/documents .env.local packages/calculator/tsconfig.cjs.tsbuildinfo packages/calculator/tsconfig.esm.tsbuildinfo +packages/chart-advisor/tsconfig.esm.tsbuildinfo +packages/chart-advisor/tsconfig.cjs.tsbuildinfo +packages/vmind/__tests__/browser/src/pages/mockData.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index ab7d6f55..1a6b9227 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -85,6 +85,43 @@ importers: ts-node: 10.9.0_4lhcgfu2tqlb5z5dwdvf5srjjq typescript: 4.9.5 + ../../packages/chart-advisor: + specifiers: + '@internal/bundler': workspace:* + '@internal/eslint-config': workspace:* + '@internal/ts-config': workspace:* + '@rushstack/eslint-patch': ~1.1.4 + '@types/jest': ^26.0.0 + '@types/lodash': 4.14.182 + '@types/node': '*' + '@typescript-eslint/eslint-plugin': 5.30.0 + '@typescript-eslint/parser': 5.30.0 + jest: ^26.0.0 + lodash: 4.17.21 + npm-run-all: ^4.1.5 + rimraf: ^3.0.2 + ts-jest: ^26.0.0 + typescript: 4.9.5 + undici-types: ^5.27.2 + dependencies: + lodash: 4.17.21 + devDependencies: + '@internal/bundler': link:../../tools/bundler + '@internal/eslint-config': link:../../share/eslint-config + '@internal/ts-config': link:../../share/ts-config + '@rushstack/eslint-patch': 1.1.4 + '@types/jest': 26.0.24 + '@types/lodash': 4.14.182 + '@types/node': 20.11.16 + '@typescript-eslint/eslint-plugin': 5.30.0_dwvk2jxdxtyxgoclhbqbxvd22a + '@typescript-eslint/parser': 5.30.0_typescript@4.9.5 + jest: 26.6.3 + npm-run-all: 4.1.5 + rimraf: 3.0.2 + ts-jest: 26.5.6_xuote2qreek47x2di7kesslrai + typescript: 4.9.5 + undici-types: 5.28.3 + ../../packages/vmind: specifiers: '@arco-design/web-react': 2.46.1 @@ -103,12 +140,13 @@ importers: '@typescript-eslint/eslint-plugin': 5.30.0 '@typescript-eslint/parser': 5.30.0 '@visactor/calculator': workspace:* - '@visactor/chart-advisor': 0.1.10 - '@visactor/vchart': ^1.9.0 + '@visactor/chart-advisor': workspace:* + '@visactor/vchart': ^1.10.2 '@visactor/vdataset': ~0.17.4 '@visactor/vrender-core': ^0.17.23 '@visactor/vutils': ~0.17.4 '@vitejs/plugin-react': 3.1.0 + alasql: ~4.3.2 axios: ^1.4.0 canvas: ^2.11.2 dayjs: ~1.11.10 @@ -136,9 +174,10 @@ importers: vite-plugin-libcss: ~1.1.1 dependencies: '@visactor/calculator': link:../calculator - '@visactor/chart-advisor': 0.1.10 + '@visactor/chart-advisor': link:../chart-advisor '@visactor/vdataset': 0.17.4 '@visactor/vutils': 0.17.4 + alasql: 4.3.2 axios: 1.6.7 dayjs: 1.11.10 exceljs: 4.4.0 @@ -162,7 +201,7 @@ importers: '@types/react-dom': 18.2.18 '@typescript-eslint/eslint-plugin': 5.30.0_cow5zg7tx6c3eisi5a4ud5kwia '@typescript-eslint/parser': 5.30.0_vwud3sodsb5zxmzckoj7rdwdbq - '@visactor/vchart': 1.9.2 + '@visactor/vchart': 1.10.2 '@visactor/vrender-core': 0.17.23 '@vitejs/plugin-react': 3.1.0_vite@3.2.6 canvas: 2.11.2 @@ -1876,6 +1915,46 @@ packages: slash: 3.0.0 dev: true + /@jest/core/26.6.3: + resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} + engines: {node: '>= 10.14.2'} + dependencies: + '@jest/console': 26.6.2 + '@jest/reporters': 26.6.2 + '@jest/test-result': 26.6.2 + '@jest/transform': 26.6.2 + '@jest/types': 26.6.2 + '@types/node': 20.11.16 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 26.6.2 + jest-config: 26.6.3 + jest-haste-map: 26.6.2 + jest-message-util: 26.6.2 + jest-regex-util: 26.0.0 + jest-resolve: 26.6.2 + jest-resolve-dependencies: 26.6.3 + jest-runner: 26.6.3 + jest-runtime: 26.6.3 + jest-snapshot: 26.6.2 + jest-util: 26.6.2 + jest-validate: 26.6.2 + jest-watcher: 26.6.2 + micromatch: 4.0.5 + p-each-series: 2.2.0 + rimraf: 3.0.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /@jest/core/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} engines: {node: '>= 10.14.2'} @@ -2091,6 +2170,23 @@ packages: - supports-color dev: true + /@jest/test-sequencer/26.6.3: + resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==} + engines: {node: '>= 10.14.2'} + dependencies: + '@jest/test-result': 26.6.2 + graceful-fs: 4.2.11 + jest-haste-map: 26.6.2 + jest-runner: 26.6.3 + jest-runtime: 26.6.3 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /@jest/test-sequencer/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==} engines: {node: '>= 10.14.2'} @@ -3033,6 +3129,51 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/eslint-plugin/5.30.0_dwvk2jxdxtyxgoclhbqbxvd22a: + resolution: {integrity: sha512-lvhRJ2pGe2V9MEU46ELTdiHgiAFZPKtLhiU5wlnaYpMc2+c1R8fh8i80ZAa665drvjHKUJyRRGg3gEm1If54ow==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.30.0_typescript@4.9.5 + '@typescript-eslint/scope-manager': 5.30.0 + '@typescript-eslint/type-utils': 5.30.0_typescript@4.9.5 + '@typescript-eslint/utils': 5.30.0_typescript@4.9.5 + debug: 4.3.4 + functional-red-black-tree: 1.0.1 + ignore: 5.3.1 + regexpp: 3.2.0 + semver: 7.5.4 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.30.0_typescript@4.9.5: + resolution: {integrity: sha512-2oYYUws5o2liX6SrFQ5RB88+PuRymaM2EU02/9Ppoyu70vllPnHVO7ioxDdq/ypXHA277R04SVjxvwI8HmZpzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.30.0 + '@typescript-eslint/types': 5.30.0 + '@typescript-eslint/typescript-estree': 5.30.0_typescript@4.9.5 + debug: 4.3.4 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser/5.30.0_vwud3sodsb5zxmzckoj7rdwdbq: resolution: {integrity: sha512-2oYYUws5o2liX6SrFQ5RB88+PuRymaM2EU02/9Ppoyu70vllPnHVO7ioxDdq/ypXHA277R04SVjxvwI8HmZpzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3059,6 +3200,24 @@ packages: '@typescript-eslint/types': 5.30.0 '@typescript-eslint/visitor-keys': 5.30.0 + /@typescript-eslint/type-utils/5.30.0_typescript@4.9.5: + resolution: {integrity: sha512-GF8JZbZqSS+azehzlv/lmQQ3EU3VfWYzCczdZjJRxSEeXDQkqFhCBgFhallLDbPwQOEQ4MHpiPfkjKk7zlmeNg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/utils': 5.30.0_typescript@4.9.5 + debug: 4.3.4 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/type-utils/5.30.0_vwud3sodsb5zxmzckoj7rdwdbq: resolution: {integrity: sha512-GF8JZbZqSS+azehzlv/lmQQ3EU3VfWYzCczdZjJRxSEeXDQkqFhCBgFhallLDbPwQOEQ4MHpiPfkjKk7zlmeNg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3101,6 +3260,23 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/utils/5.30.0_typescript@4.9.5: + resolution: {integrity: sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.15 + '@typescript-eslint/scope-manager': 5.30.0 + '@typescript-eslint/types': 5.30.0 + '@typescript-eslint/typescript-estree': 5.30.0_typescript@4.9.5 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/utils/5.30.0_vwud3sodsb5zxmzckoj7rdwdbq: resolution: {integrity: sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3129,29 +3305,23 @@ packages: resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} dev: true - /@visactor/chart-advisor/0.1.10: - resolution: {integrity: sha512-br8EoI6uz2GwxjkHTUNOGkVhnTzNrf96h4LIeAz0IojXLxplqTp4USmKW8IRkgk9PJs5sDcB+/9cgZBpz5vR4A==} - dependencies: - lodash: 4.17.21 - dev: false - - /@visactor/vchart/1.9.2: - resolution: {integrity: sha512-wvzskhploTKzsspPBlx3s+m6tAMM1OU6Fyk96fRy/4WP5udjZ7X6gXFbZ2f2C4NmmleirN1nF8psVB2Q+VVk+Q==} + /@visactor/vchart/1.10.2: + resolution: {integrity: sha512-xFMe6MnpOHRyAcG9LNVrYbik4gt0HGgr/8+Phf6Bke1fwpJa1se/w2ofzlTVL7eMPmxYo7KKrmQyG0eLGWTkkA==} dependencies: - '@visactor/vdataset': 0.17.4 - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-hierarchy': 0.11.11 - '@visactor/vgrammar-projection': 0.11.11 - '@visactor/vgrammar-sankey': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vgrammar-wordcloud': 0.11.11 - '@visactor/vgrammar-wordcloud-shape': 0.11.11 - '@visactor/vrender-components': 0.17.23 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vscale': 0.17.4 - '@visactor/vutils': 0.17.4 - '@visactor/vutils-extension': 1.9.2 + '@visactor/vdataset': 0.18.1 + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-hierarchy': 0.12.7 + '@visactor/vgrammar-projection': 0.12.7 + '@visactor/vgrammar-sankey': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vgrammar-wordcloud': 0.12.7 + '@visactor/vgrammar-wordcloud-shape': 0.12.7 + '@visactor/vrender-components': 0.18.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vscale': 0.18.1 + '@visactor/vutils': 0.18.1 + '@visactor/vutils-extension': 1.10.2 dev: true /@visactor/vdataset/0.17.4: @@ -3174,90 +3344,113 @@ packages: simple-statistics: 7.8.3 simplify-geojson: 1.0.5 topojson-client: 3.1.0 + dev: false - /@visactor/vgrammar-coordinate/0.11.11: - resolution: {integrity: sha512-2gwXXfi+NrkdcoQABL2heYs31qfzIWuv3AqYm/qO5pFQ78+eGsB9ZGo/8DldjvfMH8zw50QzB+tzgLrlEcUenA==} + /@visactor/vdataset/0.18.1: + resolution: {integrity: sha512-ByrBt2kgLvYRve+Q+9oo3Ibav5WVSyWPuxdDJHK7kDTJGtTuV8z4qKcqArB86PcAOJS1s5L0TtHlV4Femm2xoA==} dependencies: - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vutils': 0.17.4 + '@turf/flatten': 6.5.0 + '@turf/helpers': 6.5.0 + '@turf/rewind': 6.5.0 + '@visactor/vutils': 0.18.1 + d3-dsv: 2.0.0 + d3-geo: 1.12.1 + d3-hexbin: 0.2.2 + d3-hierarchy: 3.1.2 + eventemitter3: 4.0.7 + geobuf: 3.0.2 + geojson-dissolve: 3.1.0 + path-browserify: 1.0.1 + pbf: 3.2.1 + point-at-length: 1.1.0 + simple-statistics: 7.8.3 + simplify-geojson: 1.0.5 + topojson-client: 3.1.0 dev: true - /@visactor/vgrammar-core/0.11.11: - resolution: {integrity: sha512-3y1EPhWn95FaqA6UnNR+LNHzTM3zaJRUPLl29a9oUXVV6OgOTo05dEFyETuaybrxIDpPodqroBGU2yemikvgjA==} + /@visactor/vgrammar-coordinate/0.12.7: + resolution: {integrity: sha512-nJR506XRUnci4i/URc/6QIk4M0chjmhXX3nb/8DtpnpfKItFA976J6UuG1RJxVgYpcOHk3qIionX+oZotQnvIg==} dependencies: - '@visactor/vdataset': 0.17.4 - '@visactor/vgrammar-coordinate': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vrender-components': 0.17.23 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vscale': 0.17.4 - '@visactor/vutils': 0.17.4 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vgrammar-hierarchy/0.11.11: - resolution: {integrity: sha512-iJmUYV++GxguZgpeoH3g2C6b1Es25hXdSr7wvZyckQBEMhfXoLj8zDV00BNkMKO46gPNUGBq6lH84NHDmXfCAw==} + /@visactor/vgrammar-core/0.12.7: + resolution: {integrity: sha512-oaCqiCM/JYyDyh603oehaP7QATS9Ur9AkEvC6rmyxqjTXE1HHDXIIUtvPOgTuGJCi47mf23QCyrf+rAgefX5vQ==} dependencies: - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vutils': 0.17.4 + '@visactor/vdataset': 0.18.1 + '@visactor/vgrammar-coordinate': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vrender-components': 0.18.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vscale': 0.18.1 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vgrammar-projection/0.11.11: - resolution: {integrity: sha512-fop112v6EROgZQA4jOpkRa5MFymo7JrfUybIt1d0qZzW2TDHNP1BCDZl6GmODMflBNXs03zalwgzj/GnEBC6bw==} + /@visactor/vgrammar-hierarchy/0.12.7: + resolution: {integrity: sha512-jpBQplqsheyFU0RmiyCGXm0ppCPZyganfR3Z/IudP3VG4UaRKkY6TxYw76wTcqqcIvRDfo4rWwTq/TnWI7f4Gg==} dependencies: - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vutils': 0.17.4 + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vutils': 0.18.1 + dev: true + + /@visactor/vgrammar-projection/0.12.7: + resolution: {integrity: sha512-MMpMa4nd+gSFsyWjCMYZEpATYLYIMai+z7/G2yeZ102d0B+tXLLkgrPQkmxewRrbaGxmEpLqMxuNMMB8g1CKYw==} + dependencies: + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vutils': 0.18.1 d3-geo: 1.12.1 dev: true - /@visactor/vgrammar-sankey/0.11.11: - resolution: {integrity: sha512-J98xvPW6aJXCJ/AIb0Mw5RU7HVCH63k7ZoQXyphbT850AZXN7s+cztY6TQk9/CHe2Fv536Wf+bHe8mdbty5jlw==} + /@visactor/vgrammar-sankey/0.12.7: + resolution: {integrity: sha512-Ax5qeeuLhBgKv9JcRjM/1+vA3mGqK/LlcTWEA0yN31KiHwse9THH2n93MlDhU/HkX0yQ7tFeMAKXxwUFZsVT+A==} dependencies: - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vutils': 0.17.4 + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vgrammar-util/0.11.11: - resolution: {integrity: sha512-3R4KdQ2j0JH0ZeM8eMSoa8RM/RXHWNv3AFiO5ebzBZDALzkgBvcYmKmyMEINTkakRlojEFpaP27LqkCL0CpYPg==} + /@visactor/vgrammar-util/0.12.7: + resolution: {integrity: sha512-rxKy9Y2PMGxJX9B+bMOdQqQ5604tWvXiDxSTnPt2HtzBKGLnTj7j7dUfKdxBlVvaQnlMjaeLr4OdKnA3Yh9KhA==} dependencies: - '@visactor/vutils': 0.17.4 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vgrammar-wordcloud-shape/0.11.11: - resolution: {integrity: sha512-kzPJgfZonxWUVsKNn0ov81KIrzJAV3d6MQm6A/yYWfJvqF4T44iB38FRo9Ox+uBZ19wLqnx8ueE2WgId3Far5w==} + /@visactor/vgrammar-wordcloud-shape/0.12.7: + resolution: {integrity: sha512-9QjyxiVxNdP9tddCyI1W20aWSfMHkigNsz7/J4njCLJlSE3AN3+bZa6llMparNGWTFldHg3Hxlsbp5/40g+F3A==} dependencies: - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vscale': 0.17.4 - '@visactor/vutils': 0.17.4 + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vscale': 0.18.1 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vgrammar-wordcloud/0.11.11: - resolution: {integrity: sha512-OPThALDHaXKBw9EadNuFhUoOBD+vvtxTvdV+YxJrZ7B1dDfr8lJ896ACNqacQ9lUikHJdO8DhutdcS5FQw1/gg==} + /@visactor/vgrammar-wordcloud/0.12.7: + resolution: {integrity: sha512-kx3/GZ3xL7B1oPOJf25iC+c3GeArFFhi8eUaRCQYrSkCkVhZ7EEVJfKe2kxgdgXyj6Di/FgLh8g3hcBSaIlAnA==} dependencies: - '@visactor/vgrammar-core': 0.11.11 - '@visactor/vgrammar-util': 0.11.11 - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vutils': 0.17.4 + '@visactor/vgrammar-core': 0.12.7 + '@visactor/vgrammar-util': 0.12.7 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vutils': 0.18.1 dev: true - /@visactor/vrender-components/0.17.23: - resolution: {integrity: sha512-unFzLVodABDVh5RQA3ls/eR6mLHoy/nx6yyoMm6VibuHZUYORI7W3x30vgFHn+POo7yPV3aVjB56Y2IUH7FrtQ==} + /@visactor/vrender-components/0.18.7: + resolution: {integrity: sha512-nQVW8skqklijPqLJb14wtyrUYqdCDvicfPF40rXszpmwF8SZrlN5ZMgor+afr+7/mxLYHHYlI9k9ecvTSfyHEw==} dependencies: - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 '@visactor/vscale': 0.17.4 - '@visactor/vutils': 0.17.4 + '@visactor/vutils': 0.18.1 dev: true /@visactor/vrender-core/0.17.23: @@ -3267,12 +3460,19 @@ packages: color-convert: 2.0.1 dev: true - /@visactor/vrender-kits/0.17.23: - resolution: {integrity: sha512-3Mnr5y62ZTeZweaEGNLMM8Wf3vkf2K8aTiW4kinXkk0Am0k78v/0kdkwaYEnNM89uJ4zRVXPkkLG6h+/4KMo8Q==} + /@visactor/vrender-core/0.18.7: + resolution: {integrity: sha512-mjqJpgsQo+sbsF9UNkFNQNFaO0AqPX+6uxnwoMZ1q3LJoQ8p/cRvDAez/dYT4Z1ZqdaB8GgRXYLp+vy2+P2lUA==} + dependencies: + '@visactor/vutils': 0.18.1 + color-convert: 2.0.1 + dev: true + + /@visactor/vrender-kits/0.18.7: + resolution: {integrity: sha512-UG3WKMBufnQa2cPGhJnpd6/e5NdH3FQgDfE/FA/UKxTBqfG1bSS2KkK7Y+bJJwueCMRZQx3d4ksPeW1H3vOnMg==} dependencies: '@resvg/resvg-js': 2.4.1 - '@visactor/vrender-core': 0.17.23 - '@visactor/vutils': 0.17.4 + '@visactor/vrender-core': 0.18.7 + '@visactor/vutils': 0.18.1 roughjs: 4.5.2 dev: true @@ -3282,13 +3482,19 @@ packages: '@visactor/vutils': 0.17.4 dev: true - /@visactor/vutils-extension/1.9.2: - resolution: {integrity: sha512-qOL5lsUx+RSM3/A/kEJB3+50QPC+wQs2tEclrfa7EXWdXQzVPNlcU2evDikTJska6b/WlMJIdtdNvbAhaTz51A==} + /@visactor/vscale/0.18.1: + resolution: {integrity: sha512-0wpd0avbFLvuDKNHt2PxdKdqLSU9+zUkM6GJYWbXsUUYOiKaFkt2xTkdwUHKq66v23C7Iy14Pm7VVr0wVgflbA==} dependencies: - '@visactor/vrender-core': 0.17.23 - '@visactor/vrender-kits': 0.17.23 - '@visactor/vscale': 0.17.4 - '@visactor/vutils': 0.17.4 + '@visactor/vutils': 0.18.1 + dev: true + + /@visactor/vutils-extension/1.10.2: + resolution: {integrity: sha512-xX4SAAg9YG8iSCWH4tnaWYOZCtqVHtZ6bzX1u9255qN0FbEEll12kWBZEiSn1kX4vQCMbbbs/jjX/WDOLPwuIw==} + dependencies: + '@visactor/vrender-core': 0.18.7 + '@visactor/vrender-kits': 0.18.7 + '@visactor/vscale': 0.18.1 + '@visactor/vutils': 0.18.1 dev: true /@visactor/vutils/0.17.4: @@ -3298,6 +3504,14 @@ packages: '@turf/invariant': 6.5.0 eventemitter3: 4.0.7 + /@visactor/vutils/0.18.1: + resolution: {integrity: sha512-XGq9a85HrVP3Rbby1qO2/JS9GewJtZv6y35Xujcb2ZGLEjnpCK61Y1OXwSC5SZOKmtsH4SjYMf5czlnNhQ3GeA==} + dependencies: + '@turf/helpers': 6.5.0 + '@turf/invariant': 6.5.0 + eventemitter3: 4.0.7 + dev: true + /@vitejs/plugin-react/3.1.0_vite@3.2.6: resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3459,6 +3673,17 @@ packages: uri-js: 4.4.1 dev: true + /alasql/4.3.2: + resolution: {integrity: sha512-eil4Y/GQx0LP7P2dOiG8+N+jESibN2t/8cnVcNW6kuatPLkW7bCxC/vKINxJNOfyReeisco9nW4kjWg75bTnDg==} + engines: {node: '>=15'} + hasBin: true + dependencies: + cross-fetch: 4.0.0 + yargs: 16.2.0 + transitivePeerDependencies: + - encoding + dev: false + /ansi-colors/1.1.0: resolution: {integrity: sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==} engines: {node: '>=0.10.0'} @@ -4536,7 +4761,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true /cliui/8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -4773,6 +4997,14 @@ packages: /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /cross-fetch/4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn/6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} @@ -5812,6 +6044,15 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 + /eslint-utils/3.0.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint-visitor-keys: 2.1.0 + dev: true + /eslint-utils/3.0.0_eslint@8.18.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} @@ -7576,6 +7817,32 @@ packages: throat: 5.0.0 dev: true + /jest-cli/26.6.3: + resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} + engines: {node: '>= 10.14.2'} + hasBin: true + dependencies: + '@jest/core': 26.6.3 + '@jest/test-result': 26.6.2 + '@jest/types': 26.6.2 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + is-ci: 2.0.0 + jest-config: 26.6.3 + jest-util: 26.6.2 + jest-validate: 26.6.2 + prompts: 2.4.2 + yargs: 15.4.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /jest-cli/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} engines: {node: '>= 10.14.2'} @@ -7653,6 +7920,40 @@ packages: - supports-color dev: true + /jest-config/26.6.3: + resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==} + engines: {node: '>= 10.14.2'} + peerDependencies: + ts-node: '>=9.0.0' + peerDependenciesMeta: + ts-node: + optional: true + dependencies: + '@babel/core': 7.20.12 + '@jest/test-sequencer': 26.6.3 + '@jest/types': 26.6.2 + babel-jest: 26.6.3_@babel+core@7.20.12 + chalk: 4.1.2 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-environment-jsdom: 26.6.2 + jest-environment-node: 26.6.2 + jest-get-type: 26.3.0 + jest-jasmine2: 26.6.3 + jest-regex-util: 26.0.0 + jest-resolve: 26.6.2 + jest-util: 26.6.2 + jest-validate: 26.6.2 + micromatch: 4.0.5 + pretty-format: 26.6.2 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + dev: true + /jest-config/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==} engines: {node: '>= 10.14.2'} @@ -7947,6 +8248,36 @@ packages: - supports-color dev: true + /jest-jasmine2/26.6.3: + resolution: {integrity: sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==} + engines: {node: '>= 10.14.2'} + dependencies: + '@babel/traverse': 7.23.9 + '@jest/environment': 26.6.2 + '@jest/source-map': 26.6.2 + '@jest/test-result': 26.6.2 + '@jest/types': 26.6.2 + '@types/node': 20.11.16 + chalk: 4.1.2 + co: 4.6.0 + expect: 26.6.2 + is-generator-fn: 2.1.0 + jest-each: 26.6.2 + jest-matcher-utils: 26.6.2 + jest-message-util: 26.6.2 + jest-runtime: 26.6.3 + jest-snapshot: 26.6.2 + jest-util: 26.6.2 + pretty-format: 26.6.2 + throat: 5.0.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /jest-jasmine2/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==} engines: {node: '>= 10.14.2'} @@ -8182,6 +8513,38 @@ packages: - supports-color dev: true + /jest-runner/26.6.3: + resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==} + engines: {node: '>= 10.14.2'} + dependencies: + '@jest/console': 26.6.2 + '@jest/environment': 26.6.2 + '@jest/test-result': 26.6.2 + '@jest/types': 26.6.2 + '@types/node': 20.11.16 + chalk: 4.1.2 + emittery: 0.7.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 26.6.3 + jest-docblock: 26.0.0 + jest-haste-map: 26.6.2 + jest-leak-detector: 26.6.2 + jest-message-util: 26.6.2 + jest-resolve: 26.6.2 + jest-runtime: 26.6.3 + jest-util: 26.6.2 + jest-worker: 26.6.2 + source-map-support: 0.5.21 + throat: 5.0.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /jest-runner/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==} engines: {node: '>= 10.14.2'} @@ -8278,6 +8641,46 @@ packages: - supports-color dev: true + /jest-runtime/26.6.3: + resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==} + engines: {node: '>= 10.14.2'} + hasBin: true + dependencies: + '@jest/console': 26.6.2 + '@jest/environment': 26.6.2 + '@jest/fake-timers': 26.6.2 + '@jest/globals': 26.6.2 + '@jest/source-map': 26.6.2 + '@jest/test-result': 26.6.2 + '@jest/transform': 26.6.2 + '@jest/types': 26.6.2 + '@types/yargs': 15.0.19 + chalk: 4.1.2 + cjs-module-lexer: 0.6.0 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-config: 26.6.3 + jest-haste-map: 26.6.2 + jest-message-util: 26.6.2 + jest-mock: 26.6.2 + jest-regex-util: 26.0.0 + jest-resolve: 26.6.2 + jest-snapshot: 26.6.2 + jest-util: 26.6.2 + jest-validate: 26.6.2 + slash: 3.0.0 + strip-bom: 4.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /jest-runtime/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==} engines: {node: '>= 10.14.2'} @@ -8496,6 +8899,22 @@ packages: supports-color: 7.2.0 dev: true + /jest/26.6.3: + resolution: {integrity: sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==} + engines: {node: '>= 10.14.2'} + hasBin: true + dependencies: + '@jest/core': 26.6.3 + import-local: 3.1.0 + jest-cli: 26.6.3 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + dev: true + /jest/26.6.3_canvas@2.11.2: resolution: {integrity: sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==} engines: {node: '>= 10.14.2'} @@ -9476,7 +9895,6 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 - dev: true /node-int64/0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -11735,7 +12153,6 @@ packages: /tr46/0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true /tr46/1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -11997,6 +12414,10 @@ packages: /undici-types/5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici-types/5.28.3: + resolution: {integrity: sha512-VJD0un4i6M1/lFOJPhacHdq6FadtlkdhKBed2W6yBqmrAr/W58oqENaOIX031stDVFwz9AemOLkIj/2AXAMLCg==} + dev: true + /unicode-canonical-property-names-ecmascript/2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -12484,7 +12905,6 @@ packages: /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true /webidl-conversions/4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -12520,7 +12940,6 @@ packages: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true /whatwg-url/6.5.0: resolution: {integrity: sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==} @@ -12764,7 +13183,6 @@ packages: /yargs-parser/20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} - dev: true /yargs-parser/21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} @@ -12830,7 +13248,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 - dev: true /yargs/17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 66cc743f..8bdf9a65 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -2,7 +2,7 @@ { "definitionName": "lockStepVersion", "policyName": "vmindMin", - "version": "1.2.4", + "version": "1.2.5", "mainProject": "@visactor/vmind", "nextBump": "patch" } diff --git a/docs/assets/changelog/en/changelog.md b/docs/assets/changelog/en/changelog.md index e6e922f6..e69de29b 100644 --- a/docs/assets/changelog/en/changelog.md +++ b/docs/assets/changelog/en/changelog.md @@ -1,542 +0,0 @@ -# v0.17.18 - -2024-01-24 - - -**🆕 New feature** - -- **@visactor/vrender-components**: adjust the timing for label customLayoutFunc invocation -- **@visactor/vrender-components**: label component will sync maxLineWidth to maxWidth in richText -- **@visactor/vrender-kits**: compatible canvas in lynx env -- **@visactor/vrender-core**: support backgroundCornerRadius - -**🐛 Bug fix** - -- **@visactor/vrender-components**: event pos error when interactive in site -- **@visactor/vrender-kits**: fix issue with interface -- **@visactor/vrender-core**: fix issue with multiline text textBaseline, closed [#886](https://github.com/VisActor/VRender/issues/886) -- **@visactor/vrender-core**: fix issue with union empty bounds -- **@visactor/vrender-core**: richtext.textConfig supports number type text - - - -[more detail about v0.17.18](https://github.com/VisActor/VRender/releases/tag/v0.17.18) - -# v0.17.17 - -2024-01-22 - - -**🆕 New feature** - -- **@visactor/vrender-core**: html only append dom inside body -- **@visactor/vrender-core**: color support str gradient color -- **@visactor/vrender**: color support str gradient color - -**🐛 Bug fix** - -- **@visactor/vrender-components**: title support multiline -- **@visactor/vrender-kits**: fix issue with loaded tree-shaking -- **@visactor/vrender-core**: fix issue with rerun getTextBounds -- **@visactor/vrender-core**: fix issue with set image -- **@visactor/vrender-core**: fix issue with loaded tree-shaking - - - -[more detail about v0.17.17](https://github.com/VisActor/VRender/releases/tag/v0.17.17) - -# v0.17.16 - -2024-01-18 - - -**🆕 New feature** - -- **@visactor/vrender-core**: enable pass supportsPointerEvents and supportsTouchEvents - -**🐛 Bug fix** - -- **@visactor/vrender-components**: when no brush is active, brush should not call stopPropagation() - -[more detail about v0.17.16](https://github.com/VisActor/VRender/releases/tag/v0.17.16) - -# v0.17.15 - -2024-01-17 - - -**🆕 New feature** - -- **@visactor/vrender-components**: support boolean config in label -- **@visactor/vrender-core**: add supportsTouchEvents and supportsPointerEvents params - -**🐛 Bug fix** - -- **@visactor/vrender-components**: fix the flush of axis when axis label has rotate angle -- **@visactor/vrender-components**: arc label line not shown -- **@visactor/vrender-components**: error happens in line-label when line has no points -- **@visactor/vrender-core**: fix issue with html attribute -- **@visactor/vrender-core**: fix issue with env-check -- **@visactor/vrender-core**: fix issue with text background opacity - - - -[more detail about v0.17.15](https://github.com/VisActor/VRender/releases/tag/v0.17.15) - -# v0.17.14 - -2024-01-12 - -**🐛 Bug fix** -- **@visactor/vrender-core**: fix `splitRect` when rect has `x1` or `y1` -- **@visactor/vrender**: fix `splitRect` when rect has `x1` or `y1` - - -[more detail about v0.17.14](https://github.com/VisActor/VRender/releases/tag/v0.17.14) - -# v0.17.13 - -2024-01-10 - -**🆕 New feature** -- **@visactor/vrender-core**: background support opacity -**🐛 Bug fix** -- **@visactor/vrender-components**: filter out invisible indicator spec -- **@visactor/vrender-components**: `measureTextSize` needs to take into account the fonts configured on the stage theme -- **@visactor/vrender-core**: fix issue with incremental draw -- **@visactor/vrender-core**: supply the `getTheme()` api for `IStage` - - - -[more detail about v0.17.13](https://github.com/VisActor/VRender/releases/tag/v0.17.13) - -# v0.17.12 - -2024-01-10 - -**🆕 New feature** -- **@visactor/vrender-components**: support fit strategy for indicator -- **marker**: mark point support confine. fix @Visactor/VChart[#1573](https://github.com/VisActor/VRender/issues/1573) -**🐛 Bug fix** -- **marker**: fix problem of no render when set visible attr and add valid judgment logic. fix@Visactor/Vchart[#1901](https://github.com/VisActor/VRender/issues/1901) -- **datazoom**: adaptive handler text layout. fix@Visactor/VChart[#1809](https://github.com/VisActor/VRender/issues/1809) -- **datazoom**: set pickable false when zoomLock. fix @Visactor/VChart[#1565](https://github.com/VisActor/VRender/issues/1565) -- **datazoom**: handler not follow mouse after resize. fix@Visactor/Vchart[#1490](https://github.com/VisActor/VRender/issues/1490) -- **@visactor/vrender-components**: arc outside label invisible with visible label line - - - -[more detail about v0.17.12](https://github.com/VisActor/VRender/releases/tag/v0.17.12) - -# v0.17.11 - -2024-01-05 - -**🆕 New feature** -- **@visactor/vrender-core**: add backgroundFit attribute -**🐛 Bug fix** -- **@visactor/vrender-core**: fix issue with position in html attribute -- fix: label invisible when baseMark visible is false - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.17.10...v0.17.11 - -[more detail about v0.17.11](https://github.com/VisActor/VRender/releases/tag/v0.17.11) - -# v0.17.10 - -2024-01-03 - -**🆕 New feature** -- **@visactor/vrender-components**: support `lastVisible` of LineAxis label -- **@visactor/vrender-kits**: support fillPickable and strokePickable for area, closed [#792](https://github.com/VisActor/VRender/issues/792) -- **@visactor/vrender-core**: support fillPickable and strokePickable for area, closed [#792](https://github.com/VisActor/VRender/issues/792) -- **@visactor/vrender-core**: support `lastVisible` of LineAxis label -- **@visactor/vrender**: support `lastVisible` of LineAxis label -**🐛 Bug fix** -- **@visactor/vrender-components**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -- **@visactor/vrender-components**: fix issue with legend symbol size -- **@visactor/vrender-components**: fixed height calculation issue after multi-layer axis text rotation -- **@visactor/vrender-core**: fix issue with area-line highperformance draw -- **@visactor/vrender-core**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -- **@visactor/vrender-core**: disable layer picker in interactive layer -- **@visactor/vrender**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -**🔖 other** -- **@visactor/vrender-components**: 'feat: support label line in label component' - - - -[more detail about v0.17.10](https://github.com/VisActor/VRender/releases/tag/v0.17.10) - -# v0.17.9 - -2024-01-03 - -**🐛 Bug fix** -- **@visactor/vrender-components**: fix label position when offset is 0 -- **@visactor/vrender-core**: fix issue with conical cache not work as expect - - - -[more detail about v0.17.9](https://github.com/VisActor/VRender/releases/tag/v0.17.9) - -# v0.17.8 - -2023-12-29 - -**🆕 New feature** -- **@visactor/vrender-components**: optimize outer label layout in tangential direction -- **@visactor/vrender-core**: support drawGraphicToCanvas -- **@visactor/vrender**: support drawGraphicToCanvas -**🐛 Bug fix** -- **@visactor/vrender-components**: when axis label space is 0, and axis tick' inside is true, the axis label's position is not correct -- **@visactor/vrender-components**: fix morphing of rect -- **@visactor/vrender-kits**: fix issue with mapToCanvasPoint in miniapp, closed [#828](https://github.com/VisActor/VRender/issues/828) -- **@visactor/vrender-core**: fix issue with rect.toCustomPath -- **@visactor/vrender-core**: fix issue with area segment with single point, closed [#801](https://github.com/VisActor/VRender/issues/801) -- **@visactor/vrender-core**: fix issue with new Function in miniapp -- **@visactor/vrender-core**: fix morphing of rect -- **@visactor/vrender-core**: fix issue with side-effect in some env -- **@visactor/vrender-core**: fix issue with check tt env -- **@visactor/vrender-core**: fix issue with cliped attribute in vertical text, closed [#827](https://github.com/VisActor/VRender/issues/827) -- **@visactor/vrender**: fix issue with area segment with single point, closed [#801](https://github.com/VisActor/VRender/issues/801) -- **@visactor/vrender**: fix morphing of rect -- **@visactor/vrender**: fix issue with side-effect in some env - - - -[more detail about v0.17.8](https://github.com/VisActor/VRender/releases/tag/v0.17.8) - -# v0.17.7 - -2023-12-21 - -**🐛 Bug fix** -- **@visactor/vrender-kits**: fix issue with create layer in miniapp env -- **@visactor/vrender-core**: fix issue with create layer in miniapp env - - - -[more detail about v0.17.7](https://github.com/VisActor/VRender/releases/tag/v0.17.7) - -# v0.17.6 - -2023-12-20 - -**What's Changed** -* Main by @neuqzxy in https://github.com/VisActor/VRender/pull/813 -* fix: fix issue with rect stroke contribution by @neuqzxy in https://github.com/VisActor/VRender/pull/814 -* [Auto release] release 0.17.6 by @github-actions in https://github.com/VisActor/VRender/pull/815 - - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.17.5...v0.17.6 - -[more detail about v0.17.6](https://github.com/VisActor/VRender/releases/tag/v0.17.6) - -# v0.17.5 - -2023-12-19 - -**🆕 New feature** -- **scrollbar**: dispatch scrollDown event -- **@visactor/vrender-components**: labelLine support animate -- **@visactor/vrender-components**: label don't create enter animate animationEnter while duration less than 0 -- **@visactor/vrender**: add disableAutoClipedPoptip attribute in text graphic -**🐛 Bug fix** -- **@visactor/vrender-components**: fix issue with arc animate with delayafter -- **@visactor/vrender-components**: fix issue with poptip circular dependencies -- **@visactor/vrender-core**: fix issue with plugin unregister -- **@visactor/vrender-core**: fix issue with text while whitespace is normal -- **@visactor/vrender**: fix cursor update error in multi-stage - - - -[more detail about v0.17.5](https://github.com/VisActor/VRender/releases/tag/v0.17.5) - -# v0.17.4 - -2023-12-15 - -**🐛 Bug fix** -- **datazoom**: symbol size problem -- **@visactor/vrender-core**: fix issue with arc imprecise bounds, closed [#728](https://github.com/VisActor/VRender/issues/728) - - - -[more detail about v0.17.4](https://github.com/VisActor/VRender/releases/tag/v0.17.4) - -# v0.17.3 - -2023-12-14 - -**🐛 Bug fix** -- **datazoom**: handler zindex to interaction error - - - -[more detail about v0.17.3](https://github.com/VisActor/VRender/releases/tag/v0.17.3) - -# v0.17.2 - -2023-12-14 - -**🆕 New feature** -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **@visactor/vrender-core**: rect3d support x1y1, fix -radius issue with rect -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -**🐛 Bug fix** -- **@visactor/vrender-components**: scrollbar slider width/height should not be negative -- **@visactor/vrender-components**: datazoom event block window event. fix @visactor/vchart[#1686](https://github.com/VisActor/VRender/issues/1686) -- **@visactor/vrender-components**: fix the issue of brushEnd trigger multiple times, related https://github.com/VisActor/VChart/issues/1694 -- **@visactor/vrender-core**: fix shadow pick issue -**⚡ Performance optimization** -- **@visactor/vrender-components**: optimize the `_handleStyle()` in legend - - - -[more detail about v0.17.2](https://github.com/VisActor/VRender/releases/tag/v0.17.2) - -# v0.17.1 - -2023-12-06 - -**🆕 New feature** -- **@visactor/vrender-kits**: support pickStrokeBuffer, closed [#758](https://github.com/VisActor/VRender/issues/758) -- **@visactor/vrender-core**: support pickStrokeBuffer, closed [#758](https://github.com/VisActor/VRender/issues/758) -**🐛 Bug fix** -- **@visactor/vrender-kits**: fix issue with rebind pick-contribution -- **@visactor/vrender-core**: fix issue in area chart with special points -- **@visactor/vrender-core**: fix issue with rebind pick-contribution -- **@visactor/vrender-core**: fix error with wrap text and normal whiteSpace text - - - -[more detail about v0.17.1](https://github.com/VisActor/VRender/releases/tag/v0.17.1) - -# v0.17.0 - -2023-11-30 - -**🆕 New feature** -- **@visactor/vrender-components**: optmize bounds performance -- **@visactor/vrender-kits**: rect support x1 and y1 -- **@visactor/vrender-kits**: optmize bounds performance -- **@visactor/vrender-core**: support disableCheckGraphicWidthOutRange to skip check if graphic out of range -- **@visactor/vrender-core**: rect support x1 and y1 -- **@visactor/vrender-core**: don't rewrite global reflect -- **@visactor/vrender-core**: text support background, closed [#711](https://github.com/VisActor/VRender/issues/711) -- **@visactor/vrender-core**: optmize bounds performance -- **@visactor/vrender**: don't rewrite global reflect -- **@visactor/vrender**: skip update bounds while render small node-tree, closed [#660](https://github.com/VisActor/VRender/issues/660) -- **@visactor/vrender**: optmize bounds performance -**🔨 Refactor** -- **@visactor/vrender-kits**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -- **@visactor/vrender-core**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -- **@visactor/vrender**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -**⚡ Performance optimization** -- **@visactor/vrender-components**: add option `skipDefault` to vrender-components -- **@visactor/vrender-core**: area support drawLinearAreaHighPerformance, closed [#672](https://github.com/VisActor/VRender/issues/672) - - - -[more detail about v0.17.0](https://github.com/VisActor/VRender/releases/tag/v0.17.0) - -# v0.16.18 - -2023-11-30 - -**🆕 New feature** -- **@visactor/vrender-components**: discrete legend's pager support position property -- **@visactor/vrender-core**: support suffixPosition, closed [#625](https://github.com/VisActor/VRender/issues/625) -- **@visactor/vrender**: support suffixPosition, closed [#625](https://github.com/VisActor/VRender/issues/625) -**🐛 Bug fix** -- **@visactor/vrender-kits**: doubletap should not be triggered when the target is different twice before and after -- **@visactor/vrender-core**: fix issue with attribute interpolate, closed [#741](https://github.com/VisActor/VRender/issues/741) -- **@visactor/vrender-core**: fix issue about calcuate bounds with shadow, closed [#474](https://github.com/VisActor/VRender/issues/474) -- **@visactor/vrender-core**: fix issue with white line in some dpr device, closed [#666](https://github.com/VisActor/VRender/issues/666) -**🔨 Refactor** -- **@visactor/vrender-components**: move getSizeHandlerPath out of sizlegend -- **@visactor/vrender-core**: event-related coordinate points do not require complex Point classes - - - -[more detail about v0.16.18](https://github.com/VisActor/VRender/releases/tag/v0.16.18) - -# v0.16.17 - -2023-11-23 - -**🆕 New feature** -- **@visactor/vrender-components**: support rich text for label, axis, marker,tooltip, indicator and title -- **@visactor/vrender-components**: add mode type of smartInvert -- **@visactor/vrender-components**: place more label for overlapPadding case -- **@visactor/vrender-kits**: support 'tap' gesture for Gesture plugin -- **@visactor/vrender-core**: add `event` config for Stage params, which can configure `clickInterval` and some other options in eventSystem -- **@visactor/vrender-core**: support fill and stroke while svg don't support, closed [#710](https://github.com/VisActor/VRender/issues/710) -**🐛 Bug fix** -- **@visactor/vrender-kits**: \`pickMode: 'imprecise'\` not work in polygon -- **@visactor/vrender-core**: richtext may throw error when textConfig is null -- **@visactor/vrender-core**: fix issue with image repeat, closed [#712](https://github.com/VisActor/VRender/issues/712) -- **@visactor/vrender-core**: fix issue with restore and save count not equal -**⚡ Performance optimization** -- **@visactor/vrender-core**: not setAttribute while background is not url, closed [#696](https://github.com/VisActor/VRender/issues/696) - - - -[more detail about v0.16.17](https://github.com/VisActor/VRender/releases/tag/v0.16.17) - -# v0.16.16 - -2023-11-17 - -**🐛 Bug fix** -- **@visactor/vrender-components**: fix the issue of legend item.shape can not set visible, related https://github.com/VisActor/VChart/issues/1508 -- **@visactor/vrender-core**: assign symbol rect function to old - - - -[more detail about v0.16.16](https://github.com/VisActor/VRender/releases/tag/v0.16.16) - -# v0.16.15 - -2023-11-16 - -**🐛 Bug fix** -- **@visactor/vrender-compoments**: legendItemHover and legendItemUnHover should trigger once - - - -[more detail about v0.16.15](https://github.com/VisActor/VRender/releases/tag/v0.16.15) - -# v0.16.14 - -2023-11-15 - -**🆕 New feature** -- **@visactor/vrender-components**: datazoom update callback supports new trigger tag param -- **@visactor/vrender-components**: support line/area label -- **@visactor/vrender-components**: lineHeight support string, which means percent -- **@visactor/vrender-core**: add round line symbol, closed [#1458](https://github.com/VisActor/VRender/issues/1458) -- **@visactor/vrender-core**: lineHeight support string, which means percent -**🐛 Bug fix** -- **@visactor/vrender-core**: fix issue with render while in scale transform - - - -[more detail about v0.16.14](https://github.com/VisActor/VRender/releases/tag/v0.16.14) - -# v0.16.13 - -2023-11-15 - -**🆕 New feature** -- **@visactor/vrender-core**: add preventRender function -- **@visactor/vrender-core**: merge wrap text function to text -**🐛 Bug fix** -- **@visactor/vrender-kits**: temp fix issue with lynx measuretext - - - -[more detail about v0.16.13](https://github.com/VisActor/VRender/releases/tag/v0.16.13) - -# v0.16.12 - -2023-11-07 - -**🆕 New feature** -- **@visactor/vrender-core**: optimize text increase animation -**🐛 Bug fix** -- **@visactor/vrender-components**: padding of title component -- **@visactor/vrender-components**: padding offset of AABBbounds -- **@visactor/vrender-kits**: fix node-canvas max count issue -- **@visactor/vrender-core**: fix node-canvas max count issue - - - -[more detail about v0.16.12](https://github.com/VisActor/VRender/releases/tag/v0.16.12) - -# v0.16.11 - -2023-11-07 - -**🐛 Bug fix** -- **@visactor/vrender-components**: optimize the auto-overlap of axis label, which use rotateBounds when text rotated, relate https://github.com/VisActor/VChart/issues/133 -- **@visactor/vrender-components**: flush should not sue width height -- **@visactor/vrender-components**: fix the lastvisible logic of axis's auto-hide -- **@visactor/vrender-kits**: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) -- **@visactor/vrender-core**: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) - - - -[more detail about v0.16.11](https://github.com/VisActor/VRender/releases/tag/v0.16.11) - -# v0.16.10 - -2023-11-02 - -**What's Changed** -* Sync main by @neuqzxy in https://github.com/VisActor/VRender/pull/640 -* fix: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) by @neuqzxy in https://github.com/VisActor/VRender/pull/635 -* Echance/axis auto rotate by @kkxxkk2019 in https://github.com/VisActor/VRender/pull/633 -* [Auto release] release 0.16.9 by @github-actions in https://github.com/VisActor/VRender/pull/641 - - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.16.9...v0.16.10 - -[more detail about v0.16.10](https://github.com/VisActor/VRender/releases/tag/v0.16.10) - -# v0.16.9 - -2023-10-27 - -**🆕 New feature** -- **@visactor/vrender-components**: add checkbox indeterminate state -- **label**: rect label support position `top-right`|`top-left`|`bottom-righ`|`bottom-left` -- **@visactor/vrender-core**: stage background support image -**🐛 Bug fix** -- **@visactor/vrender-components**: all the group container of marker do not trigger event -- **datazoom**: text bounds when visible is false. fix VisActor/VChart[#1281](https://github.com/VisActor/VRender/issues/1281) - - - -[more detail about v0.16.9](https://github.com/VisActor/VRender/releases/tag/v0.16.9) - -# v0.16.8 - -2023-10-23 - -**🐛 Bug fix** -- **@visactor/vrender-components**: fix the issue of error position of focus when legend item just has label - - - -[more detail about v0.16.8](https://github.com/VisActor/VRender/releases/tag/v0.16.8) - -# v0.16.7 - -2023-10-23 - -**🐛 Bug fix** -- **label**: fix the issue that `clampForce` does not work when`overlapPadding` is configured -- **@visactor/vrender-core**: fix issue with creating multi chart in miniapp - - - -[more detail about v0.16.7](https://github.com/VisActor/VRender/releases/tag/v0.16.7) - -# v0.16.6 - -2023-10-23 - -**🆕 New feature** -- **@visactor/vrender-components**: optimize the layout method of circle axis label -**🐛 Bug fix** -- **@visactor/vrender-components**: fix the layout issue of legend item because of the error logic of `focusStartX` - - - -[more detail about v0.16.6](https://github.com/VisActor/VRender/releases/tag/v0.16.6) - diff --git a/docs/assets/changelog/zh/changelog.md b/docs/assets/changelog/zh/changelog.md index bdf0cc2e..e69de29b 100644 --- a/docs/assets/changelog/zh/changelog.md +++ b/docs/assets/changelog/zh/changelog.md @@ -1,542 +0,0 @@ -# v0.17.18 - -2024-01-24 - - -**🆕 新增功能** - -- **@visactor/vrender-components**: adjust the timing for label customLayoutFunc invocation -- **@visactor/vrender-components**: label component will sync maxLineWidth to maxWidth in richText -- **@visactor/vrender-kits**: compatible canvas in lynx env -- **@visactor/vrender-core**: support backgroundCornerRadius - -**🐛 功能修复** - -- **@visactor/vrender-components**: event pos error when interactive in site -- **@visactor/vrender-kits**: fix issue with interface -- **@visactor/vrender-core**: fix issue with multiline text textBaseline, closed [#886](https://github.com/VisActor/VRender/issues/886) -- **@visactor/vrender-core**: fix issue with union empty bounds -- **@visactor/vrender-core**: richtext.textConfig supports number type text - - - -[更多详情请查看 v0.17.18](https://github.com/VisActor/VRender/releases/tag/v0.17.18) - -# v0.17.17 - -2024-01-22 - - -**🆕 新增功能** - -- **@visactor/vrender-core**: html only append dom inside body -- **@visactor/vrender-core**: color support str gradient color -- **@visactor/vrender**: color support str gradient color - -**🐛 功能修复** - -- **@visactor/vrender-components**: title support multiline -- **@visactor/vrender-kits**: fix issue with loaded tree-shaking -- **@visactor/vrender-core**: fix issue with rerun getTextBounds -- **@visactor/vrender-core**: fix issue with set image -- **@visactor/vrender-core**: fix issue with loaded tree-shaking - - - -[更多详情请查看 v0.17.17](https://github.com/VisActor/VRender/releases/tag/v0.17.17) - -# v0.17.16 - -2024-01-18 - - -**🆕 新增功能** - -- **@visactor/vrender-core**: enable pass supportsPointerEvents and supportsTouchEvents - -**🐛 功能修复** - -- **@visactor/vrender-components**: when no brush is active, brush should not call stopPropagation() - -[更多详情请查看 v0.17.16](https://github.com/VisActor/VRender/releases/tag/v0.17.16) - -# v0.17.15 - -2024-01-17 - - -**🆕 新增功能** - -- **@visactor/vrender-components**: support boolean config in label -- **@visactor/vrender-core**: add supportsTouchEvents and supportsPointerEvents params - -**🐛 功能修复** - -- **@visactor/vrender-components**: fix the flush of axis when axis label has rotate angle -- **@visactor/vrender-components**: arc label line not shown -- **@visactor/vrender-components**: error happens in line-label when line has no points -- **@visactor/vrender-core**: fix issue with html attribute -- **@visactor/vrender-core**: fix issue with env-check -- **@visactor/vrender-core**: fix issue with text background opacity - - - -[更多详情请查看 v0.17.15](https://github.com/VisActor/VRender/releases/tag/v0.17.15) - -# v0.17.14 - -2024-01-12 - -**🐛 功能修复** -- **@visactor/vrender-core**: fix `splitRect` when rect has `x1` or `y1` -- **@visactor/vrender**: fix `splitRect` when rect has `x1` or `y1` - - -[更多详情请查看 v0.17.14](https://github.com/VisActor/VRender/releases/tag/v0.17.14) - -# v0.17.13 - -2024-01-10 - -**🆕 新增功能** -- **@visactor/vrender-core**: background support opacity -**🐛 功能修复** -- **@visactor/vrender-components**: filter out invisible indicator spec -- **@visactor/vrender-components**: `measureTextSize` needs to take into account the fonts configured on the stage theme -- **@visactor/vrender-core**: fix issue with incremental draw -- **@visactor/vrender-core**: supply the `getTheme()` api for `IStage` - - - -[更多详情请查看 v0.17.13](https://github.com/VisActor/VRender/releases/tag/v0.17.13) - -# v0.17.12 - -2024-01-10 - -**🆕 新增功能** -- **@visactor/vrender-components**: support fit strategy for indicator -- **marker**: mark point support confine. fix @Visactor/VChart[#1573](https://github.com/VisActor/VRender/issues/1573) -**🐛 功能修复** -- **marker**: fix problem of no render when set visible attr and add valid judgment logic. fix@Visactor/Vchart[#1901](https://github.com/VisActor/VRender/issues/1901) -- **datazoom**: adaptive handler text layout. fix@Visactor/VChart[#1809](https://github.com/VisActor/VRender/issues/1809) -- **datazoom**: set pickable false when zoomLock. fix @Visactor/VChart[#1565](https://github.com/VisActor/VRender/issues/1565) -- **datazoom**: handler not follow mouse after resize. fix@Visactor/Vchart[#1490](https://github.com/VisActor/VRender/issues/1490) -- **@visactor/vrender-components**: arc outside label invisible with visible label line - - - -[更多详情请查看 v0.17.12](https://github.com/VisActor/VRender/releases/tag/v0.17.12) - -# v0.17.11 - -2024-01-05 - -**🆕 新增功能** -- **@visactor/vrender-core**: add backgroundFit attribute -**🐛 功能修复** -- **@visactor/vrender-core**: fix issue with position in html attribute -- fix: label invisible when baseMark visible is false - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.17.10...v0.17.11 - -[更多详情请查看 v0.17.11](https://github.com/VisActor/VRender/releases/tag/v0.17.11) - -# v0.17.10 - -2024-01-03 - -**🆕 新增功能** -- **@visactor/vrender-components**: support `lastVisible` of LineAxis label -- **@visactor/vrender-kits**: support fillPickable and strokePickable for area, closed [#792](https://github.com/VisActor/VRender/issues/792) -- **@visactor/vrender-core**: support fillPickable and strokePickable for area, closed [#792](https://github.com/VisActor/VRender/issues/792) -- **@visactor/vrender-core**: support `lastVisible` of LineAxis label -- **@visactor/vrender**: support `lastVisible` of LineAxis label -**🐛 功能修复** -- **@visactor/vrender-components**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -- **@visactor/vrender-components**: fix issue with legend symbol size -- **@visactor/vrender-components**: fixed height calculation issue after multi-layer axis text rotation -- **@visactor/vrender-core**: fix issue with area-line highperformance draw -- **@visactor/vrender-core**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -- **@visactor/vrender-core**: disable layer picker in interactive layer -- **@visactor/vrender**: fix the auto limit width of label when the label has vertical `direction` in orient top or bottom -**🔖 其他** -- **@visactor/vrender-components**: 'feat: support label line in label component' - - - -[更多详情请查看 v0.17.10](https://github.com/VisActor/VRender/releases/tag/v0.17.10) - -# v0.17.9 - -2024-01-03 - -**🐛 功能修复** -- **@visactor/vrender-components**: fix label position when offset is 0 -- **@visactor/vrender-core**: fix issue with conical cache not work as expect - - - -[更多详情请查看 v0.17.9](https://github.com/VisActor/VRender/releases/tag/v0.17.9) - -# v0.17.8 - -2023-12-29 - -**🆕 新增功能** -- **@visactor/vrender-components**: optimize outer label layout in tangential direction -- **@visactor/vrender-core**: support drawGraphicToCanvas -- **@visactor/vrender**: support drawGraphicToCanvas -**🐛 功能修复** -- **@visactor/vrender-components**: when axis label space is 0, and axis tick' inside is true, the axis label's position is not correct -- **@visactor/vrender-components**: fix morphing of rect -- **@visactor/vrender-kits**: fix issue with mapToCanvasPoint in miniapp, closed [#828](https://github.com/VisActor/VRender/issues/828) -- **@visactor/vrender-core**: fix issue with rect.toCustomPath -- **@visactor/vrender-core**: fix issue with area segment with single point, closed [#801](https://github.com/VisActor/VRender/issues/801) -- **@visactor/vrender-core**: fix issue with new Function in miniapp -- **@visactor/vrender-core**: fix morphing of rect -- **@visactor/vrender-core**: fix issue with side-effect in some env -- **@visactor/vrender-core**: fix issue with check tt env -- **@visactor/vrender-core**: fix issue with cliped attribute in vertical text, closed [#827](https://github.com/VisActor/VRender/issues/827) -- **@visactor/vrender**: fix issue with area segment with single point, closed [#801](https://github.com/VisActor/VRender/issues/801) -- **@visactor/vrender**: fix morphing of rect -- **@visactor/vrender**: fix issue with side-effect in some env - - - -[更多详情请查看 v0.17.8](https://github.com/VisActor/VRender/releases/tag/v0.17.8) - -# v0.17.7 - -2023-12-21 - -**🐛 功能修复** -- **@visactor/vrender-kits**: fix issue with create layer in miniapp env -- **@visactor/vrender-core**: fix issue with create layer in miniapp env - - - -[更多详情请查看 v0.17.7](https://github.com/VisActor/VRender/releases/tag/v0.17.7) - -# v0.17.6 - -2023-12-20 - -**What's Changed** -* Main by @neuqzxy in https://github.com/VisActor/VRender/pull/813 -* fix: fix issue with rect stroke contribution by @neuqzxy in https://github.com/VisActor/VRender/pull/814 -* [Auto release] release 0.17.6 by @github-actions in https://github.com/VisActor/VRender/pull/815 - - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.17.5...v0.17.6 - -[更多详情请查看 v0.17.6](https://github.com/VisActor/VRender/releases/tag/v0.17.6) - -# v0.17.5 - -2023-12-19 - -**🆕 新增功能** -- **scrollbar**: dispatch scrollDown event -- **@visactor/vrender-components**: labelLine support animate -- **@visactor/vrender-components**: label don't create enter animate animationEnter while duration less than 0 -- **@visactor/vrender**: add disableAutoClipedPoptip attribute in text graphic -**🐛 功能修复** -- **@visactor/vrender-components**: fix issue with arc animate with delayafter -- **@visactor/vrender-components**: fix issue with poptip circular dependencies -- **@visactor/vrender-core**: fix issue with plugin unregister -- **@visactor/vrender-core**: fix issue with text while whitespace is normal -- **@visactor/vrender**: fix cursor update error in multi-stage - - - -[更多详情请查看 v0.17.5](https://github.com/VisActor/VRender/releases/tag/v0.17.5) - -# v0.17.4 - -2023-12-15 - -**🐛 功能修复** -- **datazoom**: symbol size problem -- **@visactor/vrender-core**: fix issue with arc imprecise bounds, closed [#728](https://github.com/VisActor/VRender/issues/728) - - - -[更多详情请查看 v0.17.4](https://github.com/VisActor/VRender/releases/tag/v0.17.4) - -# v0.17.3 - -2023-12-14 - -**🐛 功能修复** -- **datazoom**: handler zindex to interaction error - - - -[更多详情请查看 v0.17.3](https://github.com/VisActor/VRender/releases/tag/v0.17.3) - -# v0.17.2 - -2023-12-14 - -**🆕 新增功能** -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -- **@visactor/vrender-core**: rect3d support x1y1, fix -radius issue with rect -- **dataZoom**: add mask to modify hot zone. feat @visactor/vchart[#1415](https://github.com/VisActor/VRender/issues/1415)' -**🐛 功能修复** -- **@visactor/vrender-components**: scrollbar slider width/height should not be negative -- **@visactor/vrender-components**: datazoom event block window event. fix @visactor/vchart[#1686](https://github.com/VisActor/VRender/issues/1686) -- **@visactor/vrender-components**: fix the issue of brushEnd trigger multiple times, related https://github.com/VisActor/VChart/issues/1694 -- **@visactor/vrender-core**: fix shadow pick issue -**⚡ 性能优化** -- **@visactor/vrender-components**: optimize the `_handleStyle()` in legend - - - -[更多详情请查看 v0.17.2](https://github.com/VisActor/VRender/releases/tag/v0.17.2) - -# v0.17.1 - -2023-12-06 - -**🆕 新增功能** -- **@visactor/vrender-kits**: support pickStrokeBuffer, closed [#758](https://github.com/VisActor/VRender/issues/758) -- **@visactor/vrender-core**: support pickStrokeBuffer, closed [#758](https://github.com/VisActor/VRender/issues/758) -**🐛 功能修复** -- **@visactor/vrender-kits**: fix issue with rebind pick-contribution -- **@visactor/vrender-core**: fix issue in area chart with special points -- **@visactor/vrender-core**: fix issue with rebind pick-contribution -- **@visactor/vrender-core**: fix error with wrap text and normal whiteSpace text - - - -[更多详情请查看 v0.17.1](https://github.com/VisActor/VRender/releases/tag/v0.17.1) - -# v0.17.0 - -2023-11-30 - -**🆕 新增功能** -- **@visactor/vrender-components**: optmize bounds performance -- **@visactor/vrender-kits**: rect support x1 and y1 -- **@visactor/vrender-kits**: optmize bounds performance -- **@visactor/vrender-core**: support disableCheckGraphicWidthOutRange to skip check if graphic out of range -- **@visactor/vrender-core**: rect support x1 and y1 -- **@visactor/vrender-core**: don't rewrite global reflect -- **@visactor/vrender-core**: text support background, closed [#711](https://github.com/VisActor/VRender/issues/711) -- **@visactor/vrender-core**: optmize bounds performance -- **@visactor/vrender**: don't rewrite global reflect -- **@visactor/vrender**: skip update bounds while render small node-tree, closed [#660](https://github.com/VisActor/VRender/issues/660) -- **@visactor/vrender**: optmize bounds performance -**🔨 功能重构** -- **@visactor/vrender-kits**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -- **@visactor/vrender-core**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -- **@visactor/vrender**: refact inversify completely, closed [#657](https://github.com/VisActor/VRender/issues/657) -**⚡ 性能优化** -- **@visactor/vrender-components**: add option `skipDefault` to vrender-components -- **@visactor/vrender-core**: area support drawLinearAreaHighPerformance, closed [#672](https://github.com/VisActor/VRender/issues/672) - - - -[更多详情请查看 v0.17.0](https://github.com/VisActor/VRender/releases/tag/v0.17.0) - -# v0.16.18 - -2023-11-30 - -**🆕 新增功能** -- **@visactor/vrender-components**: discrete legend's pager support position property -- **@visactor/vrender-core**: support suffixPosition, closed [#625](https://github.com/VisActor/VRender/issues/625) -- **@visactor/vrender**: support suffixPosition, closed [#625](https://github.com/VisActor/VRender/issues/625) -**🐛 功能修复** -- **@visactor/vrender-kits**: doubletap should not be triggered when the target is different twice before and after -- **@visactor/vrender-core**: fix issue with attribute interpolate, closed [#741](https://github.com/VisActor/VRender/issues/741) -- **@visactor/vrender-core**: fix issue about calcuate bounds with shadow, closed [#474](https://github.com/VisActor/VRender/issues/474) -- **@visactor/vrender-core**: fix issue with white line in some dpr device, closed [#666](https://github.com/VisActor/VRender/issues/666) -**🔨 功能重构** -- **@visactor/vrender-components**: move getSizeHandlerPath out of sizlegend -- **@visactor/vrender-core**: event-related coordinate points do not require complex Point classes - - - -[更多详情请查看 v0.16.18](https://github.com/VisActor/VRender/releases/tag/v0.16.18) - -# v0.16.17 - -2023-11-23 - -**🆕 新增功能** -- **@visactor/vrender-components**: support rich text for label, axis, marker,tooltip, indicator and title -- **@visactor/vrender-components**: add mode type of smartInvert -- **@visactor/vrender-components**: place more label for overlapPadding case -- **@visactor/vrender-kits**: support 'tap' gesture for Gesture plugin -- **@visactor/vrender-core**: add `event` config for Stage params, which can configure `clickInterval` and some other options in eventSystem -- **@visactor/vrender-core**: support fill and stroke while svg don't support, closed [#710](https://github.com/VisActor/VRender/issues/710) -**🐛 功能修复** -- **@visactor/vrender-kits**: \`pickMode: 'imprecise'\` not work in polygon -- **@visactor/vrender-core**: richtext may throw error when textConfig is null -- **@visactor/vrender-core**: fix issue with image repeat, closed [#712](https://github.com/VisActor/VRender/issues/712) -- **@visactor/vrender-core**: fix issue with restore and save count not equal -**⚡ 性能优化** -- **@visactor/vrender-core**: not setAttribute while background is not url, closed [#696](https://github.com/VisActor/VRender/issues/696) - - - -[更多详情请查看 v0.16.17](https://github.com/VisActor/VRender/releases/tag/v0.16.17) - -# v0.16.16 - -2023-11-17 - -**🐛 功能修复** -- **@visactor/vrender-components**: fix the issue of legend item.shape can not set visible, related https://github.com/VisActor/VChart/issues/1508 -- **@visactor/vrender-core**: assign symbol rect function to old - - - -[更多详情请查看 v0.16.16](https://github.com/VisActor/VRender/releases/tag/v0.16.16) - -# v0.16.15 - -2023-11-16 - -**🐛 功能修复** -- **@visactor/vrender-compoments**: legendItemHover and legendItemUnHover should trigger once - - - -[更多详情请查看 v0.16.15](https://github.com/VisActor/VRender/releases/tag/v0.16.15) - -# v0.16.14 - -2023-11-15 - -**🆕 新增功能** -- **@visactor/vrender-components**: datazoom update callback supports new trigger tag param -- **@visactor/vrender-components**: support line/area label -- **@visactor/vrender-components**: lineHeight support string, which means percent -- **@visactor/vrender-core**: add round line symbol, closed [#1458](https://github.com/VisActor/VRender/issues/1458) -- **@visactor/vrender-core**: lineHeight support string, which means percent -**🐛 功能修复** -- **@visactor/vrender-core**: fix issue with render while in scale transform - - - -[更多详情请查看 v0.16.14](https://github.com/VisActor/VRender/releases/tag/v0.16.14) - -# v0.16.13 - -2023-11-15 - -**🆕 新增功能** -- **@visactor/vrender-core**: add preventRender function -- **@visactor/vrender-core**: merge wrap text function to text -**🐛 功能修复** -- **@visactor/vrender-kits**: temp fix issue with lynx measuretext - - - -[更多详情请查看 v0.16.13](https://github.com/VisActor/VRender/releases/tag/v0.16.13) - -# v0.16.12 - -2023-11-07 - -**🆕 新增功能** -- **@visactor/vrender-core**: optimize text increase animation -**🐛 功能修复** -- **@visactor/vrender-components**: padding of title component -- **@visactor/vrender-components**: padding offset of AABBbounds -- **@visactor/vrender-kits**: fix node-canvas max count issue -- **@visactor/vrender-core**: fix node-canvas max count issue - - - -[更多详情请查看 v0.16.12](https://github.com/VisActor/VRender/releases/tag/v0.16.12) - -# v0.16.11 - -2023-11-07 - -**🐛 功能修复** -- **@visactor/vrender-components**: optimize the auto-overlap of axis label, which use rotateBounds when text rotated, relate https://github.com/VisActor/VChart/issues/133 -- **@visactor/vrender-components**: flush should not sue width height -- **@visactor/vrender-components**: fix the lastvisible logic of axis's auto-hide -- **@visactor/vrender-kits**: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) -- **@visactor/vrender-core**: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) - - - -[更多详情请查看 v0.16.11](https://github.com/VisActor/VRender/releases/tag/v0.16.11) - -# v0.16.10 - -2023-11-02 - -**What's Changed** -* Sync main by @neuqzxy in https://github.com/VisActor/VRender/pull/640 -* fix: fix issue with xul and html attribute, closed [#634](https://github.com/VisActor/VRender/issues/634) by @neuqzxy in https://github.com/VisActor/VRender/pull/635 -* Echance/axis auto rotate by @kkxxkk2019 in https://github.com/VisActor/VRender/pull/633 -* [Auto release] release 0.16.9 by @github-actions in https://github.com/VisActor/VRender/pull/641 - - -**Full Changelog**: https://github.com/VisActor/VRender/compare/v0.16.9...v0.16.10 - -[更多详情请查看 v0.16.10](https://github.com/VisActor/VRender/releases/tag/v0.16.10) - -# v0.16.9 - -2023-10-27 - -**🆕 新增功能** -- **@visactor/vrender-components**: add checkbox indeterminate state -- **label**: rect label support position `top-right`|`top-left`|`bottom-righ`|`bottom-left` -- **@visactor/vrender-core**: stage background support image -**🐛 功能修复** -- **@visactor/vrender-components**: all the group container of marker do not trigger event -- **datazoom**: text bounds when visible is false. fix VisActor/VChart[#1281](https://github.com/VisActor/VRender/issues/1281) - - - -[更多详情请查看 v0.16.9](https://github.com/VisActor/VRender/releases/tag/v0.16.9) - -# v0.16.8 - -2023-10-23 - -**🐛 功能修复** -- **@visactor/vrender-components**: fix the issue of error position of focus when legend item just has label - - - -[更多详情请查看 v0.16.8](https://github.com/VisActor/VRender/releases/tag/v0.16.8) - -# v0.16.7 - -2023-10-23 - -**🐛 功能修复** -- **label**: fix the issue that `clampForce` does not work when`overlapPadding` is configured -- **@visactor/vrender-core**: fix issue with creating multi chart in miniapp - - - -[更多详情请查看 v0.16.7](https://github.com/VisActor/VRender/releases/tag/v0.16.7) - -# v0.16.6 - -2023-10-23 - -**🆕 新增功能** -- **@visactor/vrender-components**: optimize the layout method of circle axis label -**🐛 功能修复** -- **@visactor/vrender-components**: fix the layout issue of legend item because of the error logic of `focusStartX` - - - -[更多详情请查看 v0.16.6](https://github.com/VisActor/VRender/releases/tag/v0.16.6) - diff --git a/packages/calculator/package.json b/packages/calculator/package.json index c9482560..6ec7aa1b 100644 --- a/packages/calculator/package.json +++ b/packages/calculator/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/calculator", - "version": "1.2.4", + "version": "1.2.5", "description": "SQL-like query executor with DSL", "main": "lib", "module": "es", diff --git a/packages/chart-advisor/.eslintrc.cjs b/packages/chart-advisor/.eslintrc.cjs new file mode 100644 index 00000000..f3be945a --- /dev/null +++ b/packages/chart-advisor/.eslintrc.cjs @@ -0,0 +1,18 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@internal/eslint-config/profile/react'], + parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.eslint.json' }, + // ignorePatterns: [], + env: { + browser: true, + es2021: true, + node: true, + jest: true + }, + rules: { + '@typescript-eslint/no-unused-vars': 'warn', + 'react/display-name': 'off', + 'no-console': 'warn' + } +}; diff --git a/packages/chart-advisor/LICENSE b/packages/chart-advisor/LICENSE new file mode 100644 index 00000000..308f98cc --- /dev/null +++ b/packages/chart-advisor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Bytedance Ltd. and/or its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/chart-advisor/README.md b/packages/chart-advisor/README.md new file mode 100644 index 00000000..9c936565 --- /dev/null +++ b/packages/chart-advisor/README.md @@ -0,0 +1,154 @@ + +# Chart Advisor + +## How to use + +``` +chartAdvisor(originDataset: DataItem[], dimensionList:Field[], measureList:Field[], aliasMap: AliasMap) +``` + +Enter the dataset and the information about the dimension and indicator fields, it returns the current recommended chart type and field allocation. + +## Example + +``` +const dataset=[{ "210816110721021": "Furniture", "210816110721021": "14138" }, { "210816110721021": "Office supplies", "210816110721021": "34611" }, { "210816110721021": "Technology", "210816110721021": "12637" }] + +const dimensionList=[{ "uniqueId": 210816110721021, "type": "textual" }] + +const measureList=[{ "uniqueId": 210816110721022, "type": "numerical" }] + +const aliasMap={ "210816110721021": "Category", "210816110721022": "Number" } + +const result = advisor.calAdvisedChart(dataset, dimensionList, measureList, aliasMap) + +console.log(result) +``` + +The output of the above code is: + +``` +{ +chartType: 'column', +cell: [ +{ +x: ['210816110721021'], +y: ['210816110721022'], +row: [], +column: [], +color: [], +size: [], +angle: [] +} +], +colorItems: [], +dataset: [[[[{"210816110721021":"Furniture","210816110721022":"14138"},{"210816110721021":"Office supplies","210816110721022":"34611"},{"210816110721021":"Technology","210816110721022":"12637"}]]]], +aliasMap: { '210816110721021': 'Category', '210816110721022': 'Number' } +} +``` + +The above data can be used directly to generate vizData. + +The final generated chart is: + + + +## Parameter explanation: + +originDataset: The original data set, which is a list, each element is a DataItem, representing a row of data, the format is {uniqueID1: value1, uniqueID2: value2, ...}, where uniqueID is the id of the field, value is the value of this row data in that field. + +``` +type DataItem = { +[key: number]: string +} +``` + +dimensionList: Collection of dimension information, which is a list, each element is a Field. +measureList: The collection of indicator information, which is list, each element is a Field. + +``` +type Field={ +uniqueId: number; //id of the field +type: string; //type of the field (number, string, date) +} +``` + +alisMap: Field alias table, consistent with the aliasMap in vizData. + +``` +type AliasMap = { +[key: number]: string; +}; +``` + +## Return value: + +Returns AdviseResult. The currently supported chart types are listed in ChartType. + +``` +type AdviseResult={ +chartType: ChartType; //chartType in vizData +cell:Cell; //cell in vizData, +colorItems:[] //colorItems in vizData +dataset: DataItem[]; //The processed dataset used to generate vizData +aliasMap: AliasMap; //The processed field alias table used to generate vizData +} +``` + +Explanation of each type: + +``` +enum ChartType { +/** Table */ +TABLE = 'table', + +/** Bar chart */ +COLUMN = 'column', +/** Percentage Bar chart */ +COLUMN_PERCENT = 'column_percent', +/** Parallel Bar chart */ +COLUMN_PARALLEL = 'column_parallel', + +/** Line chart */ +LINE = 'line', + +/** Pie chart */ +PIE = 'pie', + +/** Scatter plot */ +SCATTER = 'scatter', + +/** Combined Bar chart */ +COMBINECOLUMN = 'combineColumn', //combined chart composed of multiple bar charts + +/** Combined z line chart */ +COMBINELINE = 'combineLine', //combined chart composed of multiple line charts + +/** Measure card */ +MEASURE_CARD = 'measure_card', + +/** Word cloud */ +WORD_CLOUD = 'word_cloud', +} + +interface Cell { +column?: UniqueId[]; +row?: UniqueId[]; +x?: UniqueId[]; +y?: UniqueId[]; +group?: UniqueId[]; +color?: UniqueId[]; +size?: UniqueId[]; +shape?: UniqueId[]; +angle?: UniqueId[]; +radius?: UniqueId[]; +text?: UniqueId[]; +value?: UniqueId[]; +tooltip?: UniqueId[]; + +// Dimension expansion information (Cartesian product) +cartesianInfo?: CartesianInfo; +// Indicator expansion information (Indicator flattening) +foldInfo?: FoldInfo; +} +``` diff --git a/packages/chart-advisor/exampleChart.png b/packages/chart-advisor/exampleChart.png new file mode 100644 index 00000000..702bb060 Binary files /dev/null and b/packages/chart-advisor/exampleChart.png differ diff --git a/packages/chart-advisor/package.json b/packages/chart-advisor/package.json new file mode 100644 index 00000000..930b30a7 --- /dev/null +++ b/packages/chart-advisor/package.json @@ -0,0 +1,52 @@ +{ + "name": "@visactor/chart-advisor", + "version": "1.2.5", + "description": "图表推荐模块", + "main": "lib", + "module": "es", + "types": "es", + "sideEffects": false, + "exports": { + ".": { + "import": "./es/index.js", + "require": "./lib/index.js" + } + }, + "files": [ + "README.md", + "lib", + "es", + "build" + ], + "scripts": { + "clean": "rimraf es lib dist build *.tsbuildinfo", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "test": "jest --passWithNoTests", + "test:coverage": "jest --coverage", + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm-run-all clean --parallel build:esm build:cjs" + }, + "author": "", + "license": "MIT", + "dependencies": { + "lodash": "4.17.21" + }, + "devDependencies": { + "@types/jest": "^26.0.0", + "@types/lodash": "4.14.182", + "@types/node": "*", + "jest": "^26.0.0", + "ts-jest": "^26.0.0", + "typescript": "4.9.5", + "undici-types": "^5.27.2", + "@typescript-eslint/eslint-plugin": "5.30.0", + "@typescript-eslint/parser": "5.30.0", + "npm-run-all": "^4.1.5", + "rimraf": "^3.0.2", + "@internal/ts-config": "workspace:*", + "@internal/eslint-config": "workspace:*", + "@internal/bundler": "workspace:*", + "@rushstack/eslint-patch": "~1.1.4" + } +} diff --git a/packages/chart-advisor/src/constant.ts b/packages/chart-advisor/src/constant.ts new file mode 100644 index 00000000..3a46fa00 --- /dev/null +++ b/packages/chart-advisor/src/constant.ts @@ -0,0 +1,26 @@ +import { UniqueId } from './type'; +//后端传来的平坦化数据集中,和前端的设置不一致 +export const FOLD_NAME: UniqueId = 10001; +export const FOLD_VALUE: UniqueId = 10002; +export const FOLD_VALUE_MAIN: UniqueId = 10011; +export const FOLD_VALUE_SUB: UniqueId = 10012; + +export const COLOR_FIELD: UniqueId = 20001; +export const GROUP_FIELD: UniqueId = 30001; + +// export const MAX_PIVOT_ROW: number = 0 //允许的最大透视行数 +// export const MAX_PIVOT_COLUMN: number = 0 //允许的最大透视列数 + +// X轴刻度数 +export const X_MAX_COUNT = 5000; +// 数据点数 +export const MAX_POINT_COUNT = 100000; +// 图例个数 +export const LEGEND_MAX_COUNT = 1000; +// 图例个数超过时数据点的限制 +export const LEGEND_MAX_POINT_COUNT = 200; + +export const MIN_BAR_NUMBER = 2; +export const MAX_BAR_NUMBER = 30; + +export const APPLY_PIVOT = false; //透视开关 diff --git a/packages/chart-advisor/src/dataUtil.ts b/packages/chart-advisor/src/dataUtil.ts new file mode 100644 index 00000000..62f47097 --- /dev/null +++ b/packages/chart-advisor/src/dataUtil.ts @@ -0,0 +1,70 @@ +import { cloneDeep, isNil, uniq, isNaN } from 'lodash'; +import { DataTypeName } from './type'; +//将vizData中的dataset数组展开 后端版本可直接获取到dataSource,不用执行此方法 +const restoreDataItem = item => { + if (!Array.isArray(item)) { + return item; + } + + return item.reduce((prev, cur) => prev.concat(restoreDataItem(cur)), []); +}; +export const restoreDatasets = dataset => restoreDataItem(dataset); + +// + +//计算平均数 +export const calMean = dataset => { + const { data } = dataset; + const dataNotNull = data.filter(each => !isNil(each) && !isNaN(each)); + + const sum = dataNotNull.reduce((prev, cur) => prev + cur, 0); + const { length } = data; + return sum / length; +}; +//计算数据集的标准差 +export const calStandardDeviation = dataset => { + const { data } = dataset; + if (data.length === 1) return 0; + + const dataNotNull = data.filter(each => !isNil(each) && !isNaN(each)); + + const mean = dataset.mean ? dataset.mean : calMean(dataset); + const sumpow = dataNotNull.reduce((prev, cur) => prev + (cur - mean) ** 2, 0); + const { length } = data; + return Math.sqrt(sumpow / (length - 1)); +}; + +//计算变异系数 +export const calCoefficient = dataset => { + const mean = dataset.mean ? dataset.mean : calMean(dataset); + //平均数=0时,变异系数无意义 + const standardDev = dataset.standardDev ? dataset.standardDev : calStandardDeviation(dataset); + if (mean !== 0) { + return standardDev / mean; + } else { + return undefined; + } +}; + +// 升序排序 +const asc = arr => arr.sort((a, b) => a - b); + +export const calQuantile = (dataset, q) => { + const { data = [] } = dataset; + + // 取绝对值且过滤掉 0 的 + const sorted = asc(cloneDeep(data.map(Math.abs))).filter(each => each && each > 0); + const pos = (sorted.length - 1) * q; + const base = Math.floor(pos); + const rest = pos - base; + if (sorted[base + 1] !== undefined) { + return sorted[base] + rest * (sorted[base + 1] - sorted[base]); + } else { + return sorted[base]; + } +}; + +//数组去重 +export const unique = arr => uniq(arr); + +export const isTemporal = (type: DataTypeName) => type === 'date'; diff --git a/packages/chart-advisor/src/fieldAssign.ts b/packages/chart-advisor/src/fieldAssign.ts new file mode 100644 index 00000000..8d08f8e9 --- /dev/null +++ b/packages/chart-advisor/src/fieldAssign.ts @@ -0,0 +1,596 @@ +import { clone, cloneDeep } from 'lodash'; +import { AutoChartCell } from './type'; +import * as dataUtils from './dataUtil'; + +import { AliasMap, UniqueId, DataItem, Dataset } from './type'; +import { + productLength, + legendProduct, + getDomainFromDataset, + retainDatasetField, + getCartesianInfo, + getFoldInfo, + fold, + removeDatasetField +} from './fieldUtils'; + +import { + FOLD_NAME, + FOLD_VALUE, + COLOR_FIELD, + GROUP_FIELD, + FOLD_VALUE_MAIN, + FOLD_VALUE_SUB, + X_MAX_COUNT, + MAX_POINT_COUNT, + LEGEND_MAX_COUNT, + LEGEND_MAX_POINT_COUNT +} from './constant'; + +/*自动分配策略: +当维度数大于1小于3时,前面的字段先用来形成透视,余下字段中的第一个用来做x轴。 +当透视达到1x1后,第三个字段用来做x轴,其余字段按规则分配到视觉通道中, +最后多余的字段用来形成笛卡尔积 +这样做的目的是: +1. 从外往里进行透视更加自然 +2. 改变颜色的影响远大于改变x轴的影响 +这与tableau的策略是一致的 +目前先限制1x1的透视,后续性能提升后透视的层数可以增加 +*/ + +/* +为支持透视的图表分配字段(条形图、柱状图) +*/ +export const assignPivotCharts = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + aliasMapOld: AliasMap, + MAX_PIVOT_ROW: number, + MAX_PIVOT_COLUMN: number +) => { + const aliasMap = cloneDeep(aliasMapOld); + let dataset = cloneDeep(originDataset); + const cell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [] + }; + + if (dimList.length === 0 || measureList.length === 0) { + return { cell, dataset }; + } + + let nowDimIndex = 0; + + //首先分配透视字段 + while ( + (cell.row.length < MAX_PIVOT_ROW || cell.column.length < MAX_PIVOT_COLUMN) && + nowDimIndex + 1 < dimList.length //还有剩余字段,可以分配透视维度 + ) { + if (cell.row.length <= cell.column.length) { + cell.row.push(dimList[nowDimIndex]); + } else { + cell.column.push(dimList[nowDimIndex]); + } + nowDimIndex++; + } + + //剩余第一个维度分配到x轴 + cell.x.push(dimList[nowDimIndex]); + nowDimIndex++; + + const colorFields = []; + + //其余字段做笛卡尔积分配到颜色 + colorFields.push(...dimList.slice(nowDimIndex)); + + const measuresListLength: number = measureList.length; + // 提取 y 字段(度量值) + let colorFieldsIncludeMeasure = false; + if (measuresListLength > 1) { + // 多度量 + aliasMap[FOLD_NAME] = `指标名称`; + aliasMap[FOLD_VALUE] = `指标值`; + colorFields.push(FOLD_NAME); + colorFieldsIncludeMeasure = true; + cell.y.push(FOLD_VALUE); + } else if (measuresListLength === 1) { + // 单度量 + cell.y.push(measureList[0]); + } + + // 提取 color分组字段 (维度其他项 以及 度量名称) + const colorFieldsValues: string[][] = colorFields.map((uniqueId: UniqueId) => + getDomainFromDataset(dataset, uniqueId) + ); + + // 计算图例项笛卡尔积之前,限制条目数量 + const dimItemsLen: number = getDomainFromDataset(dataset, cell.x[0]).length; + const colorItemsLen: number = productLength(colorFieldsValues); + if ( + dimItemsLen > X_MAX_COUNT || + dataset.length > MAX_POINT_COUNT || + (colorItemsLen > LEGEND_MAX_COUNT && dataset.length > LEGEND_MAX_POINT_COUNT) + ) { + return { + error: true, + errMsg: `数据量或图例项过多,请使用表格展示。` + }; + } + + // 计算图例项 + const colorItemsList: string[][] = legendProduct(colorFieldsValues, colorFieldsIncludeMeasure); + let colorItems: string[] = colorItemsList.map(d => d.join('-')); + + // 使用笛卡尔积后的图例项替换原数据中对应的字段 + if (colorFields.length > 0) { + dataset = dataset.map((data: DataItem) => { + const colorItem = colorFields.map(field => data[field]).join('-'); + return { + ...data, + [COLOR_FIELD]: colorItem + }; + }); + // 将笛卡尔积后无效的 colorItem 移除 + const _colorItems: string[] = getDomainFromDataset(dataset, COLOR_FIELD); + colorItems = colorItems.filter((d: string) => _colorItems.includes(d)); + + aliasMap[COLOR_FIELD] = `图例项`; + cell.color.push(COLOR_FIELD); + cell.cartesianInfo = getCartesianInfo(colorFields, COLOR_FIELD); + + // 多度量时的平坦化字段 + if (measuresListLength > 1) { + cell.foldInfo = getFoldInfo(measureList, FOLD_NAME, FOLD_VALUE, aliasMap); + } + } + + return { cell, dataset, colorItems, aliasMap }; +}; + +export const processCombination = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + aliasMapOld: AliasMap, + MAX_PIVOT_ROW: number, + MAX_PIVOT_COLUMN: number +) => { + const aliasMap = cloneDeep(aliasMapOld); + //为组合图单独配置cell + const dataset = cloneDeep(originDataset); + const metaDatas = []; + + measureList.forEach(measure => { + const _allPillsIdList: UniqueId[] = [].concat(dimList, [measure]); + const _dataset: Dataset = retainDatasetField(dataset, _allPillsIdList); + const metaData = assignPivotCharts(_dataset, dimList, [measure], aliasMap, MAX_PIVOT_ROW, MAX_PIVOT_COLUMN); + metaDatas.push(metaData); + }); + + return metaDatas; +}; + +export const assignScatterPlot = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + aliasMapOld: AliasMap +) => { + const aliasMap = cloneDeep(aliasMapOld); + let dataset = cloneDeep(originDataset); + const scatterCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [], + group: [GROUP_FIELD] + }; + + let remainMeasure = measureList.length; //剩余未分配的度量数量 + + if (dimList.length === 0 || measureList.length < 2) { + return { scatterCell, dataset }; + } + + //首先分配两个轴 + scatterCell.x.push(measureList[0]); + scatterCell.y.push(measureList[1]); + + remainMeasure -= 2; + + //将第三个度量分配到尺寸 + if (measureList.length > 2) { + scatterCell.size.push(measureList[2]); + } + + remainMeasure -= 1; + + const groupDimensions: UniqueId[] = []; + + //将第一个维度分配到group,优先将第二个维度分配到颜色 + if (dimList.length > 1) { + scatterCell.color.push(dimList[1]); + groupDimensions.concat(dimList.slice(2)); + } else { + groupDimensions.concat(dimList.slice(1)); + //将度量分配到颜色 + if (measureList.length > 3) { + scatterCell.color.push(measureList[3]); + remainMeasure -= 1; + } + } + + if (remainMeasure > 0) { + //不支持此情况 + const voidCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [] + }; + return { scatterCell: voidCell, dataset }; + } + + // 提取color分组字段 + const colorFieldsValues: string[][] = scatterCell.color.map((uniqueId: UniqueId) => + getDomainFromDataset(dataset, uniqueId) + ); + + // 计算图例项笛卡尔积之前,限制条目数量 + const colorItemsLen: number = productLength(colorFieldsValues); + if ( + dataset.length > MAX_POINT_COUNT || + (colorItemsLen > LEGEND_MAX_COUNT && dataset.length > LEGEND_MAX_POINT_COUNT) + ) { + return { + error: true, + errMsg: `数据量或图例项过多,请使用表格展示。` + }; + } + // 计算图例项 + const colorItemsList: string[][] = legendProduct(colorFieldsValues, false); + const colorItems: string[] = colorItemsList.map(d => d.join('-')); + + dataset = dataset.map((data: DataItem) => { + const groupItem: string = groupDimensions.map((field: UniqueId) => data[field]).join('-'); + return { + ...data, + [GROUP_FIELD]: groupItem + }; + }); + aliasMap[GROUP_FIELD] = `细分`; + + return { + scatterCell, + dataset: [[[dataset]]], + colorItems, + aliasMap + }; +}; + +export const sortTimeDim = (dimList, MAX_PIVOT_ROW, MAX_PIVOT_COLUMN): UniqueId[] => { + //对于lineChart,先进行预排序,将第一个时间维度放到能分配到x轴的位置上 + const dimListLength = dimList.length; + + //第一个时间维度 + const firstTimeIndex = dimList.findIndex(dim => { + const isDateType = dataUtils.isTemporal(dim.dataType); + return isDateType; + }); + + let targetPosition; + if (MAX_PIVOT_COLUMN + MAX_PIVOT_ROW > dimListLength - 1) { + //维度数量不够形成MAX_PIVOT_COLUMN*MAX_PIVOT_ROW的透视,此时会将最后一个维度分配到x轴 + targetPosition = dimListLength - 1; + } else { + //维度数量可以形成MAX_PIVOT_COLUMN*MAX_PIVOT_ROW的透视,此时会将透视字段后第一个字段分配到x轴 + targetPosition = MAX_PIVOT_ROW + MAX_PIVOT_COLUMN; + } + + const idList = dimList.map(dim => dim.uniqueID); + + //将时间维度挪到前面 + const timeItem = idList[firstTimeIndex]; + + idList.splice(firstTimeIndex, 1); + idList.splice(targetPosition, 0, timeItem); + + return idList; +}; + +export const assignPieChart = (originDataset, dimList: UniqueId[], measureList: UniqueId[], aliasMapOld: AliasMap) => { + const aliasMap = cloneDeep(aliasMapOld); + let dataset = cloneDeep(originDataset); + + const pieCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [] + }; + + //不满足自动图表下饼图字段要求 + if (!(dimList.length === 0 && measureList.length >= 3)) { + return { pieCell, dataset }; + } + + // 饼图的所有维度都参与颜色计算 + const colorFields: UniqueId[] = [...dimList]; + // 图例项的字段中是否包含指标名称(包含的话需要将指标名称放到最后计算笛卡尔积) + let colorFieldsIncludeMeasure = false; + + const measuresListLength: number = measureList.length; + if (measuresListLength > 1) { + // 多度量 + aliasMap[FOLD_NAME] = `指标名称`; + aliasMap[FOLD_VALUE] = `指标值`; + colorFields.push(FOLD_NAME); + colorFieldsIncludeMeasure = true; + pieCell.angle.push(FOLD_VALUE); + } else if (measuresListLength === 1) { + // 单度量 + pieCell.angle.push(measureList[0]); + } + + // 提取 color分组字段 (维度 以及 度量名称) + const colorFieldsValues: string[][] = colorFields.map((uniqueId: UniqueId) => + getDomainFromDataset(dataset, uniqueId) + ); + + // 计算图例项笛卡尔积之前,限制条目数量 + const colorItemsLen: number = productLength(colorFieldsValues); + if ( + dataset.length > MAX_POINT_COUNT || + (colorItemsLen > LEGEND_MAX_COUNT && dataset.length > LEGEND_MAX_POINT_COUNT) + ) { + return { + error: true, + errMsg: `数据量或图例项过多,请使用表格展示。` + }; + } + + // 计算图例项 + const colorItemsList: string[][] = legendProduct(colorFieldsValues, colorFieldsIncludeMeasure); + let colorItems: string[] = colorItemsList.map(d => d.join('-')); + + // 使用笛卡尔积后的图例项替换原数据中对应的字段 + if (colorFields.length > 0) { + dataset = dataset.map((data: DataItem) => { + const colorItem = colorFields.map(field => data[field]).join('-'); + return { + ...data, + [COLOR_FIELD]: colorItem + }; + }); + // 将笛卡尔积后无效的 colorItem 移除 + const _colorItems: string[] = getDomainFromDataset(dataset, COLOR_FIELD); + colorItems = colorItems.filter((d: string) => _colorItems.includes(d)); + + aliasMap[COLOR_FIELD] = `图例项`; + pieCell.color.push(COLOR_FIELD); + pieCell.cartesianInfo = getCartesianInfo(colorFields, COLOR_FIELD); + + // 多度量时的平坦化字段 + if (measuresListLength > 1) { + pieCell.foldInfo = getFoldInfo(measureList, FOLD_NAME, FOLD_VALUE, aliasMap); + } + } + + return { pieCell, dataset, colorItems, aliasMap }; +}; + +export const assignMeasureCard = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + aliasMapOld: AliasMap +) => { + // eslint-disable-line + const aliasMap = cloneDeep(aliasMapOld); + const dataset = cloneDeep(originDataset); + + const cardDataset: Dataset = fold(dataset, measureList, FOLD_NAME, FOLD_VALUE, aliasMap); + + const cardCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [FOLD_NAME], + size: [FOLD_VALUE], + angle: [], + value: [FOLD_VALUE], + text: [FOLD_NAME] + }; + + return { cardCell, dataset: cardDataset }; +}; + +export const assignFunnelChart = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + aliasMapOld: AliasMap +) => { + const aliasMap = cloneDeep(aliasMapOld); + const dataset = cloneDeep(originDataset); + + const funnelCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [] + }; + + //不符合最低要求 + if (!((dimList.length === 1 && measureList.length === 1) || (dimList.length === 0 && measureList.length >= 2))) { + return { funnelCell, dataset }; + } + + const measuresListLength: number = measureList.length; + if (measuresListLength > 1) { + // 多度量 + aliasMap[FOLD_NAME] = `指标名称`; + aliasMap[FOLD_VALUE] = `指标值`; + funnelCell.size.push(FOLD_VALUE); + funnelCell.foldInfo = getFoldInfo(measureList, FOLD_NAME, FOLD_VALUE, aliasMap); + } else if (measuresListLength === 1) { + // 单度量 + funnelCell.size.push(measureList[0]); + } + + // 提取color分组字段 + const dimensionIdListLength: number = dimList.length; + if (dimensionIdListLength > 0) { + funnelCell.color.push(dimList[0]); + } else if (dimensionIdListLength === 0 && measuresListLength > 1) { + funnelCell.size.push(FOLD_NAME); + } + + return { funnelCell, dataset }; +}; + +export const assignDualAxis = ( + originDataset, + dimList: UniqueId[], + measureList: UniqueId[], + subMeasureList: UniqueId[], + aliasMapOld: AliasMap +) => { + const aliasMap = cloneDeep(aliasMapOld); + let dataset = cloneDeep(originDataset); + const cell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + cartesianInfo: null, + foldInfo: null + }; + + const singleSide = isMain => { + let _measuresIdList: UniqueId[]; + let removeIdList: UniqueId[]; + let FOLD_VALUE_FIELD: UniqueId; + let aliasFoldValue: string; + + if (isMain) { + _measuresIdList = measureList; + removeIdList = subMeasureList; + FOLD_VALUE_FIELD = FOLD_VALUE_MAIN; + aliasFoldValue = `指标值(主轴)`; + } else { + _measuresIdList = subMeasureList; + removeIdList = measureList; + FOLD_VALUE_FIELD = FOLD_VALUE_SUB; + aliasFoldValue = `指标值(次轴)`; + } + + if (_measuresIdList.length === 0) { + return []; + } + + // 只保留属于该轴的指标字段 + let sideDataset: Dataset = removeDatasetField(dataset, removeIdList); + + // 无论是否多度量都需要进行平坦化 + sideDataset = fold(sideDataset, _measuresIdList, FOLD_NAME, FOLD_VALUE_FIELD, aliasMap, false); + aliasMap[FOLD_VALUE_FIELD] = aliasFoldValue; + cell.y.push(FOLD_VALUE_FIELD); + return sideDataset; + }; + + // 左右轴依次进行平坦化 + const datasetMain: Dataset = singleSide(true); + const datasetSub: Dataset = singleSide(false); + + dataset = [].concat(datasetMain, datasetSub); + + const dimensionIdList: UniqueId[] = dimList; + const measureIdList: UniqueId[] = measureList.concat(subMeasureList); + + // 维度中参与图例项笛卡尔积计算的维度 + const colorFields: UniqueId[] = []; + + if (dimensionIdList.length > 0) { + // 提取 x 字段(维度第一项) + cell.x.push(String(dimensionIdList[0])); + // 其余项参与笛卡尔积计算 + colorFields.push(...dimensionIdList.slice(1)); + } + + const measuresListLength: number = measureIdList.length; + // 提取 y 字段(度量值) + if (measuresListLength > 0) { + aliasMap[FOLD_NAME] = `指标名称`; + colorFields.push(FOLD_NAME); + } + + // 提取 color分组字段 (维度其他项 以及 度量名称) + const colorFieldsValues: string[][] = colorFields.map((uniqueId: UniqueId) => + getDomainFromDataset(dataset, uniqueId) + ); + + // 计算图例项笛卡尔积之前,限制条目数量 + const dimItemsLen: number = getDomainFromDataset(dataset, cell.x[0]).length; + const colorItemsLen: number = productLength(colorFieldsValues); + if ( + dimItemsLen > X_MAX_COUNT || + dataset.length > MAX_POINT_COUNT || + (colorItemsLen > LEGEND_MAX_COUNT && dataset.length > LEGEND_MAX_POINT_COUNT) + ) { + return { + error: true, + errorMsg: `数据量或图例项过多,请使用表格展示。` + }; + } + + // 计算图例项 + const colorItemsList: string[][] = legendProduct(colorFieldsValues, true); + let colorItems: string[] = colorItemsList.map(d => d.join('-')); + + // 使用笛卡尔积后的图例项替换原数据中对应的字段 + if (colorFields.length > 0) { + dataset = dataset.map((data: DataItem) => { + const colorItem = colorFields.map(field => data[field]).join('-'); + return { + ...data, + [COLOR_FIELD]: colorItem + }; + }); + // 将笛卡尔积后无效的 colorItem 移除 + const _colorItems: string[] = getDomainFromDataset(dataset, COLOR_FIELD); + colorItems = colorItems.filter((d: string) => _colorItems.includes(d)); + + aliasMap[COLOR_FIELD] = `图例项`; + cell.color.push(COLOR_FIELD); + cell.cartesianInfo = getCartesianInfo(colorFields, COLOR_FIELD); + cell.foldInfo = getFoldInfo(measureIdList, FOLD_NAME, [FOLD_VALUE_MAIN, FOLD_VALUE_SUB], aliasMap); + } + + return { + dataset, + cell, + colorItems, + aliasMap + }; +}; diff --git a/packages/chart-advisor/src/fieldUtils.ts b/packages/chart-advisor/src/fieldUtils.ts new file mode 100644 index 00000000..804f8bd3 --- /dev/null +++ b/packages/chart-advisor/src/fieldUtils.ts @@ -0,0 +1,127 @@ +import { uniq, pick, omit } from 'lodash'; +import { FoldInfo } from './type'; +import { Dataset, UniqueId, DataItem, AliasMap } from './type'; + +/** + * 输入二维数组,输出笛卡尔积长度 + * @param list + */ +export const productLength = (list: any[][]) => + list.length === 0 ? 0 : list.map(d => d.length).reduce((pre, cur) => pre * cur, 1); + +/** + * 输入二维数组,输出笛卡尔积 + * 新增了计算笛卡尔积时将指标名称放在最后计算的特殊处理 + * @param list + */ +export const legendProduct = (list: any[][], hasMeasureName = false) => { + if (hasMeasureName && list.length > 1) { + const _list = [...list]; + const measureNames = _list.pop(); + const productResult = product(_list); + return measureNames + .map((measureName: any[]) => productResult.map((d: any[]) => d.concat(measureName))) + .reduce((pre, cur) => pre.concat(cur), []); + } + return product(list); +}; + +// 计算笛卡尔积 +const product = (list: any[][]) => + list.length === 0 + ? [] + : list.reduce( + function (a, b) { + return a + .map(function (x) { + return b.map(function (y) { + return x.concat(y); + }); + }) + .reduce(function (a, b) { + return a.concat(b); + }, []); + }, + [[]] + ); + +// 从数据集中获取 domain map +export const getDomainFromDataset = (dataset: Dataset, dim: UniqueId): string[] => { + const values: string[] = dataset.map((d: DataItem) => String(d[dim])); + return uniq(values); +}; + +// 保留dataset中的某些字段 +export const retainDatasetField = (dataset: Dataset, fields: UniqueId[]): Dataset => + dataset.map((data: DataItem) => pick(data, fields)) as Dataset; + +// 移除dataset中的某些字段 +export const removeDatasetField = (dataset: Dataset, fields: UniqueId[]): Dataset => + dataset.map((data: DataItem) => omit(data, fields)) as Dataset; + +/** + * 维度笛卡尔积的原始信息,因此可以通过cartesianInfo匹配原始的维度字段 + */ +export const getCartesianInfo = (fieldList: UniqueId[], key: UniqueId) => ({ + key, + fieldList +}); + +/** + * 由于多度量的情况下数据会被平坦化,会导致原本的字段信息丢失,因此可以通过foldInfo匹配原始的指标字段 + */ +export const getFoldInfo = ( + measuresId: UniqueId[], + foldName: UniqueId, + foldValue: UniqueId | UniqueId[], + aliasMap: AliasMap +) => + ({ + key: foldName, + value: foldValue, + // foldMap: Object.fromEntries(new Map( + foldMap: strMap2Obj(new Map(measuresId.map(id => [id, aliasMap[id]]))) + } as FoldInfo); +/** + * 临时替代fromEntries 以兼容73以前的chrome + */ +const strMap2Obj = strMap => { + const obj = Object.create(null); + for (const [k, v] of strMap) { + obj[k] = v; + } + return obj; +}; + +/** + * Transform fold 字段展开 + * @param dataset 原始数据集 + * @param fields 待展开的字段集 + * @param foldName foldName字段 + * @param foldValue foldValue字段 + * @param aliasMap 别名表 + * @param retains 是否保留被展开的字段 + * @return dataset + */ +export const fold = ( + dataset: Dataset, + fields: UniqueId[], + foldName: UniqueId, + foldValue: UniqueId, + aliasMap?: AliasMap, + retains = true +) => { + const _dataset = []; + dataset.forEach((data: DataItem) => { + fields.forEach((field: UniqueId) => { + // 是否保留被展开的字段 + const _data = retains ? data : omit(data, fields); + _dataset.push({ + ..._data, + [foldName]: aliasMap ? aliasMap[field] : field, + [foldValue]: data[field] + }); + }); + }); + return _dataset; +}; diff --git a/packages/chart-advisor/src/index.ts b/packages/chart-advisor/src/index.ts new file mode 100644 index 00000000..c0770f2a --- /dev/null +++ b/packages/chart-advisor/src/index.ts @@ -0,0 +1,140 @@ +import { DimensionDataset, MeasureDataset, ChartType, ScreenSize, UserPurpose } from './type'; +import type { + AdviseResult, + Scorer, + AdviserParams, + ScoreResult, + DataTypeName, + MeasureField, + DimensionField +} from './type'; +import { scorer as defaultScorer } from './score'; +import * as dataUtils from './dataUtil'; +import { isNil, isNaN } from 'lodash'; + +export { fold } from './fieldUtils'; +export { FOLD_NAME, FOLD_VALUE } from './constant'; + +export function chartAdvisor(params: AdviserParams): AdviseResult { + const { + originDataset, + dimensionList, + measureList, + aliasMap = {}, + maxPivotRow = 0, + maxPivotColumn = 0, + purpose = UserPurpose.NONE, + screen = ScreenSize.LARGE, + scorer = defaultScorer + } = params; + + const measureDatasets: MeasureDataset[] = []; + + /* + 计算measure的统计特征。 + 由于服务端返回的vizData.dataset做了平坦化(fold)处理,且remain=false, + 因此当多度量时需要先判断dataset中是否有该uniqueID对应的值. + */ + measureList.forEach(measure => { + const uniqueID = measure.uniqueId; + const measureSet: MeasureDataset = { + data: [] + }; + measureSet.uniqueID = uniqueID; + originDataset.forEach(row => { + if (row.hasOwnProperty(uniqueID)) { + measureSet.data.push(parseFloat(row[uniqueID])); + } + }); + const dataNotNull = measureSet.data.filter(each => !isNil(each) && !isNaN(each)); + measureSet.min = Math.min(...dataNotNull); + measureSet.max = Math.max(...dataNotNull); + measureSet.mean = dataUtils.calMean(measureSet); + measureSet.standardDev = dataUtils.calStandardDeviation(measureSet); + measureSet.coefficient = dataUtils.calCoefficient(measureSet); + measureSet.Q1 = dataUtils.calQuantile(measureSet, 0.25); + + measureDatasets.push(measureSet); + }); + + const dimensionDatasets: DimensionDataset[] = []; + + /* + 计算dimension的字段特征(字段名、基数、基数/数据条数)。 + */ + dimensionList.forEach(dimension => { + const uniqueID = dimension.uniqueId; + const dimensionSet: DimensionDataset = { + data: [] + }; + dimensionSet.uniqueID = uniqueID; + originDataset.forEach(row => { + dimensionSet.data.push(row[uniqueID]); + }); + dimensionSet.dataType = dimension.type; + dimensionSet.dimensionName = aliasMap[uniqueID]; + dimensionSet.cardinal = dataUtils.unique(dimensionSet.data).length; + dimensionSet.ratio = dimensionSet.cardinal / dimensionSet.data.length; + dimensionSet.isGeoField = !!dimension.isGeoField; + dimensionDatasets.push(dimensionSet); + }); + + try { + const scores = scorer({ + inputDataSet: originDataset, + dimList: dimensionDatasets, + measureList: measureDatasets, + aliasMap, + maxRowNum: maxPivotRow, + maxColNum: maxPivotColumn, + purpose, + screen + }).map(calculator => { + const score = calculator(); + return score; + }); + + scores.sort((chart1, chart2) => chart2.score - chart1.score); + // console.log(scores) + if (scores[0].score === 0) { + return { + chartType: ChartType.TABLE, + scores: [] + }; + } + // console.log(scores) + + // scores.forEach(score => { + // let cell = score.cell + // if (!Array.isArray(cell)) { + // cell = [cell] + // } + // //将所有的key转换为string + // cell.forEach(cl => { + // Object.entries(cl).forEach(([k, v]) => { + // if (k === 'cartesianInfo' || k === 'foldInfo') { + // cl[k] = null + // } + // else { + // cl[k] = v.map(value => String(value)) + // } + // }) + // }) + + // score.cell = cell + // }) + + return { + chartType: scores[0].chartType, + scores + }; + } catch (exception: any) { + return { + chartType: ChartType.TABLE, + scores: [], + error: exception.message ?? exception + }; + } +} + +export { Scorer, AdviserParams, ScoreResult, ChartType, AdviseResult, DataTypeName, MeasureField, DimensionField }; diff --git a/packages/chart-advisor/src/pivot.ts b/packages/chart-advisor/src/pivot.ts new file mode 100644 index 00000000..c2136a72 --- /dev/null +++ b/packages/chart-advisor/src/pivot.ts @@ -0,0 +1,203 @@ +import { isNil, range, uniq } from 'lodash'; + +import { getDomainFromDataset } from './fieldUtils'; +import { UniqueId, Dataset, Datasets, DataItem, PivotTree } from './type'; + +// 透视分析 +export const pivot = ( + dataset: Dataset, + colList: UniqueId[], + rowList: UniqueId[], + measureList: UniqueId[], + paginationInfo?: any +): { + datasets: Datasets; + colPivotTree: PivotTree; + rowPivotTree: PivotTree; + length: number; +} => { + // 行列的透视的 pathList + const rowPathList = getPathList(rowList, dataset); + const colPathList = getPathList(colList, dataset); + + // 按行分页 + const rowPathListPage = paginationDataset(rowPathList, paginationInfo); + + // 按行维度透视 + const rowGroups = groupByDims(dataset, rowList, rowPathListPage); + + // 按列维度透视 + const colGroups = rowGroups.map((row: Dataset) => groupByDims(row, colList, colPathList)); + + // 按多指标透视 (透视表中实际不存在此场景,因为多指标已经被平坦化) + const groups: Datasets = colGroups.map((pane: Dataset[]) => + pane.map((cell: Dataset) => groupByMeas(cell, measureList)) + ); + + // 根据分页后的 pathList 生成 header tree + const rowPivotTree = pivotTree(rowList, rowPathListPage); + const colPivotTree = pivotTree(colList, colPathList); + + return { + datasets: groups, + colPivotTree, + rowPivotTree, + length: rowGroups.length + }; +}; + +// 组合图透视分析 +export const pivotCombination = ( + dataset: Dataset[], + colList: UniqueId[], + rowList: UniqueId[] +): { + datasets: Datasets; + colPivotTree: PivotTree; + rowPivotTree: PivotTree; + length: number; +} => { + // 组合图 meta 数量 + const metaLength: number = dataset.length; + + // 先为数据打上 组合图 mate index tag, 然后将数据平坦为一个集合 + const datasetWithTag: Dataset[] = dataset.map((dataList: Dataset, index: number) => + dataList.map((data: DataItem) => addTag(data, index)) + ); + const flatDataset = datasetWithTag.flat(); + + // 行列的透视的 path_list + const rowPathList = getPathList(rowList, flatDataset); + const colPathList = getPathList(colList, flatDataset); + + // 按行维度透视 + const rowGroups = groupByDims(flatDataset, rowList, rowPathList); + + // 按列维度透视 + const colGroups = rowGroups.map((row: Dataset) => groupByDims(row, colList, colPathList)); + + // 按多指标透视 + const groups: Datasets = colGroups.map((pane: Dataset[]) => + pane.map((cell: Dataset) => groupByMeta(cell, metaLength)) + ); + // console.log(groups) + + // 根据分页后的 pathList 生成 header tree + const rowPivotTree = pivotTree(rowList, rowPathList); + const colPivotTree = pivotTree(colList, colPathList); + + return { + datasets: groups, + colPivotTree, + rowPivotTree, + length: rowGroups.length + }; +}; + +// 通过 domain_map 和 sort_service 生成透视路径集合 +type Path = string[]; +const getPathList = (keys: UniqueId[], dataset: Dataset, filterList: Path = []) => { + const pathList: Path[] = []; + if (keys.length > 0) { + const key: UniqueId = keys[0]; + const valueList: string[] = getDomainFromDataset(dataset, key); + + valueList.forEach((value: string) => { + if (keys.length > 1) { + const _dataset: Dataset = filterDataItem(dataset, [key], [value]); + const _filterList: Path = [...filterList, value]; + pathList.push(...getPathList(keys.slice(1), _dataset, _filterList)); + } else { + pathList.push([...filterList, value]); + } + }); + } + return pathList; +}; + +// 分组 dimension +const groupByDims = (source: Dataset, keys: UniqueId[], pathList: Path[]): Dataset[] => { + if (pathList.length === 0) { + return [source]; + } + const groups: Dataset[] = pathList.map((path: Path) => filterDataItem(source, keys, path)); + return groups; +}; + +// 分组 measure +const groupByMeas = (source: Dataset, measures: UniqueId[]): Dataset[] => { + if (measures.length <= 1) { + return [source]; + } + return measures.map((measure: UniqueId) => source.filter((dataItem: DataItem) => !isNil(dataItem[measure]))); +}; + +// 分组 meta +const groupByMeta = (source: Dataset, length: number): Dataset[] => { + const group: Dataset[] = range(length).map((index: number) => + source.filter((data: DataItem) => data[COMBINATION_INDEX] === index).map((data: DataItem) => removeTag(data)) + ); + return group; +}; + +// 分页逻辑 +const paginationDataset = (pathList: Path[], paginationInfo?: any) => { + if (isNil(paginationInfo)) { + return pathList; + } + const pageOffset: number = paginationInfo.offset; + const pageSize: number = paginationInfo.size; + return pathList.slice(pageOffset, pageOffset + pageSize); +}; + +// 生成 pivot header tree 递归创建 header node +const pivotTree = (keys: UniqueId[], pathList: Path[], deep = 0): PivotTree => { + if (keys.length < deep + 1) { + return null; + } + const getDomainFromPath = () => { + const nodes: string[] = pathList.map((path: Path) => path[deep]); + return uniq(nodes); + }; + const field: UniqueId = keys[deep]; + const domain: string[] = getDomainFromPath(); + return { + field, + values: domain.map((value: string) => ({ + value, + child: pivotTree( + keys, + pathList.filter((path: Path) => path[deep] === value), + deep + 1 + ) + })) + }; +}; + +// 工具函数 + +// 通过分组好的 kv 数据 在数据集中过滤出满足条件的数据项 +const filterDataItem = (sourceList: Dataset, keyList: UniqueId[], valueList: string[]): Dataset => + sourceList.filter(dataItem => checkKeysValues(dataItem, keyList, valueList)); + +// 通过指定的 kv 信息判断当前的数据项是否匹配 +const checkKeysValues = (dataItem: any, keyList: UniqueId[], valueList: string[]): boolean => + range(keyList.length) + .map((idx: number) => String(dataItem[keyList[idx]]) === valueList[idx]) + .reduce((pre: boolean, cur: boolean) => pre && cur, true); + +// 组合图透视分析所依赖的tag +const COMBINATION_INDEX = '__combination_index__'; + +// 增加tag +const addTag = (dataItem: DataItem, i: number) => ({ + ...dataItem, + [COMBINATION_INDEX]: i +}); + +// 移除tag +const removeTag = (dataItem: DataItem) => { + const _dataItem = { ...dataItem }; + delete _dataItem[COMBINATION_INDEX]; + return _dataItem; +}; diff --git a/packages/chart-advisor/src/score.ts b/packages/chart-advisor/src/score.ts new file mode 100644 index 00000000..8833a1a2 --- /dev/null +++ b/packages/chart-advisor/src/score.ts @@ -0,0 +1,1200 @@ +import { isTemporal } from './dataUtil'; +import { cloneDeep, uniq } from 'lodash'; +import { + ChartType, + MeasureDataset, + ScreenSize, + UserPurpose, + AutoChartCell, + Dataset, + UniqueId, + ScoreResult, + Scorer +} from './type'; +import * as dataUtils from './dataUtil'; +import { + assignMeasureCard, + assignPieChart, + assignPivotCharts, + assignScatterPlot, + assignFunnelChart, + sortTimeDim, + processCombination, + assignDualAxis +} from './fieldAssign'; +import { COLOR_FIELD, MAX_BAR_NUMBER, MIN_BAR_NUMBER, FOLD_NAME, FOLD_VALUE } from './constant'; +import { getDomainFromDataset, fold } from './fieldUtils'; +import { pivot, pivotCombination } from './pivot'; + +export const scorer: Scorer = params => { + const { + inputDataSet, + dimList, + measureList, + aliasMap = {}, + maxRowNum = 0, + maxColNum = 0, + purpose = UserPurpose.NONE, + screen = ScreenSize.LARGE + } = params; + + const datasetWithoutFold = cloneDeep(inputDataSet); + let originDataset = inputDataSet; + + //多度量可能没有扁平化 + if (measureList.length > 1 && !originDataset[0].hasOwnProperty(FOLD_NAME)) { + originDataset = fold( + originDataset, + measureList.map(measure => measure.uniqueID), + FOLD_NAME, + FOLD_VALUE, + aliasMap, + false + ); + } + + //找出时间字段和非时间字段 + const timeDim: UniqueId[] = []; + const noneTimeDim: UniqueId[] = []; + dimList.forEach(dim => { + if (isTemporal(dim.dataType)) { + timeDim.push(dim.uniqueID); + } else { + noneTimeDim.push(dim.uniqueID); + } + }); + + //uniqueId到字段values的映射 + const uniqueIdMap = {}; + dimList.forEach(dim => { + uniqueIdMap[dim.uniqueID] = dim; + }); + + measureList.forEach(measure => { + uniqueIdMap[measure.uniqueID] = measure; + }); + + const dimensionID = dimList.map(dim => dim.uniqueID); + const measureID = measureList.map(measure => measure.uniqueID); + + const pivotChartData = assignPivotCharts(originDataset, dimensionID, measureID, aliasMap, maxRowNum, maxColNum); + + const { dataset, colorItems, error, errMsg, aliasMap: newAliasMap } = pivotChartData; + let { cell } = pivotChartData; + + const colList = error ? [] : cell.column; + const rowList = error ? [] : cell.row; + const emptyCell = { + x: [], + y: [], + column: [], + row: [], + color: [], + size: [], + angle: [] + }; + // @todo:图例过多处理 + if (error) { + cell = emptyCell; + } + + // 透视分析 + const { datasets: pivotDataSet, colPivotTree, rowPivotTree } = pivot(dataset, colList, rowList, cell.y); + + const calBarParallel = (): ScoreResult => { + let score = 0; + const scoreDetails: any = {}; + let totalScore = 0; + + if (error) { + return { + chartType: ChartType.COLUMN_PARALLEL, + originScore: 0, + score: 0, + fullMark: 0, + scoreDetails, + error: error ? errMsg : null + }; + } + const measureLength = measureList.length; + + //维度数>0,指标数>0 + const rule1Score = 1.0; + totalScore += rule1Score; + if (cell.x.length > 0 && cell.y.length > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.COLUMN_PARALLEL, + originScore: 0, + score: 0, + fullMark: 0, + scoreDetails + }; + } + + //指标最大值里面最大的/指标Q1(下四分位数)里面最小的<100(不同指标数值在同一个量级) + const rule2Score = 3.0; + totalScore += rule2Score; + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + if (maxMax === 0) { + maxMax = 1; + } + if (minQ1 === 0) { + minQ1 = 1; + } + + if (maxMax / minQ1 < 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //2<=图元数量<=30 (根据屏幕尺寸调整) + const rule3Score = 4.0; + totalScore += rule3Score; + + const colorSize = cell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + const axisSize = dataUtils.unique(uniqueIdMap[cell.x[0]].data).length; + + const dataSize = colorSize * axisSize; + + if (dataSize <= MAX_BAR_NUMBER && dataSize >= MIN_BAR_NUMBER) { + score += rule3Score; + scoreDetails.rule4 = rule3Score; + } + + return { + chartType: ChartType.COLUMN_PARALLEL, + score: score / totalScore, + fullMark: totalScore, + originScore: score, + scoreDetails, + cell, + dataset + }; + }; + + const calBarPercent = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + if (error) { + return { + chartType: ChartType.COLUMN_PERCENT, + originScore: 0, + fullMark: 0, + score: 0, + scoreDetails, + error: error ? errMsg : null + }; + } + + const dimensionLength = dimList.length - cell.row.length - cell.column.length; + const measureLength = measureList.length; + + //维度数>1,指标数>0(不能是平行柱状图) + const rule1Score = 1.0; + totalScore += rule1Score; + if (dimensionLength > 1 && measureLength > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.COLUMN_PERCENT, + score: 0, + fullMark: 0, + originScore: 0, + scoreDetails + }; + } + + //指标最大值里面最大的/指标Q1(下四分位数)里面最小的<100(不同指标数值在同一个量级) + const rule2Score = 3.0; + totalScore += rule2Score; + + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + if (maxMax === 0) { + maxMax = 1; + } + if (minQ1 === 0) { + minQ1 = 1; + } + + if (maxMax / minQ1 < 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //2<=图元数量<=30 (根据屏幕尺寸调整) + const rule3Score = 1.0; + totalScore += rule3Score; + + const axisSize = dataUtils.unique(uniqueIdMap[cell.x[0]].data).length; + + const dataSize = axisSize * measureLength; + + if (dataSize <= MAX_BAR_NUMBER && dataSize >= MIN_BAR_NUMBER) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + //图例数量<=150 (根据屏幕尺寸调整) + const rule4Score = 1.0; + totalScore += rule4Score; + + const colorSize = cell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + + if (colorSize <= MAX_BAR_NUMBER) { + score += rule4Score; + scoreDetails.rule4 = rule4Score; + } + + //用户目的=占比 + const rule5Score = 1.0; + totalScore += rule5Score; + if (purpose === UserPurpose.PROPORTION) { + score += rule5Score; + scoreDetails.rule5 = rule5Score; + } + + return { + chartType: ChartType.COLUMN_PERCENT, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell, + dataset + }; + }; + + const calBar = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + if (error) { + return { + chartType: ChartType.COLUMN, + score: 0, + scoreDetails, + fullMark: 0, + originScore: 0, + error: error ? errMsg : '' + }; + } + + const dimensionLength = dimList.length - cell.row.length - cell.column.length; + const measureLength = measureList.length; + + //维度数>1(不能是平行柱状图) + //databot中,维度数>=1,指标数>=1 + const rule1Score = 1.0; + totalScore += rule1Score; + + if (dimensionLength >= 1 && measureLength >= 1) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.COLUMN, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //指标最大值里面最大的/指标Q1(下四分位数)里面最小的<100(指标数值在同一个量级) + const rule2Score = 3.0; + totalScore += rule2Score; + + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + if (maxMax === 0) { + maxMax = 1; + } + if (minQ1 === 0) { + minQ1 = 1; + } + + if (maxMax / minQ1 < 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //2<=图元数量<=30 (根据屏幕尺寸调整) + const rule3Score = 1.0; + totalScore += rule3Score; + + const axisSize = dataUtils.unique(uniqueIdMap[cell.x[0]].data).length; + const dataSize = measureLength * axisSize; + + if (dataSize <= MAX_BAR_NUMBER && dataSize >= MIN_BAR_NUMBER) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + //图例数量<=150 (根据屏幕尺寸调整) + const rule4Score = 1.0; + totalScore += rule4Score; + + const colorSize = cell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + + if (colorSize <= MAX_BAR_NUMBER) { + score += rule4Score; + scoreDetails.rule4 = rule4Score; + } + + //屏幕尺寸=小 + const rule5Score = 1.0; + totalScore += rule5Score; + if (screen === ScreenSize.SMALL) { + score += rule5Score; + scoreDetails.rule4 = rule5Score; + } + + return { + chartType: ChartType.COLUMN, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell, + dataset + }; + }; + + //数据差异过大时,使用组合柱状图。 + const calCombination = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + if (error) { + return { + chartType: ChartType.COMBINATION, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0, + error: error ? errMsg : null + }; + } + + //根据cell决定参与图表推荐的字段 + const measureLength = measureList.length; + + //维度数>0,指标数>1 + const rule1Score = 1.0; + totalScore += rule1Score; + if (measureLength > 1 && cell.x.length > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.COMBINATION, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //指标最大值里面最大的/指标Q1(下四分位数)里面最小的>100(不同指标数值不在同一个量级) + const rule2Score = 1.0; + totalScore += rule2Score; + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + if (maxMax === 0) { + maxMax = 1; + } + if (minQ1 === 0) { + minQ1 = 1; + } + + if (maxMax / minQ1 > 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //同一个指标里面最小bar的数值/最大bar的数值>1% + const rule3Score = 3.0; + totalScore += rule3Score; + const score3Flag = measureList.reduce((prev, cur) => { + if (prev) { + return cur.min / cur.max > 0.01; + } + return false; + }, true); + if (score3Flag) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + //2<=图元数量<=30 (根据屏幕尺寸调整) + const rule4Score = 1.0; + totalScore += rule4Score; + + const colorSize = cell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + const axisSize = dataUtils.unique(uniqueIdMap[cell.x[0]].data).length; + + const dataSize = axisSize * colorSize; + if (dataSize <= MAX_BAR_NUMBER && dataSize >= MIN_BAR_NUMBER) { + score += rule4Score; + scoreDetails.rule4 = rule4Score; + } + + //计算cells + const combineMetadata = processCombination( + datasetWithoutFold, + dimensionID, + measureID, + aliasMap, + maxRowNum, + maxColNum + ); + + const combineDatasets: Dataset[] = combineMetadata.map(metaData => metaData.dataset); + const combineCells: any[] = combineMetadata.map(metaData => metaData.cell); + + const combineColorItems: string[] = uniq( + combineMetadata.map(metaData => metaData.colorItems).reduce((pre: string[], cur: string[]) => pre.concat(cur), []) + ); + + // 透视分析 + const { + datasets: combinePivotDataSet, + colPivotTree, + rowPivotTree + } = pivotCombination(combineDatasets, colList, rowList); + + return { + chartType: ChartType.COMBINATION, + score: score / totalScore, + scoreDetails, + originScore: score, + fullMark: totalScore, + cell: combineCells, + dataset: combinePivotDataSet + }; + }; + + const calScatterplot = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + const dimensionID = dimList.map(dim => dim.uniqueID); + const measureID = measureList.map(measure => measure.uniqueID); + + const scatterData = assignScatterPlot(datasetWithoutFold, dimensionID, measureID, aliasMap); + + const { scatterCell, dataset, colorItems, aliasMap: newAliasMap, error, errMsg } = scatterData; + + if (error) { + return { + chartType: ChartType.SCATTER, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0, + error: error ? errMsg : null + }; + } + //维度数>0,指标数>=2,维度+指标<=5 + const rule1Score = 1.0; + totalScore += rule1Score; + if (scatterCell.x.length > 0 && scatterCell.y.length > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.SCATTER, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //数据个数>30 <最大数量限制(根据屏幕尺寸调整) + const rule2Score = 1.0; + totalScore += rule2Score; + if (datasetWithoutFold.length >= 30 && datasetWithoutFold.length <= 1000) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //数据分布不能太集中 + + return { + chartType: ChartType.SCATTER, + score: score / totalScore, + scoreDetails, + originScore: score, + fullMark: totalScore, + cell: scatterCell, + dataset + }; + }; + + const calLineChart = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + //折线图先进行预排序 + const lineChartDimID: UniqueId[] = sortTimeDim(dimList, maxRowNum, maxColNum); + + const { + cell: lineChartCell, + dataset: lineDataset, + error, + errMsg + } = assignPivotCharts(originDataset, lineChartDimID, measureID, aliasMap, maxRowNum, maxColNum); + if (error) { + return { + chartType: ChartType.LINE, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0, + error: error ? errMsg : null + }; + } + + //维度数=1且为时间,指标数>=1或维度=2且只有一个时间,指标数=1,非时间维度基数不能太多 + const rule1Score = 2.0; + totalScore += rule1Score - 1.0; + const _colorItems: string[] = getDomainFromDataset(lineDataset, COLOR_FIELD); + const colorItemCardinal = lineDataset.hasOwnProperty(COLOR_FIELD) ? dataUtils.unique(_colorItems).length : 1; + if (timeDim.length > 0 && cell.y.length > 0 && colorItemCardinal <= 50) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.LINE, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //指标数>1时,指标最大值里面最大的/指标Q1(下四分位数)里面最小的<100(不同指标数值在同一个量级) + const rule2Score = 1.0; + if (measureList.length > 1) { + totalScore += rule2Score; + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + if (minQ1 === 0) { + minQ1 = 1; + } + if (maxMax === 0) { + maxMax = 1; + } + + if (maxMax / minQ1 <= 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + } + + //变异系数>x (需要调整) + const rule3Score = 1.0; + totalScore += rule3Score; + const coefficientFlag = measureList.reduce((prev, cur) => { + if (!prev) { + return false; + } else { + if (cur.coefficient) { + return cur.coefficient >= 0.2; + } else { + return true; + } + } + }, true); + + if (coefficientFlag) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + // 透视分析 + const { + datasets: lineChartDataset, + colPivotTree, + rowPivotTree + } = pivot(lineDataset, colList, rowList, lineChartCell.y); + + return { + chartType: ChartType.LINE, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: lineChartCell, + dataset: lineDataset + }; + }; + + const calLineChartCombine = (): ScoreResult => { + //组合折线图 + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + //折线图先进行预排序 + const lineChartDimID: UniqueId[] = sortTimeDim(dimList, maxRowNum, maxColNum); + + const { + cell: lineChartCell, + dataset: lineDataset, + error, + errMsg + } = assignPivotCharts(originDataset, lineChartDimID, measureID, aliasMap, maxRowNum, maxColNum); + + if (error) { + return { + chartType: ChartType.LINE, + score: 0, + scoreDetails, + fullMark: 0, + originScore: 0, + error: error ? errMsg : null + }; + } + + //维度数=1且为时间,指标数>=1或维度=2且只有一个时间,指标数=1,非时间维度基数不能太多 + const rule1Score = 2.0; + totalScore += rule1Score - 1.0; + const _colorItems: string[] = getDomainFromDataset(lineDataset, COLOR_FIELD); + const colorItemCardinal = lineDataset.hasOwnProperty(COLOR_FIELD) ? dataUtils.unique(_colorItems).length : 1; + if (timeDim.length > 0 && cell.y.length > 0 && colorItemCardinal <= 50) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.LINE, + score: 0, + scoreDetails, + fullMark: 0, + originScore: 0 + }; + } + + //指标数>1时,指标最大值里面最大的/指标Q1(下四分位数)里面最小的>100(不同指标数值不在同一个量级) + const rule2Score = 1.0; + if (measureList.length > 1) { + totalScore += rule2Score; + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + if (minQ1 === 0) { + minQ1 = 1; + } + if (maxMax === 0) { + maxMax = 1; + } + + if (maxMax / minQ1 > 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + } + + //变异系数>x (需要调整) + const rule3Score = 1.0; + totalScore += rule3Score; + const coefficientFlag = measureList.reduce((prev, cur) => { + if (!prev) { + return false; + } else { + if (cur.coefficient) { + return cur.coefficient >= 0.2; + } else { + return true; + } + } + }, true); + + if (coefficientFlag) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + //计算cells + const combineMetadata = processCombination( + datasetWithoutFold, + lineChartDimID, + measureID, + aliasMap, + maxRowNum, + maxColNum + ); + + const combineDatasets: Dataset[] = combineMetadata.map(metaData => metaData.dataset); + const combineCells: any[] = combineMetadata.map(metaData => metaData.cell); + + const combineColorItems: string[] = uniq( + combineMetadata.map(metaData => metaData.colorItems).reduce((pre: string[], cur: string[]) => pre.concat(cur), []) + ); + + // 透视分析 + const { + datasets: combinePivotDataSet, + colPivotTree, + rowPivotTree + } = pivotCombination(combineDatasets, colList, rowList); + + return { + chartType: ChartType.EXTEND, //暂时用扩展类型来代替 + score: score / totalScore, + scoreDetails, + originScore: score, + fullMark: totalScore + }; + }; + + const calPieChart = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + const pieChartData = assignPieChart(originDataset, dimensionID, measureID, aliasMap); + + const { pieCell, dataset: pieDataset, colorItems, aliasMap: newAliasMap, error, errMsg } = pieChartData; + + if (error) { + return { + chartType: ChartType.PIE, + score: 0, + scoreDetails, + fullMark: 0, + originScore: 0, + error: error ? errMsg : null + }; + } + + //维度数=0,指标数>=3 + const rule1Score = 2.0; + totalScore += rule1Score; + if (dimList.length === 0 && measureList.length >= 3) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.PIE, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //数据个数<=20 + const rule2Score = 2.0; + totalScore += rule2Score; + if (measureList.length <= 20) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //维度最小值/维度最大值>0.1 + const rule3Score = 3.0; + totalScore += rule3Score; + measureList.map(measure => measure.min); + const minMeasure = Math.min(...measureList.map(measure => measure.min)); + const maxMeasure = Math.max(...measureList.map(measure => measure.max)); + if (minMeasure / maxMeasure > 0.1) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + //变异系数>x (需要调整) + const rule4Score = 1.0; + totalScore += rule4Score; + //当维度数=0时,计算指标的变异系数 + if (dimList.length === 0) { + const tempDataset: MeasureDataset = { + data: measureList.reduce((prev, cur) => prev.concat(cur.data), []) + }; + const coefficient = dataUtils.calCoefficient(tempDataset); + if (!coefficient || coefficient > 0.2) { + score += rule4Score; + scoreDetails.rule4 = rule4Score; + } + } + + //得分归一化用于比较 + + return { + chartType: ChartType.PIE, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: pieCell, + dataset: pieDataset + }; + }; + + const calMeasureCard = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + //1<=指标数<=3,维度数=0 + const rule1Score = 2.0; + totalScore += rule1Score; + if (dimList.length === 0 && measureList.length <= 3 && measureList.length >= 1) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.MEASURE_CARD, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + const cardData = assignMeasureCard(datasetWithoutFold, dimensionID, measureID, aliasMap); + + const { cardCell, dataset: cardDataset } = cardData; + + return { + chartType: ChartType.MEASURE_CARD, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: cardCell, + dataset: cardDataset + }; + }; + + const calRadar = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + if (error) { + return { + chartType: ChartType.RADAR, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0, + error: error ? errMsg : null + }; + } + + //1<=维度数<=2,指标数=1,或者维度数=1,指标数>=1 + const rule1Score = 1.0; + totalScore += rule1Score; + if (cell.x.length > 0 && cell.y.length > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.RADAR, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //用户目的=分布 + const rule2Score = 5.0; + totalScore += rule2Score; + if (purpose === UserPurpose.DISTRIBUTION) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + return { + chartType: ChartType.RADAR, + score: score / totalScore, + fullMark: totalScore, + originScore: score, + scoreDetails, + cell, + dataset + }; + }; + + const calWordCloud = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + const rule1Score = 1.0; + totalScore += rule1Score; + if (dimList.length === 1 && measureList.length == 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.WORD_CLOUD, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //数据基数>=10且<=100 + const rule2Score = 2.0; + totalScore += rule2Score; + if (dimList[0].cardinal >= 20 && dimList[0].cardinal <= 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + //用户目的=storyTelling + const rule3Score = 5.0; + totalScore += rule3Score; + if (purpose === UserPurpose.STORYTELLING) { + score += rule3Score; + scoreDetails.rule3 = rule3Score; + } + + const wordCloudCell: AutoChartCell = { + x: [], + y: [], + row: [], + column: [], + color: [], + size: [], + angle: [] + }; + + wordCloudCell.color.push(dimList[0].uniqueID); + + if (measureList.length > 0) { + wordCloudCell.size.push(measureList[0].uniqueID); + } + + return { + chartType: ChartType.WORD_CLOUD, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: wordCloudCell, + dataset + }; + }; + + const calFunnelChart = (): ScoreResult => { + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + //维度数=1,指标数=1,或者维度数=0,指标数>=2, + const rule1Score = 1.0; + totalScore += rule1Score; + if ((dimList.length === 1 && measureList.length === 1) || (dimList.length === 0 && measureList.length >= 2)) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.FUNNEL, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //用户目的=Trend + const rule2Score = 5.0; + totalScore += rule2Score; + if (purpose === UserPurpose.TREND) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + const { funnelCell, dataset: funnelDataset } = assignFunnelChart(originDataset, dimensionID, measureID, aliasMap); + + return { + chartType: ChartType.FUNNEL, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: funnelCell, + dataset: funnelDataset + }; + }; + + const calDualAxis = (): ScoreResult => { + //多度量且度量之间差异过大,用双轴图 + + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + if (!(measureList.length === 2 && dimList.length > 0)) { + return { + chartType: ChartType.DUAL_AXIS, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //计算cells + const dualAxisData = assignDualAxis(datasetWithoutFold, dimensionID, [measureID[0]], [measureID[1]], aliasMap); + + const { error: newError, errorMsg, dataset, cell: newCell, colorItems, aliasMap: newAliasMap } = dualAxisData; + + if (newError) { + return { + chartType: ChartType.DUAL_AXIS, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0, + error: error ? errMsg : null + }; + } + + //根据cell决定参与图表推荐的字段 + const measureLength = measureList.length; + + //指标数=2 + const rule1Score = 1.0; + totalScore += rule1Score; + if (measureLength === 2 && newCell.x.length > 0) { + score += rule1Score; + scoreDetails.rule1 = rule1Score; + } else { + return { + chartType: ChartType.DUAL_AXIS, + score: 0, + scoreDetails, + originScore: 0, + fullMark: 0 + }; + } + + //指标最大值里面最大的/指标Q1(下四分位数)里面最小的>100(不同指标数值不在同一个量级) + const rule2Score = 1.0; + totalScore += rule2Score; + let minQ1 = Math.min(...measureList.map(measure => Math.abs(measure.Q1))); + let maxMax = Math.max(...measureList.map(measure => Math.abs(measure.max))); + + if (minQ1 === 0) { + minQ1 = 1; + } + if (maxMax === 0) { + maxMax = 1; + } + + if (maxMax / minQ1 > 100) { + score += rule2Score; + scoreDetails.rule2 = rule2Score; + } + + /* //同一个指标里面最小bar的数值/最大bar的数值>1% + const rule3Score = 3.0 + totalScore += rule3Score + const score3Flag = measureList.reduce((prev, cur) => { + if (prev) { + return cur.min / cur.max > 0.01 + } + return false + }, true) + if (score3Flag) { + score += rule3Score + scoreDetails .rule3 = rule3Score + + } */ + + //2<=图元数量<=20 (根据屏幕尺寸调整) + const rule4Score = 1.0; + totalScore += rule4Score; + + const colorSize = newCell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + const axisSize = dataUtils.unique(uniqueIdMap[newCell.x[0]].data).length; + + const dataSize = axisSize; + if (dataSize <= MAX_BAR_NUMBER && dataSize >= MIN_BAR_NUMBER) { + score += rule4Score; + scoreDetails.rule4 = rule4Score; + } + + return { + chartType: ChartType.DUAL_AXIS, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell: newCell, + dataset + }; + }; + + const calTable = (): ScoreResult => { + //图例数量过多时,推荐表格 + let score = 0; + let totalScore = 0; + const scoreDetails: any = {}; + + const rule1Score = 2.0; + totalScore += rule1Score; + + const colorSize = cell.color.length > 0 ? dataUtils.unique(dataset.map(data => data[COLOR_FIELD])).length : 1; + + let axisSize; + if (cell.x.length > 0) { + axisSize = dataUtils.unique(uniqueIdMap[cell.x[0]].data).length; + } else { + axisSize = 1; + } + + const dataSize = colorSize * axisSize; + + if (dataSize >= 100) { + score += rule1Score; + scoreDetails.rule4 = rule1Score; + } + + return { + chartType: ChartType.TABLE, + score: score / totalScore, + originScore: score, + fullMark: totalScore, + scoreDetails, + cell, + dataset + }; + }; + + const scoreCalculators = [ + calBar, + calBarPercent, + calBarParallel, + calCombination, + calScatterplot, + calLineChart, + // calLineChartCombine, + calPieChart, + calMeasureCard, + calRadar, + calWordCloud, + calFunnelChart, + calDualAxis, + calTable + ]; + + return scoreCalculators; +}; diff --git a/packages/chart-advisor/src/tests/Q1.test.ts b/packages/chart-advisor/src/tests/Q1.test.ts new file mode 100644 index 00000000..4610bc8f --- /dev/null +++ b/packages/chart-advisor/src/tests/Q1.test.ts @@ -0,0 +1,12 @@ +import { calQuantile } from '../dataUtil'; + +test('should be true', () => { + const mock1 = [0, 679042241.42, 1084634383.2, 485539225.08]; + + const Q11 = calQuantile({ data: mock1 }, 0.25); + expect(Q11).toBe(582290733.25); + + const mock2 = [null, 0.02, 0.14, 0.02]; + const Q12 = calQuantile({ data: mock2 }, 0.25); + expect(Q12).toBe(0.02); +}); diff --git a/packages/chart-advisor/src/tests/base.test.ts b/packages/chart-advisor/src/tests/base.test.ts new file mode 100644 index 00000000..f5ec372d --- /dev/null +++ b/packages/chart-advisor/src/tests/base.test.ts @@ -0,0 +1,150 @@ +import { chartAdvisor } from '../index'; +import { AdviserParams, DimensionField, AdviseResult, ChartType } from '../type'; + +const mockDataset = [ + { + '231026105743048': '5734340.8279953', + '231026105743063': '家具' + }, + { + '231026105743048': '4865589.799788475', + '231026105743063': '办公用品' + }, + { + '231026105743048': '5469023.505149841', + '231026105743063': '技术' + } +]; + +const dimensionList: DimensionField[] = [ + { + uniqueId: 231026105743063, + type: 'string' + } +]; + +const measureList: DimensionField[] = [ + { + uniqueId: 231026105743048, + type: 'number' + } +]; + +const aliasMap = { + '231026105743063': '类别', + '231026105743048': '销售额' +}; + +const mockParams: AdviserParams = { + originDataset: mockDataset, + dimensionList, + measureList, + aliasMap +}; + +function advise() { + const result = chartAdvisor(mockParams); + + console.log(123, 'scores', result); + + const chartType = result.chartType; + + return chartType; +} + +// test('column', () => { +// expect(advise()).toBe(ChartType.COLUMN_PARALLEL); +// }); + +const mockDimList2 = [ + { uniqueId: 1699615168282, type: 'date' }, + { uniqueId: 1699615168283, type: 'string' } +]; +const mockMeaList2 = [{ uniqueId: 1699615168284 }, { uniqueId: 1699615168285 }]; +const mockDataset2 = [ + { '1699615168282': '2022-10-01', '1699615168283': '公司', '1699615168284': '111', '1699615168285': null }, + { + '1699615168282': '2023-01-01', + '1699615168283': '公司', + '1699615168284': '213', + '1699615168285': '0.918918918918919' + }, + { + '1699615168282': '2023-04-01', + '1699615168283': '公司', + '1699615168284': '292', + '1699615168285': '0.37089201877934275' + }, + { + '1699615168282': '2023-07-01', + '1699615168283': '公司', + '1699615168284': '257', + '1699615168285': '-0.11986301369863013' + }, + { + '1699615168282': '2023-10-01', + '1699615168283': '公司', + '1699615168284': '106', + '1699615168285': '-0.5875486381322957' + }, + { '1699615168282': '2022-10-01', '1699615168283': '小型企业', '1699615168284': '93', '1699615168285': null }, + { + '1699615168282': '2023-01-01', + '1699615168283': '小型企业', + '1699615168284': '78', + '1699615168285': '-0.16129032258064516' + }, + { + '1699615168282': '2023-04-01', + '1699615168283': '小型企业', + '1699615168284': '150', + '1699615168285': '0.9230769230769231' + }, + { '1699615168282': '2023-07-01', '1699615168283': '小型企业', '1699615168284': '183', '1699615168285': '0.22' }, + { + '1699615168282': '2023-10-01', + '1699615168283': '小型企业', + '1699615168284': '91', + '1699615168285': '-0.5027322404371585' + }, + { '1699615168282': '2022-10-01', '1699615168283': '消费者', '1699615168284': '196', '1699615168285': null }, + { + '1699615168282': '2023-01-01', + '1699615168283': '消费者', + '1699615168284': '299', + '1699615168285': '0.5255102040816326' + }, + { + '1699615168282': '2023-04-01', + '1699615168283': '消费者', + '1699615168284': '485', + '1699615168285': '0.6220735785953178' + }, + { + '1699615168282': '2023-07-01', + '1699615168283': '消费者', + '1699615168284': '451', + '1699615168285': '-0.07010309278350516' + }, + { + '1699615168282': '2023-10-01', + '1699615168283': '消费者', + '1699615168284': '247', + '1699615168285': '-0.4523281596452328' + } +]; + +test('combination', () => { + function advise() { + const result = chartAdvisor({ + dimensionList: mockDimList2 as any, + measureList: mockMeaList2, + originDataset: mockDataset2 + }); + + const chartType = result.chartType; + + return chartType; + } + expect(advise()).toBe(ChartType.COLUMN_PARALLEL); +}); diff --git a/packages/chart-advisor/src/type.ts b/packages/chart-advisor/src/type.ts new file mode 100644 index 00000000..841e142e --- /dev/null +++ b/packages/chart-advisor/src/type.ts @@ -0,0 +1,231 @@ +//屏幕尺寸:小、中、大 +export enum ScreenSize { + LARGE = 0, + MEDIUM = 1, + SMALL = 2 +} + +//用户目的:对比、趋势、分布、排名、占比、组成、StoryTelling +export enum UserPurpose { + NONE = 0, //未指定目的 + COMPARISON = 1, + TREND = 2, + DISTRIBUTION = 3, + RANK = 4, + PROPORTION = 5, + COMPOSITION = 6, + STORYTELLING = 7 +} + +export interface DimensionField { + uniqueId: number; //该字段的id + type: DataTypeName; //该字段的类型 + isGeoField?: boolean; +} + +export interface MeasureField { + uniqueId: number; +} + +//measure数据集 +export interface MeasureDataset { + uniqueID?: number; + data: number[]; + min?: number; + max?: number; + mean?: number; //平均值 + standardDev?: number; //标准差 + coefficient?: number; //变异系数 + Q1?: number; //下四分位数 +} + +//dimension数据集 +export interface DimensionDataset { + uniqueID?: number; + data: string[]; + dataType?: DataTypeName; + dimensionName?: string; //字段名 + cardinal?: number; //基数(不同值的数量) + ratio?: number; //基数除以数据条数 + isGeoField?: boolean; // 是否为地理字段 +} + +export interface AutoChartCell { + x: UniqueId[]; + y: UniqueId[]; + row: UniqueId[]; //作为透视行的字段 + column: UniqueId[]; //作为透视列的字段 + color?: UniqueId[]; + size?: UniqueId[]; + angle?: UniqueId[]; + value?: UniqueId[]; + text?: UniqueId[]; + group?: UniqueId[]; + error?: boolean; + errMsg?: string; + // 维度展开的信息(笛卡尔积) + cartesianInfo?: CartesianInfo; + // 指标展开的信息(指标平坦化) + foldInfo?: FoldInfo; +} + +export interface CartesianInfo { + key: UniqueId; + fieldList: UniqueId[]; +} + +export interface FoldInfo { + key: UniqueId; + value: UniqueId; + foldMap: { + [key: number]: string; + }; +} + +export interface FieldTypeMap { + [key: number]: DataTypeName; +} + +export type DataItem = { [key: number]: string }; + +export type Dataset = DataItem[]; + +export type Datasets = DataItem[][][][] | Dataset; + +export type UniqueId = number | string; + +export type DataTypeName = 'number' | 'string' | 'date'; + +/** + * vqs 接口中 visData 中的 aliasMap + * 做字段名字映射 + */ +export type AliasMap = { + [key: number]: string; +}; + +/** + * 图表类型枚举 + */ +export enum ChartType { + /** 表格 */ + TABLE = 'table', + /** 明细表 */ + RAW_TABLE = 'raw_table', + /** 透视表 */ + PIVOT_TABLE = 'pivot_table', + + /** 柱状图 */ + COLUMN = 'column', + /** 百分比柱状图 */ + COLUMN_PERCENT = 'column_percent', + /** 并列柱状图 */ + COLUMN_PARALLEL = 'column_parallel', + /** 条形图 */ + BAR = 'bar', + /** 百分比条形图 */ + BAR_PERCENT = 'bar_percent', + /** 并列条形图 */ + BAR_PARALLEL = 'bar_parallel', + + /** 折线图 */ + LINE = 'line', + /** 面积图 */ + AREA = 'area', + /** 百分比面积图 */ + AREA_PERCENT = 'area_percent', + + /** 饼图 */ + PIE = 'pie', + /** 环形饼图 */ + ANNULAR = 'annular', + /** 南丁格尔玫瑰图 */ + ROSE = 'rose', + + /** 散点图 */ + SCATTER = 'scatter', + /** 圆视图 */ + CIRCLE_VIEWS = 'circle_views', + + /** 双轴图 */ + DUAL_AXIS = 'double_axis', + /** 双向条形图 */ + BILATERAL = 'bilateral', + /** 组合图 */ + COMBINATION = 'combination', + + /** 填充地图 */ + MAP = 'map', + /** 标记地图 */ + SCATTER_MAP = 'scatter_map', + + /** 指标卡 */ + MEASURE_CARD = 'measure_card', + /** 对比指标卡 */ + COMPARATIVE_MEASURE_CARD = 'comparative_measure_card', + /** 词云 */ + WORD_CLOUD = 'word_cloud', + /** 直方图 */ + HISTOGRAM = 'histogram', + /** 漏斗图 */ + FUNNEL = 'funnel', + /** 雷达图 */ + RADAR = 'radar', + /** 桑基图 */ + SANKEY = 'sankey', + + /** 扩展自定义类型 */ + EXTEND = 'extend', + /** 单值百分比环形图 */ + PROGRESS = 'progress' +} + +export interface ScoreResult { + chartType: ChartType; + originScore: number; + fullMark: number; + score: number; + scoreDetails: Array<{ name: string; score: number }>; + cell?: AutoChartCell | AutoChartCell[]; + dataset?: Datasets; + error?: any; +} + +export interface PivotTree { + field: UniqueId; + values: { + value: string; + child: PivotTree | null; + }[]; +} + +export interface AdviseResult { + chartType: ChartType; //vizData中的chartType + scores: ScoreResult[]; + error?: any; +} + +interface ScorerParams { + inputDataSet: Dataset; + dimList: DimensionDataset[]; + measureList: MeasureDataset[]; + aliasMap?: AliasMap; + maxRowNum?: number; + maxColNum?: number; + purpose?: UserPurpose; + screen?: ScreenSize; +} + +export type Scorer = (params: ScorerParams) => Array<() => ScoreResult>; + +export interface AdviserParams { + originDataset: Dataset; + dimensionList: DimensionField[]; + measureList: MeasureField[]; + aliasMap?: AliasMap; + maxPivotRow?: number; + maxPivotColumn?: number; + purpose?: UserPurpose; + screen?: ScreenSize; + scorer?: Scorer; +} diff --git a/packages/chart-advisor/tsconfig.cjs.json b/packages/chart-advisor/tsconfig.cjs.json new file mode 100644 index 00000000..9576fc69 --- /dev/null +++ b/packages/chart-advisor/tsconfig.cjs.json @@ -0,0 +1,21 @@ +/** + * NOTE: this file is symlink to '@aeolian/dev-config/tsconfig/build.cjs.json' + */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "target": "es2018", + "module": "CommonJS", + "noEmit": false, + "sourceMap": false, + }, + "exclude": [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.tsx", + "**/*.mock.ts", + "**/tests", + "**/mocks", + ], +} diff --git a/packages/chart-advisor/tsconfig.eslint.json b/packages/chart-advisor/tsconfig.eslint.json new file mode 100644 index 00000000..c6dbef60 --- /dev/null +++ b/packages/chart-advisor/tsconfig.eslint.json @@ -0,0 +1,10 @@ +{ + "extends": "@internal/ts-config/tsconfig.base.json", + "compilerOptions": { + "types": ["jest", "node"], + "lib": ["DOM", "ESNext"], + "baseUrl": "./", + "rootDir": "./" + }, + "include": ["src", "__tests__"] +} diff --git a/packages/chart-advisor/tsconfig.esm.json b/packages/chart-advisor/tsconfig.esm.json new file mode 100644 index 00000000..0bfc1693 --- /dev/null +++ b/packages/chart-advisor/tsconfig.esm.json @@ -0,0 +1,22 @@ +/** + * NOTE: this file is symlink to '@aeolian/dev-config/tsconfig/build.esm.json' + */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "es", + "target": "es2018", + "module": "ESNext", + "noEmit": false, + "sourceMap": false, + "noImplicitAny": false + }, + "exclude": [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.tsx", + "**/*.mock.ts", + "**/tests", + "**/mocks", + ], +} diff --git a/packages/chart-advisor/tsconfig.json b/packages/chart-advisor/tsconfig.json new file mode 100644 index 00000000..c564f002 --- /dev/null +++ b/packages/chart-advisor/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@internal/ts-config/tsconfig.base.json", + "compilerOptions": { + "declaration": true, //Generates corresponding d.ts files. + "composite": true, + "baseUrl": ".", + "rootDir": "src", + "outDir": "es", + "types": [ + "node", + "jest", + ], + "noImplicitAny": false + }, + "include": [ + "src", + ], + // https://www.typescriptlang.org/docs/handbook/project-references.html#what-is-a-project-reference + "references": [ + ], +} diff --git a/packages/chart-advisor/tsconfig.test.json b/packages/chart-advisor/tsconfig.test.json new file mode 100644 index 00000000..e68bcbbf --- /dev/null +++ b/packages/chart-advisor/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + } + }, + "references": [] +} diff --git a/packages/vmind/CHANGELOG.json b/packages/vmind/CHANGELOG.json index 0e55a3ae..a131a209 100644 --- a/packages/vmind/CHANGELOG.json +++ b/packages/vmind/CHANGELOG.json @@ -1,10 +1,16 @@ { "name": "@visactor/vmind", "entries": [ + { + "version": "1.2.5", + "tag": "@visactor/vmind_v1.2.5", + "date": "Tue, 26 Mar 2024 07:36:00 GMT", + "comments": {} + }, { "version": "1.2.4", "tag": "@visactor/vmind_v1.2.4", - "date": "Wed, 21 Feb 2024 12:04:49 GMT", + "date": "Tue, 26 Mar 2024 07:35:59 GMT", "comments": {} }, { diff --git a/packages/vmind/CHANGELOG.md b/packages/vmind/CHANGELOG.md index e9ae5e06..19f0c006 100644 --- a/packages/vmind/CHANGELOG.md +++ b/packages/vmind/CHANGELOG.md @@ -1,9 +1,14 @@ # Change Log - @visactor/vmind -This log was last generated on Wed, 21 Feb 2024 12:04:49 GMT and should not be manually modified. +This log was last generated on Tue, 26 Mar 2024 07:36:00 GMT and should not be manually modified. + +## 1.2.5 +Tue, 26 Mar 2024 07:36:00 GMT + +_Version update only_ ## 1.2.4 -Wed, 21 Feb 2024 12:04:49 GMT +Tue, 26 Mar 2024 07:35:59 GMT _Version update only_ diff --git a/packages/vmind/__tests__/browser/src/constants/mockData.ts b/packages/vmind/__tests__/browser/src/constants/mockData.ts index 749d8ca5..f30fe0d7 100644 --- a/packages/vmind/__tests__/browser/src/constants/mockData.ts +++ b/packages/vmind/__tests__/browser/src/constants/mockData.ts @@ -3663,3 +3663,14 @@ East Asia & Pacific,7.8,8.95,10.18,11.57,13.25 Europe & Central Asia,9.52,10.39,10.93,11.69,12.63`, input: '看下各类别数值分布' }; + +/* + * multi-measure + */ +export const mockUserInput17 = { + csv: `date,ctr,libra_ab_vid,gmv,detection_uv,User,user_count,gpm,ctcvr,version_id,co +2024-03-05,0.024752458745543306,-1,27.05042746931187,683740,31135119,683740,0.0028542157856540976,0.00011098365108464276,-1,0.01765408108609234 +2024-03-06,0.025529744509421266,-1,26.0184806658061,666756,30057727,666756,0.0029282343207837846,0.00011602865071429876,-1,0.018457400571758008 +2024-03-07,0.024929771518461413,-1,26.099474885828787,653568,25685979,653568,0.00305250127580487,0.00012037169363684177,-1,0.019630556105233156`, + input: '使用折线图展示' +}; diff --git a/packages/vmind/__tests__/browser/src/pages/ChartPreview.tsx b/packages/vmind/__tests__/browser/src/pages/ChartPreview.tsx index 49b862b4..0e7c03ed 100644 --- a/packages/vmind/__tests__/browser/src/pages/ChartPreview.tsx +++ b/packages/vmind/__tests__/browser/src/pages/ChartPreview.tsx @@ -11,6 +11,7 @@ const TextArea = Input.TextArea; type IPropsType = { spec: any; + specList?: any; time: | { totalTime: number; @@ -121,6 +122,21 @@ export function ChartPreview(props: IPropsType) { cs.renderAsync(); }, [chartSpace, props, props.spec, props.time]); + useEffect(() => { + //defaultTicker.mode = 'raf'; + const { specList } = props; + specList.forEach((spec: any, index: number) => { + (document.getElementById(`chart-${index}`) as any).innerHTML = ''; + const cs = new VChart(spec, { + dom: `chart-${index}`, + mode: 'desktop-browser', + dpr: 2, + disableDirtyBounds: true + }); + cs.renderAsync(); + }); + }, [props]); + return (
Total Time: {props.costTime / 1000} ms
+Total Time: {props.costTime / 1000} s
spec:
{/*{JSON.stringify(props.spec, null, 4)}*/} diff --git a/packages/vmind/__tests__/browser/src/pages/DataInput.tsx b/packages/vmind/__tests__/browser/src/pages/DataInput.tsx index 0561ce4f..7b65aef0 100644 --- a/packages/vmind/__tests__/browser/src/pages/DataInput.tsx +++ b/packages/vmind/__tests__/browser/src/pages/DataInput.tsx @@ -33,11 +33,13 @@ import { mockUserInput12, mockUserInput13, mockUserInput14, - mockUserInput16 + mockUserInput16, + mockUserInput17 } from '../constants/mockData'; import VMind from '../../../../src/index'; import { Model } from '../../../../src/index'; -import { queryDataset } from '../../../../src/gpt/dataProcess'; +import { isArray } from 'lodash'; +import { mockDataset, mockData2, mockData3, mockData4 } from './mockData'; const TextArea = Input.TextArea; const Option = Select.Option; @@ -52,6 +54,7 @@ type IPropsType = { }, costTime: number ) => void; + onSpecListGenerate: any; }; const demoDataList: { [key: string]: any } = { pie: mockUserInput2, @@ -73,11 +76,12 @@ const demoDataList: { [key: string]: any } = { 'College entrance examination': acceptRatioData, 'Shopping Mall Sales Performance': mallSalesData, 'Global GDP': mockUserInput6Eng, - 'Sales of different drinkings': mockUserInput3Eng + 'Sales of different drinkings': mockUserInput3Eng, + 'Multi measure': mockUserInput17 }; const globalVariables = (import.meta as any).env; -const ModelConfigMap = { +const ModelConfigMap: any = { [Model.SKYLARK2]: { url: globalVariables.VITE_SKYLARK_URL, key: globalVariables.VITE_SKYLARK_KEY }, [Model.SKYLARK]: { url: globalVariables.VITE_SKYLARK_URL, key: globalVariables.VITE_SKYLARK_KEY }, [Model.GPT3_5]: { url: globalVariables.VITE_GPT_URL, key: globalVariables.VITE_GPT_KEY }, @@ -92,11 +96,11 @@ export function DataInput(props: IPropsType) { const [spec, setSpec] = useState