From ce42e69c1acc1efb2ba378a6c41151f78e13e146 Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Wed, 6 Dec 2023 14:12:58 +0530 Subject: [PATCH 1/2] Combo chart example --- example/combo-chart/.gitignore | 24 + example/combo-chart/index.html | 17 + example/combo-chart/package-lock.json | 1260 ++++++++++++++++++++++ example/combo-chart/package.json | 21 + example/combo-chart/public/vite.svg | 1 + example/combo-chart/src/legend-plugin.ts | 128 +++ example/combo-chart/src/main.ts | 706 ++++++++++++ example/combo-chart/src/style.css | 96 ++ example/combo-chart/src/typescript.svg | 1 + example/combo-chart/src/vite-env.d.ts | 1 + example/combo-chart/tsconfig.json | 23 + 11 files changed, 2278 insertions(+) create mode 100644 example/combo-chart/.gitignore create mode 100644 example/combo-chart/index.html create mode 100644 example/combo-chart/package-lock.json create mode 100644 example/combo-chart/package.json create mode 100644 example/combo-chart/public/vite.svg create mode 100644 example/combo-chart/src/legend-plugin.ts create mode 100644 example/combo-chart/src/main.ts create mode 100644 example/combo-chart/src/style.css create mode 100644 example/combo-chart/src/typescript.svg create mode 100644 example/combo-chart/src/vite-env.d.ts create mode 100644 example/combo-chart/tsconfig.json diff --git a/example/combo-chart/.gitignore b/example/combo-chart/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/example/combo-chart/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/example/combo-chart/index.html b/example/combo-chart/index.html new file mode 100644 index 0000000..2452fd4 --- /dev/null +++ b/example/combo-chart/index.html @@ -0,0 +1,17 @@ + + + + + + TS combo chart + + +
+
+ +
+
+
+ + + diff --git a/example/combo-chart/package-lock.json b/example/combo-chart/package-lock.json new file mode 100644 index 0000000..e4fdadc --- /dev/null +++ b/example/combo-chart/package-lock.json @@ -0,0 +1,1260 @@ +{ + "name": "combo-chart", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "combo-chart", + "version": "0.0.0", + "dependencies": { + "@thoughtspot/ts-chart-sdk": "0.0.1-alpha.9", + "chart.js": "^4.3.0", + "chartjs-plugin-datalabels": "^2.2.0", + "lodash": "^4.17.21" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", + "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", + "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", + "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", + "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", + "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", + "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", + "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", + "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", + "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", + "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", + "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@thoughtspot/ts-chart-sdk": { + "version": "0.0.1-alpha.9", + "resolved": "https://registry.npmjs.org/@thoughtspot/ts-chart-sdk/-/ts-chart-sdk-0.0.1-alpha.9.tgz", + "integrity": "sha512-9+zqeSVSWL9ddbzznjam3FqqUiVbtyuppiE/MTLwOPzaaCS6RRfcJ5/pKiZTZrh4E1LTnlR25jU91HYyUAvaMA==", + "dependencies": { + "lodash": "^4.17.21", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } + }, + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, + "node_modules/chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "peerDependencies": { + "chart.js": ">=3.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/rollup": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", + "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.6.1", + "@rollup/rollup-android-arm64": "4.6.1", + "@rollup/rollup-darwin-arm64": "4.6.1", + "@rollup/rollup-darwin-x64": "4.6.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", + "@rollup/rollup-linux-arm64-gnu": "4.6.1", + "@rollup/rollup-linux-arm64-musl": "4.6.1", + "@rollup/rollup-linux-x64-gnu": "4.6.1", + "@rollup/rollup-linux-x64-musl": "4.6.1", + "@rollup/rollup-win32-arm64-msvc": "4.6.1", + "@rollup/rollup-win32-ia32-msvc": "4.6.1", + "@rollup/rollup-win32-x64-msvc": "4.6.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.5.tgz", + "integrity": "sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "dev": true, + "optional": true + }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", + "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", + "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", + "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", + "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", + "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", + "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", + "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", + "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", + "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", + "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", + "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "dev": true, + "optional": true + }, + "@thoughtspot/ts-chart-sdk": { + "version": "0.0.1-alpha.9", + "resolved": "https://registry.npmjs.org/@thoughtspot/ts-chart-sdk/-/ts-chart-sdk-0.0.1-alpha.9.tgz", + "integrity": "sha512-9+zqeSVSWL9ddbzznjam3FqqUiVbtyuppiE/MTLwOPzaaCS6RRfcJ5/pKiZTZrh4E1LTnlR25jU91HYyUAvaMA==", + "requires": { + "lodash": "^4.17.21", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } + }, + "chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, + "chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "requires": {} + }, + "esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, + "rollup": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", + "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.6.1", + "@rollup/rollup-android-arm64": "4.6.1", + "@rollup/rollup-darwin-arm64": "4.6.1", + "@rollup/rollup-darwin-x64": "4.6.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", + "@rollup/rollup-linux-arm64-gnu": "4.6.1", + "@rollup/rollup-linux-arm64-musl": "4.6.1", + "@rollup/rollup-linux-x64-gnu": "4.6.1", + "@rollup/rollup-linux-x64-musl": "4.6.1", + "@rollup/rollup-win32-arm64-msvc": "4.6.1", + "@rollup/rollup-win32-ia32-msvc": "4.6.1", + "@rollup/rollup-win32-x64-msvc": "4.6.1", + "fsevents": "~2.3.2" + } + }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true + }, + "vite": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.5.tgz", + "integrity": "sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==", + "dev": true, + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + } + } + } +} diff --git a/example/combo-chart/package.json b/example/combo-chart/package.json new file mode 100644 index 0000000..d6e00a0 --- /dev/null +++ b/example/combo-chart/package.json @@ -0,0 +1,21 @@ +{ + "name": "combo-chart", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.0" + }, + "dependencies": { + "@thoughtspot/ts-chart-sdk": "0.0.1-alpha.9", + "chart.js": "^4.3.0", + "chartjs-plugin-datalabels": "^2.2.0", + "lodash": "^4.17.21" + } +} diff --git a/example/combo-chart/public/vite.svg b/example/combo-chart/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/example/combo-chart/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/combo-chart/src/legend-plugin.ts b/example/combo-chart/src/legend-plugin.ts new file mode 100644 index 0000000..d9e87d2 --- /dev/null +++ b/example/combo-chart/src/legend-plugin.ts @@ -0,0 +1,128 @@ +import _ from 'lodash'; + +const getOrCreateLegendList = (chart, id) => { + const legendContainer = document.getElementById(id); + let listContainer = legendContainer.querySelector('ul'); + + if (!listContainer) { + listContainer = document.createElement('ul'); + listContainer.style.display = 'flex'; + listContainer.style.flexDirection = 'column'; + listContainer.style.margin = '0'; + listContainer.style.padding = '0'; + listContainer.style.height = '95vh'; + listContainer.style.overflowY = 'overlay'; + listContainer.style.alignItems = 'end'; + listContainer.style.paddingRight = '10px'; + + legendContainer.appendChild(listContainer); + } + + return listContainer; +}; + +export const htmlLegendPlugin = { + id: 'htmlLegendPlugin', + afterUpdate(chart, args, options) { + const ul = getOrCreateLegendList(chart, 'legend'); + + // Remove old legend items + while (ul.firstChild) { + ul.firstChild.remove(); + } + + // Reuse the built-in legendItems generator + + const chartMap = { + bar: [], + line: [], + stack: [], + scatter: [], + }; + const items = chart.options.plugins.legend.labels.generateLabels(chart); + + items.forEach(style => { + const textParts = style.text.split('- '); + const chartType = textParts[1]; // Extracting the chart type (bar, line, etc.) + + switch (chartType) { + case 'bar': + chartMap.bar.push(style); + break; + case 'line': + chartMap.line.push(style); + break; + case 'bar-stack': + chartMap.stack.push(style); + break; + case 'scatter': + chartMap.scatter.push(style); + break; + default: + break; + } + }); + _.entries(chartMap).forEach(item => { + const div = document.createElement('div'); + div.innerHTML = `

${item[0] + .charAt(0) + .toUpperCase() + item[0].slice(1)}

`; + item[1].forEach(item => { + const li = document.createElement('li'); + li.style.alignItems = 'center'; + li.style.cursor = 'pointer'; + li.style.display = 'flex'; + li.style.flexDirection = 'row'; + li.style.marginLeft = '10px'; + li.style.marginBottom = '10px'; + li.style.minWidth = '110px'; + + li.onclick = () => { + const { type } = chart.config; + if (type === 'pie' || type === 'doughnut') { + // Pie and doughnut charts only have a single dataset + // and visibility is per item + chart.toggleDataVisibility(item.index); + } else { + chart.setDatasetVisibility( + item.datasetIndex, + !chart.isDatasetVisible(item.datasetIndex), + ); + } + chart.update(); + }; + + // Color box + const boxSpan = document.createElement('span'); + boxSpan.style.background = item.fillStyle; + boxSpan.style.borderColor = item.strokeStyle; + boxSpan.style.borderWidth = `${item.lineWidth}px`; + boxSpan.style.display = 'inline-block'; + boxSpan.style.flexShrink = '0'; + boxSpan.style.height = '10px'; + boxSpan.style.marginRight = '10px'; + boxSpan.style.width = '10px'; + boxSpan.style.borderRadius = '100%'; + + // Text + const textContainer = document.createElement('p'); + textContainer.style.color = item.fontColor; + textContainer.style.margin = '0'; + textContainer.style.padding = '0'; + textContainer.style.textDecoration = item.hidden + ? 'line-through' + : ''; + textContainer.style.fontFamily = 'sans-serif'; + textContainer.style.fontSize = '14px'; + + const text = document.createTextNode(item.text.split('- ')[0]); + textContainer.appendChild(text); + + li.appendChild(boxSpan); + li.appendChild(textContainer); + ul.appendChild(div); + div.appendChild(li); + }); + }); + }, +}; diff --git a/example/combo-chart/src/main.ts b/example/combo-chart/src/main.ts new file mode 100644 index 0000000..97b8f23 --- /dev/null +++ b/example/combo-chart/src/main.ts @@ -0,0 +1,706 @@ +/* eslint-disable no-restricted-syntax */ +/** + * @file Combo Chart Implementation from chart.js library + * + * @fileoverview + * + * @author Rohit Singh + * + * Copyright: ThoughtSpot Inc. 2023 + */ + +import { + ChartColumn, + ChartConfig, + ChartModel, + ChartToTSEvent, + ColumnType, + CustomChartContext, + DataPointsArray, + getChartContext, + PointVal, + Query, + ValidationResponse, + VisualProps, +} from '@thoughtspot/ts-chart-sdk'; +import Chart from 'chart.js/auto'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; +import _, { find } from 'lodash'; +import { htmlLegendPlugin } from './legend-plugin'; + +const logger = console; + +Chart.register(ChartDataLabels); + +let globalChartReference: Chart; + +const visualPropKeyMap = ['datalabels', 'hideLegend']; + +const availableColors = [ + '#FF0000', + '#00FF00', + '#0000FF', + '#FFFF00', + '#FF00FF', + '#00FFFF', + '#FF4500', + '#8A2BE2', + '#32CD32', + '#1E90FF', + '#FFD700', + '#9932CC', + '#8B0000', + '#228B22', + '#008080', + '#FF6347', + '#40E0D0', + '#800080', + '#2E8B57', + '#FF8C00', + '#8B4513', + '#008B8B', + '#FF69B4', + '#7CFC00', + '#4169E1', + '#FF1493', + '#ADFF2F', + '#800000', + '#20B2AA', + '#F08080', +]; + +const COMBO_CHART_TYPE = { + bar: 'bar', + line: 'line', + stack: 'bar-stack', + scatter: 'scatter', +}; + +function getDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { + const idx = _.findIndex(dataArr.columns, colId => column.id === colId); + return _.uniq(_.map(dataArr.dataValue, row => row[idx])); +} + +function getSeriesDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { + const idx = _.findIndex(dataArr.columns, colId => column.id === colId); + return _.uniq(_.map(dataArr.dataValue, row => row[idx])); +} + +function getDataForColumnForSeries( + column: ChartColumn, + dataArr: DataPointsArray, + col: any, +) { + const idx = _.findIndex(dataArr.columns, colId => column.id === colId); + return dataArr.dataValue + .filter(item => item[2] === col) + .map(row => row[idx]); +} + +function getChartDataModel( + configDimensions, + dataArr: DataPointsArray, + inputType, +) { + const xAxisColumns = configDimensions?.[0].columns ?? []; + const yAxisColumns = configDimensions?.[1].columns ?? []; + const legend = configDimensions?.[2]?.columns[0] ?? []; + const type = inputType.split('-')[0]; + const isStacked = inputType.split('-').length > 1; + + return { + getLabels: () => getDataForColumn(xAxisColumns[0], dataArr), + getDatasets: () => { + if (!_.isEmpty(legend)) { + return _.map( + getSeriesDataForColumn(legend, dataArr), + (col, idx) => { + return { + label: `${col}- ${inputType}`, + data: getDataForColumnForSeries( + yAxisColumns[0], + dataArr, + col, + ), + type: `${type}`, + // yAxisID: `${type}-y${idx.toString()}`, + stack: `${type}-x0${ + isStacked ? '-stacked' : `y${idx.toString()}` + }`, + yPos: idx, + backgroundColor: '', + borderColor: '', + datalabels: { + anchor: 'end', + }, + }; + }, + ); + } + return _.map(yAxisColumns, (col, idx) => { + return { + label: `${col.name}- ${inputType}`, + data: getDataForColumn(col, dataArr), + // yAxisID: `${type}-y${idx.toString()}`, + stack: `${type}-x0${ + isStacked ? '-stacked' : `y${idx.toString()}` + }`, + type: `${type}`, + backgroundColor: '', + borderColor: '', + yPos: idx, + datalabels: { + anchor: 'end', + }, + }; + }); + }, + + getPointDetails: (xPos: number, yPos: number): PointVal[] => { + console.log(type); + if (!_.isEmpty(legend)) { + return [ + { + columnId: legend.id, + value: getDataForColumn(legend, dataArr)[yPos], + }, + { + columnId: xAxisColumns[0].id, + value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], + }, + { + columnId: yAxisColumns[0].id, + value: getDataForColumn(yAxisColumns[0], dataArr)[xPos], + }, + ]; + } + return [ + { + columnId: xAxisColumns[0].id, + value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], + }, + { + columnId: yAxisColumns[yPos].id, + value: getDataForColumn(yAxisColumns[yPos], dataArr)[xPos], + }, + ]; + }, + }; +} + +function getDataModel(chartModel: ChartModel) { + const xColumnDimension = chartModel.config?.chartConfig?.[0].dimensions.filter( + dim => dim.key === 'x', + )[0]; + // column chart model + const columnChartModel = getChartDataModel( + chartModel.config?.chartConfig?.[0].dimensions ?? [], + chartModel.data?.[0].data ?? ([] as any), + COMBO_CHART_TYPE.bar, + ); + + // line chart model + const lineChartModel = getChartDataModel( + [xColumnDimension, ...chartModel.config?.chartConfig?.[1].dimensions] ?? + [], + chartModel.data?.[1].data ?? ([] as any), + COMBO_CHART_TYPE.line, + ); + + // stacked chart model + const stackedChartModel = getChartDataModel( + [xColumnDimension, ...chartModel.config?.chartConfig?.[2].dimensions] ?? + [], + chartModel.data?.[2].data ?? ([] as any), + COMBO_CHART_TYPE.stack, + ); + + // scatter chart model + const scatterChartModel = getChartDataModel( + [xColumnDimension, ...chartModel.config?.chartConfig?.[3].dimensions] ?? + [], + chartModel.data?.[3].data ?? ([] as any), + COMBO_CHART_TYPE.scatter, + ); + + return { + getLabels: columnChartModel.getLabels, + getDatasets: () => { + const dataSets = [ + ...columnChartModel.getDatasets(), + ...lineChartModel.getDatasets(), + ...stackedChartModel.getDatasets(), + ...scatterChartModel.getDatasets(), + ]; + dataSets.forEach((set, index) => { + set.backgroundColor = availableColors[index % 30]; + set.borderColor = availableColors[index % 30]; + }); + return dataSets; + }, + getPointDetails: (x, y, chartType?: any) => { + if (chartType === COMBO_CHART_TYPE.bar) { + return columnChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.line) { + return lineChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.stack) { + return stackedChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.scatter) { + return scatterChartModel.getPointDetails(x, y); + } + }, + }; +} + +function getParsedEvent(evt: any) { + return _.pick(evt.native, ['clientX', 'clientY']); +} + +function render(ctx: CustomChartContext) { + const chartModel = ctx.getChartModel(); + const dataModel = getDataModel(chartModel); + const allowLabels = _.get( + chartModel.visualProps, + visualPropKeyMap[0], + false, + ); + const allowLegends = _.get( + chartModel.visualProps, + visualPropKeyMap[1], + true, + ); + if (!dataModel) { + return; + } + + try { + const canvas = document.getElementById('chart') as any; + const legend = document.getElementById('legend'); + const chartWrapper = document.getElementById('chartWrapper'); + if (allowLegends) { + legend.style.display = 'unset'; + chartWrapper.style.width = '85%'; + } else { + legend.style.display = 'none'; + chartWrapper.style.width = '100%'; + } + // clear canvas. + canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); + + globalChartReference = new Chart(canvas as any, { + type: 'bar', + data: { + labels: dataModel.getLabels(), + datasets: dataModel.getDatasets() as any, + }, + options: { + animation: { + duration: 0, + }, + plugins: { + // Change options for ALL labels of THIS CHART + datalabels: { + display: allowLabels ? 'auto' : false, + color: 'blue', + textStrokeColor: 'white', + textStrokeWidth: 5, + labels: { + title: { + font: { + weight: 'bold', + }, + }, + value: { + color: 'black', + }, + }, + }, + legend: { + display: false, + }, + }, + + // responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'point', + intersect: true, + }, + onClick: (e: any) => { + const activeElement = e.chart.getActiveElements()[0]; + if (!activeElement) { + ctx.emitEvent(ChartToTSEvent.CloseContextMenu); + return; + } + const dataX = activeElement?.index; + let { + type: chartType, + stack, + yPos, + } = activeElement?.element.$datalabels[0].$context.dataset; + const dataY = yPos; + if (stack.includes('stack')) { + chartType = 'bar-stack'; + } + logger.info( + 'ChartPoint', + dataX, + dataY, + dataModel.getPointDetails(dataX, dataY), + ); + ctx.emitEvent(ChartToTSEvent.OpenContextMenu, { + event: getParsedEvent(e), + clickedPoint: { + tuple: dataModel.getPointDetails( + dataX, + dataY, + chartType, + ), + }, + }); + }, + }, + + plugins: [htmlLegendPlugin], + }); + } catch (e) { + logger.error('renderfailed', e); + throw e; + } +} + +const renderChart = async (ctx: CustomChartContext): Promise => { + if (globalChartReference) { + globalChartReference.destroy(); + } + try { + ctx.emitEvent(ChartToTSEvent.RenderStart); + render(ctx); + } catch (e) { + ctx.emitEvent(ChartToTSEvent.RenderError, { + hasError: true, + error: e, + }); + } finally { + ctx.emitEvent(ChartToTSEvent.RenderComplete); + } +}; + +(async () => { + // populate it for multiple configs, need to populate common x-axis config + const ctx = await getChartContext({ + getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => { + const cols = chartModel.columns; + + const measureColumns = _.filter( + cols, + col => col.type === ColumnType.MEASURE, + ); + + const attributeColumns = _.filter( + cols, + col => col.type === ColumnType.ATTRIBUTE, + ); + + const axisConfig: ChartConfig[] = [ + { + key: 'column', + dimensions: [ + { + key: 'x', + columns: [attributeColumns[0]], + }, + { + key: 'y', + columns: measureColumns.slice(0, 2), + }, + ], + }, + { + key: 'line', + dimensions: [ + { + key: 'y', + columns: measureColumns.slice(0, 2), + }, + ], + }, + { + key: 'stacked-column', + dimensions: [ + { + key: 'y', + columns: measureColumns.slice(0, 2), + }, + ], + }, + { + key: 'scatter', + dimensions: [ + { + key: 'y', + columns: measureColumns.slice(0, 2), + }, + ], + }, + ]; + return axisConfig; + }, + getQueriesFromChartConfig: ( + chartConfig: ChartConfig[], + ): Array => { + const xAxisColumn = chartConfig[0].dimensions.filter( + col => col.key === 'x', + )[0].columns; + const queries = chartConfig.map( + (config: ChartConfig): Query => + _.reduce( + config.dimensions, + (acc: Query, dimension) => { + // we want to avoid adding x axis columns multiple + // times. + if (dimension.key === 'x') { + return acc; + } + return { + queryColumns: [ + ...acc.queryColumns, + ...dimension.columns, + ], + }; + }, + { + queryColumns: [xAxisColumn[0]], + } as Query, + ), + ); + return queries; + }, + renderChart: ctx => renderChart(ctx), + chartConfigEditorDefinition: [ + { + key: 'column', + label: 'Custom Column', + descriptionText: + 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + + 'Should have just 1 column in Y axis with colors columns.', + columnSections: [ + { + key: 'x', + label: 'Custom X Axis', + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: true, + maxColumnCount: 1, + }, + { + key: 'y', + label: 'Custom Y Axis', + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: 'legend', + label: 'Slice with color', + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], + }, + { + key: 'line', + label: 'Custom Line', + descriptionText: + 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + + 'Should have just 1 column in Y axis with colors columns.', + columnSections: [ + { + key: 'y', + label: 'Custom Y Axis', + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: 'legend', + label: 'Slice with color', + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], + }, + { + key: 'stacked-column', + label: 'Custom Stacked Column', + descriptionText: + 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + + 'Should have just 1 column in Y axis with colors columns.', + columnSections: [ + { + key: 'y', + label: 'Custom Y Axis', + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: 'legend', + label: 'Slice with color', + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], + }, + { + key: 'scatter', + label: 'Custom Scatter', + descriptionText: + 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + + 'Should have just 1 column in Y axis with colors columns.', + columnSections: [ + { + key: 'y', + label: 'Custom Y Axis', + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: 'legend', + label: 'Slice with color', + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], + }, + ], + visualPropEditorDefinition: { + elements: [ + { + key: 'datalabels', + type: 'toggle', + defaultValue: false, + label: 'Data Labels', + }, + { + key: 'hideLegend', + type: 'toggle', + defaultValue: true, + label: 'Legends', + }, + ], + }, + validateConfig: (updatedConfig, chartModel): ValidationResponse => { + if (updatedConfig.length <= 0) { + return { + isValid: false, + validationErrorMessage: ['invalid config. no config found'], + }; + } + // assuming 0 is x dimension + const xAxisDimensions = updatedConfig[0]; + const yAxisDimensions = updatedConfig + .map(config => ({ + type: config.key, + dimensions: config.dimensions, + })) + .map(item => { + const filteredColumns = item.dimensions.filter( + column => column.key === 'y', + ); + return { axis: filteredColumns[0], type: item.type }; + }); + + const legendValidation = () => { + const res = { + isValid: true, + errorMessage: undefined, + }; + + for (const chart of updatedConfig) { + const legend = chart.dimensions.find( + dim => dim.key === 'legend', + ); + + if (legend && legend.columns.length > 0) { + // Check if "y" axis column is not empty + const yDimension = chart.dimensions.find( + dim => dim.key === 'y', + ); + + if (yDimension) { + if (yDimension.columns.length === 0) { + res.isValid = false; + res.errorMessage = `Invalid config. Y axis column should not be empty for ${chart.key} chart while slicing with an attribute`; + return res; + } + if (yDimension.columns.length > 1) { + res.isValid = false; + res.errorMessage = `Invalid config. Y axis column should not be more than 1 for ${chart.key} chart while slicing with an attribute`; + return res; + } + } + } + } + return res; + }; + + const xAxisValidation = () => { + if (xAxisDimensions.dimensions[0].columns.length === 0) { + return { + isValid: false, + errorMessage: + 'Invalid config. X axis columns cannot be empty', + }; + } + return { + isValid: true, + }; + }; + + const yAxisValidation = () => { + if ( + find( + yAxisDimensions, + axis => axis.axis.columns.length !== 0, + ) + ) { + return { isValid: true }; + } + return { + isValid: false, + errorMessage: + 'Invalid config. Y axis column cannot be empty (need one y-axis column for any of the chart type)', + }; + }; + + if ( + !xAxisValidation().isValid || + !yAxisValidation().isValid || + !legendValidation().isValid + ) { + return { + isValid: false, + validationErrorMessage: [ + xAxisValidation().errorMessage || + yAxisValidation().errorMessage || + legendValidation().errorMessage, + ], + }; + } + return { + isValid: true, + }; + }, + }); + + renderChart(ctx); +})(); diff --git a/example/combo-chart/src/style.css b/example/combo-chart/src/style.css new file mode 100644 index 0000000..f9c7350 --- /dev/null +++ b/example/combo-chart/src/style.css @@ -0,0 +1,96 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/example/combo-chart/src/typescript.svg b/example/combo-chart/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/example/combo-chart/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/combo-chart/src/vite-env.d.ts b/example/combo-chart/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/example/combo-chart/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/example/combo-chart/tsconfig.json b/example/combo-chart/tsconfig.json new file mode 100644 index 0000000..75abdef --- /dev/null +++ b/example/combo-chart/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} From 2f8499d080c8506832af79ae4fb4bbe6d445e234 Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Wed, 24 Jan 2024 21:20:18 +0530 Subject: [PATCH 2/2] Combo chart bug fixes --- example/combo-chart/index.html | 8 +- example/combo-chart/src/legend-plugin.ts | 216 ++-- example/combo-chart/src/main.ts | 1299 +++++++++++----------- 3 files changed, 786 insertions(+), 737 deletions(-) diff --git a/example/combo-chart/index.html b/example/combo-chart/index.html index 2452fd4..b715a5f 100644 --- a/example/combo-chart/index.html +++ b/example/combo-chart/index.html @@ -5,13 +5,13 @@ TS combo chart - +
-
+
-
+
- + diff --git a/example/combo-chart/src/legend-plugin.ts b/example/combo-chart/src/legend-plugin.ts index d9e87d2..35da099 100644 --- a/example/combo-chart/src/legend-plugin.ts +++ b/example/combo-chart/src/legend-plugin.ts @@ -1,128 +1,124 @@ -import _ from 'lodash'; +import _ from "lodash"; const getOrCreateLegendList = (chart, id) => { - const legendContainer = document.getElementById(id); - let listContainer = legendContainer.querySelector('ul'); + const legendContainer = document.getElementById(id); + let listContainer = legendContainer.querySelector("ul"); - if (!listContainer) { - listContainer = document.createElement('ul'); - listContainer.style.display = 'flex'; - listContainer.style.flexDirection = 'column'; - listContainer.style.margin = '0'; - listContainer.style.padding = '0'; - listContainer.style.height = '95vh'; - listContainer.style.overflowY = 'overlay'; - listContainer.style.alignItems = 'end'; - listContainer.style.paddingRight = '10px'; + if (!listContainer) { + listContainer = document.createElement("ul"); + listContainer.style.display = "flex"; + listContainer.style.flexDirection = "column"; + listContainer.style.margin = "0"; + listContainer.style.height = "95vh"; + listContainer.style.overflowY = "overlay"; + legendContainer.appendChild(listContainer); + } - legendContainer.appendChild(listContainer); - } - - return listContainer; + return listContainer; }; export const htmlLegendPlugin = { - id: 'htmlLegendPlugin', - afterUpdate(chart, args, options) { - const ul = getOrCreateLegendList(chart, 'legend'); + id: "htmlLegendPlugin", + afterUpdate(chart, args, options) { + const ul = getOrCreateLegendList(chart, "legend"); - // Remove old legend items - while (ul.firstChild) { - ul.firstChild.remove(); - } + // Remove old legend items + while (ul.firstChild) { + ul.firstChild.remove(); + } - // Reuse the built-in legendItems generator + // Reuse the built-in legendItems generator - const chartMap = { - bar: [], - line: [], - stack: [], - scatter: [], - }; - const items = chart.options.plugins.legend.labels.generateLabels(chart); + const chartMap = { + column: [], + line: [], + stack: [], + scatter: [], + }; + const items = chart.options.plugins.legend.labels.generateLabels(chart); - items.forEach(style => { - const textParts = style.text.split('- '); - const chartType = textParts[1]; // Extracting the chart type (bar, line, etc.) + items.forEach((style) => { + const textParts = style.text.split("- "); + const chartType = textParts[1]; // Extracting the chart type (bar, line, etc.) - switch (chartType) { - case 'bar': - chartMap.bar.push(style); - break; - case 'line': - chartMap.line.push(style); - break; - case 'bar-stack': - chartMap.stack.push(style); - break; - case 'scatter': - chartMap.scatter.push(style); - break; - default: - break; - } - }); - _.entries(chartMap).forEach(item => { - const div = document.createElement('div'); - div.innerHTML = `

${item[0] - .charAt(0) - .toUpperCase() + item[0].slice(1)}

`; - item[1].forEach(item => { - const li = document.createElement('li'); - li.style.alignItems = 'center'; - li.style.cursor = 'pointer'; - li.style.display = 'flex'; - li.style.flexDirection = 'row'; - li.style.marginLeft = '10px'; - li.style.marginBottom = '10px'; - li.style.minWidth = '110px'; + switch (chartType) { + case "bar": + chartMap.column.push(style); + break; + case "line": + chartMap.line.push(style); + break; + case "bar-stack": + chartMap.stack.push(style); + break; + case "scatter": + chartMap.scatter.push(style); + break; + default: + break; + } + }); + _.entries(chartMap).forEach((item) => { + const div = document.createElement("div"); + div.innerHTML = `

${ + item[0].charAt(0).toUpperCase() + item[0].slice(1) + }

`; + item[1].forEach((item) => { + const li = document.createElement("li"); + li.style.alignItems = "center"; + li.style.cursor = "pointer"; + li.style.display = "flex"; + li.style.flexDirection = "row"; + li.style.marginLeft = "10px"; + li.style.marginBottom = "10px"; + li.style.minWidth = '110px'; + li.style.opacity = item.hidden ? "0.3" : "1"; - li.onclick = () => { - const { type } = chart.config; - if (type === 'pie' || type === 'doughnut') { - // Pie and doughnut charts only have a single dataset - // and visibility is per item - chart.toggleDataVisibility(item.index); - } else { - chart.setDatasetVisibility( - item.datasetIndex, - !chart.isDatasetVisible(item.datasetIndex), - ); - } - chart.update(); - }; + li.onclick = () => { + const { type } = chart.config; + if (type === "pie" || type === "doughnut") { + // Pie and doughnut charts only have a single dataset and visibility is per item + chart.toggleDataVisibility(item.index); + } else { + chart.setDatasetVisibility( + item.datasetIndex, + !chart.isDatasetVisible(item.datasetIndex) + ); + } + chart.update(); + }; - // Color box - const boxSpan = document.createElement('span'); - boxSpan.style.background = item.fillStyle; - boxSpan.style.borderColor = item.strokeStyle; - boxSpan.style.borderWidth = `${item.lineWidth}px`; - boxSpan.style.display = 'inline-block'; - boxSpan.style.flexShrink = '0'; - boxSpan.style.height = '10px'; - boxSpan.style.marginRight = '10px'; - boxSpan.style.width = '10px'; - boxSpan.style.borderRadius = '100%'; + // Color box + const boxSpan = document.createElement("span"); + boxSpan.style.background = item.fillStyle; + boxSpan.style.borderColor = item.strokeStyle; + boxSpan.style.borderWidth = item.lineWidth + "px"; + boxSpan.style.display = "inline-block"; + boxSpan.style.flexShrink = "0"; + boxSpan.style.height = "10px"; + boxSpan.style.marginRight = "10px"; + boxSpan.style.width = "10px"; + boxSpan.style.borderRadius = "100%"; - // Text - const textContainer = document.createElement('p'); - textContainer.style.color = item.fontColor; - textContainer.style.margin = '0'; - textContainer.style.padding = '0'; - textContainer.style.textDecoration = item.hidden - ? 'line-through' - : ''; - textContainer.style.fontFamily = 'sans-serif'; - textContainer.style.fontSize = '14px'; + // Text + const textContainer = document.createElement("p"); + textContainer.style.color = item.fontColor; + textContainer.style.margin = "0"; + textContainer.style.padding = "0"; + textContainer.style.fontFamily = "sans-serif"; + textContainer.style.fontWeight = "500"; + textContainer.style.fontSize = "0.85rem"; - const text = document.createTextNode(item.text.split('- ')[0]); - textContainer.appendChild(text); + const text = document.createTextNode(item.text.split("- ")[0]); + textContainer.appendChild(text); - li.appendChild(boxSpan); - li.appendChild(textContainer); - ul.appendChild(div); - div.appendChild(li); - }); - }); - }, + li.appendChild(boxSpan); + li.appendChild(textContainer); + ul.appendChild(div); + div.appendChild(li); + }); + }); + }, }; diff --git a/example/combo-chart/src/main.ts b/example/combo-chart/src/main.ts index 97b8f23..2f0d706 100644 --- a/example/combo-chart/src/main.ts +++ b/example/combo-chart/src/main.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-syntax */ /** * @file Combo Chart Implementation from chart.js library * @@ -21,686 +20,740 @@ import { PointVal, Query, ValidationResponse, - VisualProps, -} from '@thoughtspot/ts-chart-sdk'; -import Chart from 'chart.js/auto'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; -import _, { find } from 'lodash'; -import { htmlLegendPlugin } from './legend-plugin'; - -const logger = console; - -Chart.register(ChartDataLabels); - -let globalChartReference: Chart; - -const visualPropKeyMap = ['datalabels', 'hideLegend']; - -const availableColors = [ - '#FF0000', - '#00FF00', - '#0000FF', - '#FFFF00', - '#FF00FF', - '#00FFFF', - '#FF4500', - '#8A2BE2', - '#32CD32', - '#1E90FF', - '#FFD700', - '#9932CC', - '#8B0000', - '#228B22', - '#008080', - '#FF6347', - '#40E0D0', - '#800080', - '#2E8B57', - '#FF8C00', - '#8B4513', - '#008B8B', - '#FF69B4', - '#7CFC00', - '#4169E1', - '#FF1493', - '#ADFF2F', - '#800000', - '#20B2AA', - '#F08080', -]; - -const COMBO_CHART_TYPE = { - bar: 'bar', - line: 'line', - stack: 'bar-stack', - scatter: 'scatter', -}; - -function getDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { - const idx = _.findIndex(dataArr.columns, colId => column.id === colId); - return _.uniq(_.map(dataArr.dataValue, row => row[idx])); -} - -function getSeriesDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { - const idx = _.findIndex(dataArr.columns, colId => column.id === colId); - return _.uniq(_.map(dataArr.dataValue, row => row[idx])); -} - -function getDataForColumnForSeries( + } from "@thoughtspot/ts-chart-sdk"; + import Chart from "chart.js/auto"; + import { htmlLegendPlugin } from "./legend-plugin"; + import ChartDataLabels from "chartjs-plugin-datalabels"; + import _ from "lodash"; + + const logger = console; + + Chart.register(ChartDataLabels); + + let globalChartReference: Chart; + + const visualPropKeyMap = ["datalabels", "hideLegend"]; + + const availableColors = [ + "#66CCFF", + "#E3394A", + "#F5CB4E", + "#32CD32", + "#FF00FF", + "#00FFFF", + "#FF4500", + "#8A2BE2", + "#1E90FF", + "#FFD700", + "#9932CC", + "#8B0000", + "#228B22", + "#008080", + "#FF6347", + "#40E0D0", + "#800080", + "#2E8B57", + "#FF8C00", + "#8B4513", + "#008B8B", + "#FF69B4", + "#7CFC00", + "#4169E1", + "#FF1493", + "#ADFF2F", + "#800000", + "#20B2AA", + "#F08080", + ]; + + const COMBO_CHART_TYPE = { + bar: "bar", + line: "line", + stack: "bar-stack", + scatter: "scatter", + }; + + const MIN_WIDTH_TO_SHOW_LEGEND = 300; + + const CHART_ORDER = { + bar: 0, + line: -1, + stack: 0, + scatter: -2, + }; + + const numberFormatter = (value: number) => { + if (value > 1000000000) { + return (value / 1000000000).toFixed(2) + "B"; + } + if (value > 1000000) { + return (value / 1000000).toFixed(2) + "M"; + } + if (value > 1000) { + return (value / 1000).toFixed(2) + "K"; + } + }; + + function getDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { + const idx = _.findIndex(dataArr.columns, (colId: any) => column.id === colId); + return _.uniq(_.map(dataArr.dataValue, (row: { [x: string]: any; }) => row[idx])); + } + + function getSeriesDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { + const idx = _.findIndex(dataArr.columns, (colId: any) => column.id === colId); + return _.uniq(_.map(dataArr.dataValue, (row: { [x: string]: any; }) => row[idx])); + } + + function getDataForColumnForSeries( column: ChartColumn, dataArr: DataPointsArray, - col: any, -) { - const idx = _.findIndex(dataArr.columns, colId => column.id === colId); + legendValue: any + ) { + const idx = _.findIndex(dataArr.columns, (colId: any) => column.id === colId); return dataArr.dataValue - .filter(item => item[2] === col) - .map(row => row[idx]); -} - -function getChartDataModel( - configDimensions, + .filter((item: any[]) => item[2] === legendValue) // item[2] -> Legend data value + .map((row: { [x: string]: any; }) => row[idx]); + } + + function getChartDataModel( + configDimensions: any[], dataArr: DataPointsArray, - inputType, -) { - const xAxisColumns = configDimensions?.[0].columns ?? []; - const yAxisColumns = configDimensions?.[1].columns ?? []; - const legend = configDimensions?.[2]?.columns[0] ?? []; - const type = inputType.split('-')[0]; - const isStacked = inputType.split('-').length > 1; - + chartType: string + ) { + const xAxisColumns = + configDimensions.find((dim: { key: string; }) => dim.key === "x")?.columns ?? []; + const yAxisColumns = + configDimensions.find((dim: { key: string; }) => dim.key === "y")?.columns ?? []; + const legend = + configDimensions.find((dim: { key: string; }) => dim.key === "legend")?.columns[0] ?? []; + const type = chartType.split("-")[0]; + const isStacked = chartType.split("-").length > 1; + return { - getLabels: () => getDataForColumn(xAxisColumns[0], dataArr), - getDatasets: () => { - if (!_.isEmpty(legend)) { - return _.map( - getSeriesDataForColumn(legend, dataArr), - (col, idx) => { - return { - label: `${col}- ${inputType}`, - data: getDataForColumnForSeries( - yAxisColumns[0], - dataArr, - col, - ), - type: `${type}`, - // yAxisID: `${type}-y${idx.toString()}`, - stack: `${type}-x0${ - isStacked ? '-stacked' : `y${idx.toString()}` - }`, - yPos: idx, - backgroundColor: '', - borderColor: '', - datalabels: { - anchor: 'end', - }, - }; - }, - ); - } - return _.map(yAxisColumns, (col, idx) => { - return { - label: `${col.name}- ${inputType}`, - data: getDataForColumn(col, dataArr), - // yAxisID: `${type}-y${idx.toString()}`, - stack: `${type}-x0${ - isStacked ? '-stacked' : `y${idx.toString()}` - }`, - type: `${type}`, - backgroundColor: '', - borderColor: '', - yPos: idx, - datalabels: { - anchor: 'end', - }, - }; - }); - }, - - getPointDetails: (xPos: number, yPos: number): PointVal[] => { - console.log(type); - if (!_.isEmpty(legend)) { - return [ - { - columnId: legend.id, - value: getDataForColumn(legend, dataArr)[yPos], - }, - { - columnId: xAxisColumns[0].id, - value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], - }, - { - columnId: yAxisColumns[0].id, - value: getDataForColumn(yAxisColumns[0], dataArr)[xPos], - }, - ]; - } - return [ - { - columnId: xAxisColumns[0].id, - value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], + getLabels: () => getDataForColumn(xAxisColumns[0], dataArr), + getDatasets: () => { + if (!_.isEmpty(legend)) { + return _.map( + getSeriesDataForColumn(legend, dataArr), + (legendValue: any, idx: { toString: () => string; }) => { + return { + label: `${legendValue}- ${chartType}`, + data: getDataForColumnForSeries( + yAxisColumns[0], + dataArr, + legendValue + ), + type: `${type}`, + // yAxisID: `${type}-y${idx.toString()}`, + stack: `${type}-x0${ + isStacked ? "-stacked" : "y" + idx.toString() + }`, + yPos: idx, + backgroundColor: "", + borderColor: "", + datalabels: { + anchor: "end", }, - { - columnId: yAxisColumns[yPos].id, - value: getDataForColumn(yAxisColumns[yPos], dataArr)[xPos], - }, - ]; - }, + order: CHART_ORDER[chartType], + }; + } + ); + } else { + return _.map(yAxisColumns, (col: { name: any; }, idx: { toString: () => string; }) => { + return { + label: `${col.name}- ${chartType}`, + data: getDataForColumn(col, dataArr), + //yAxisID: `${type}-y${idx.toString()}`, + stack: `${type}-x0${isStacked ? "-stacked" : "y" + idx.toString()}`, + type: `${type}`, + backgroundColor: "", + borderColor: "", + yPos: idx, + datalabels: { + anchor: "end", + }, + order: CHART_ORDER[chartType], + }; + }); + } + }, + + getPointDetails: (xPos: number, yPos: number): PointVal[] => { + if (!_.isEmpty(legend)) { + return [ + { + columnId: legend.id, + value: getDataForColumn(legend, dataArr)[yPos], + }, + { + columnId: xAxisColumns[0].id, + value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], + }, + { + columnId: yAxisColumns[0].id, + value: getDataForColumn(yAxisColumns[0], dataArr)[xPos], + }, + ]; + } else { + return [ + { + columnId: xAxisColumns[0].id, + value: getDataForColumn(xAxisColumns[0], dataArr)[xPos], + }, + { + columnId: yAxisColumns[yPos].id, + value: getDataForColumn(yAxisColumns[yPos], dataArr)[xPos], + }, + ]; + } + }, }; -} - -function getDataModel(chartModel: ChartModel) { - const xColumnDimension = chartModel.config?.chartConfig?.[0].dimensions.filter( - dim => dim.key === 'x', - )[0]; + } + + function getDataModel(chartModel: ChartModel) { + const xColumnDimension = chartModel.config?.chartConfig + ?.find((config: { key: string; }) => config.key === "xAxis") + .dimensions.filter((dim: { key: string; }) => dim.key === "x")[0]; // column chart model const columnChartModel = getChartDataModel( - chartModel.config?.chartConfig?.[0].dimensions ?? [], - chartModel.data?.[0].data ?? ([] as any), - COMBO_CHART_TYPE.bar, + [ + xColumnDimension, + ...chartModel.config?.chartConfig?.find( + (config: { key: string; }) => config.key === COMBO_CHART_TYPE.bar + ).dimensions, + ] ?? [], + chartModel.data?.[1].data ?? ([] as any), + COMBO_CHART_TYPE.bar ); - + // line chart model const lineChartModel = getChartDataModel( - [xColumnDimension, ...chartModel.config?.chartConfig?.[1].dimensions] ?? - [], - chartModel.data?.[1].data ?? ([] as any), - COMBO_CHART_TYPE.line, + [ + xColumnDimension, + ...chartModel.config?.chartConfig?.find( + (config: { key: string; }) => config.key === COMBO_CHART_TYPE.line + ).dimensions, + ] ?? [], + chartModel.data?.[2].data ?? ([] as any), + COMBO_CHART_TYPE.line ); - + // stacked chart model const stackedChartModel = getChartDataModel( - [xColumnDimension, ...chartModel.config?.chartConfig?.[2].dimensions] ?? - [], - chartModel.data?.[2].data ?? ([] as any), - COMBO_CHART_TYPE.stack, + [ + xColumnDimension, + ...chartModel.config?.chartConfig?.find( + (config: { key: string; }) => config.key === COMBO_CHART_TYPE.stack + ).dimensions, + ] ?? [], + chartModel.data?.[3].data ?? ([] as any), + COMBO_CHART_TYPE.stack ); - + // scatter chart model const scatterChartModel = getChartDataModel( - [xColumnDimension, ...chartModel.config?.chartConfig?.[3].dimensions] ?? - [], - chartModel.data?.[3].data ?? ([] as any), - COMBO_CHART_TYPE.scatter, + [ + xColumnDimension, + ...chartModel.config?.chartConfig?.find( + (config: { key: string; }) => config.key === COMBO_CHART_TYPE.scatter + ).dimensions, + ] ?? [], + chartModel.data?.[4].data ?? ([] as any), + COMBO_CHART_TYPE.scatter ); - + return { - getLabels: columnChartModel.getLabels, - getDatasets: () => { - const dataSets = [ - ...columnChartModel.getDatasets(), - ...lineChartModel.getDatasets(), - ...stackedChartModel.getDatasets(), - ...scatterChartModel.getDatasets(), - ]; - dataSets.forEach((set, index) => { - set.backgroundColor = availableColors[index % 30]; - set.borderColor = availableColors[index % 30]; - }); - return dataSets; - }, - getPointDetails: (x, y, chartType?: any) => { - if (chartType === COMBO_CHART_TYPE.bar) { - return columnChartModel.getPointDetails(x, y); - } - if (chartType === COMBO_CHART_TYPE.line) { - return lineChartModel.getPointDetails(x, y); - } - if (chartType === COMBO_CHART_TYPE.stack) { - return stackedChartModel.getPointDetails(x, y); - } - if (chartType === COMBO_CHART_TYPE.scatter) { - return scatterChartModel.getPointDetails(x, y); - } - }, + getLabels: columnChartModel.getLabels, + getDatasets: () => { + const dataSets = [ + ...columnChartModel.getDatasets(), + ...lineChartModel.getDatasets(), + ...stackedChartModel.getDatasets(), + ...scatterChartModel.getDatasets(), + ]; + dataSets.forEach((set, index) => { + set.backgroundColor = availableColors[index % 30]; + set.borderColor = availableColors[index % 30]; + }); + return dataSets; + }, + getPointDetails: (x: number, y: number, chartType?: any) => { + if (chartType === COMBO_CHART_TYPE.bar) { + return columnChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.line) { + return lineChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.stack) { + return stackedChartModel.getPointDetails(x, y); + } + if (chartType === COMBO_CHART_TYPE.scatter) { + return scatterChartModel.getPointDetails(x, y); + } + }, }; -} - -function getParsedEvent(evt: any) { - return _.pick(evt.native, ['clientX', 'clientY']); -} - -function render(ctx: CustomChartContext) { + } + + function getParsedEvent(evt: any) { + return _.pick(evt.native, ["clientX", "clientY"]); + } + + function render(ctx: CustomChartContext) { const chartModel = ctx.getChartModel(); const dataModel = getDataModel(chartModel); - const allowLabels = _.get( - chartModel.visualProps, - visualPropKeyMap[0], - false, - ); - const allowLegends = _.get( - chartModel.visualProps, - visualPropKeyMap[1], - true, - ); + const xAxisColumnName = chartModel.config?.chartConfig + ?.find((config: { key: string; }) => config.key === "xAxis") + .dimensions.filter((dim: { key: string; }) => dim.key === "x")[0].columns[0].name; + const allowLabels = _.get(chartModel.visualProps, visualPropKeyMap[0], false); + const allowLegends = _.get(chartModel.visualProps, visualPropKeyMap[1], true); if (!dataModel) { - return; + return; } - + try { - const canvas = document.getElementById('chart') as any; - const legend = document.getElementById('legend'); - const chartWrapper = document.getElementById('chartWrapper'); - if (allowLegends) { - legend.style.display = 'unset'; - chartWrapper.style.width = '85%'; - } else { - legend.style.display = 'none'; - chartWrapper.style.width = '100%'; - } - // clear canvas. - canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); - - globalChartReference = new Chart(canvas as any, { - type: 'bar', - data: { - labels: dataModel.getLabels(), - datasets: dataModel.getDatasets() as any, + const canvas = document.getElementById("chart") as any; + const legend = document.getElementById("legend"); + const chartWrapper = document.getElementById("chartWrapper"); + if (allowLegends) { + legend.style.display = "flex"; + chartWrapper.style.width = "100%"; + } else { + legend.style.display = "flex"; + chartWrapper.style.width = "100%"; + } + // clear canvas. + canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); + globalChartReference = new Chart(canvas as any, { + type: "bar", + data: { + labels: dataModel.getLabels(), + datasets: dataModel.getDatasets() as any, + }, + options: { + animation: { + duration: 0, + }, + scales: { + y: { + ticks: { + callback: (value: number) => { + if (value > 10000000) return numberFormatter(value); + }, + }, }, - options: { - animation: { - duration: 0, + }, + plugins: { + // Change options for ALL labels of THIS CHART + datalabels: { + display: allowLabels ? "auto" : false, + formatter: (value: any) => numberFormatter(value), + color: "blue", + textStrokeColor: "white", + textStrokeWidth: 5, + labels: { + title: { + font: { + weight: "bold", + }, }, - plugins: { - // Change options for ALL labels of THIS CHART - datalabels: { - display: allowLabels ? 'auto' : false, - color: 'blue', - textStrokeColor: 'white', - textStrokeWidth: 5, - labels: { - title: { - font: { - weight: 'bold', - }, - }, - value: { - color: 'black', - }, - }, - }, - legend: { - display: false, - }, + value: { + color: "black", }, - - // responsive: true, - maintainAspectRatio: false, - interaction: { - mode: 'point', - intersect: true, + }, + }, + tooltip: { + enabled: true, + displayColors: false, + position: "nearest", + callbacks: { + label: (item: { dataset: { data: { [x: string]: any; }; label: string; }; dataIndex: string | number; }) => { + const value = item.dataset.data[item.dataIndex]; + const localeValue = numberFormatter(value); + return `${item.dataset.label.split("-")[0]}: ${localeValue}`; }, - onClick: (e: any) => { - const activeElement = e.chart.getActiveElements()[0]; - if (!activeElement) { - ctx.emitEvent(ChartToTSEvent.CloseContextMenu); - return; - } - const dataX = activeElement?.index; - let { - type: chartType, - stack, - yPos, - } = activeElement?.element.$datalabels[0].$context.dataset; - const dataY = yPos; - if (stack.includes('stack')) { - chartType = 'bar-stack'; - } - logger.info( - 'ChartPoint', - dataX, - dataY, - dataModel.getPointDetails(dataX, dataY), - ); - ctx.emitEvent(ChartToTSEvent.OpenContextMenu, { - event: getParsedEvent(e), - clickedPoint: { - tuple: dataModel.getPointDetails( - dataX, - dataY, - chartType, - ), - }, - }); + title: (item: { label: any; }[]) => { + return `${xAxisColumnName}: ${item[0].label}`; }, + }, + titleFont: { size: 11, weight: "none" }, }, - - plugins: [htmlLegendPlugin], - }); + legend: { + display: false, + }, + }, + + // responsive: true, + maintainAspectRatio: false, + interaction: { + mode: "point", + intersect: true, + }, + onClick: (e: any) => { + const activeElement = e.chart.getActiveElements()[0]; + if (!activeElement) { + ctx.emitEvent(ChartToTSEvent.CloseContextMenu); + return; + } + const dataX = activeElement?.index; + let { + type: chartType, + stack, + yPos, + } = activeElement?.element.$datalabels[0].$context.dataset; + const dataY = yPos; + if (stack.includes("stack")) { + chartType = "bar-stack"; + } + logger.info( + "ChartPoint", + dataX, + dataY, + dataModel.getPointDetails(dataX, dataY) + ); + ctx.emitEvent(ChartToTSEvent.OpenContextMenu, { + event: getParsedEvent(e), + clickedPoint: { + tuple: dataModel.getPointDetails(dataX, dataY, chartType), + }, + }); + }, + }, + + plugins: [htmlLegendPlugin], + }); + window.addEventListener("resize", () => { + if ( + window.document.activeElement.getBoundingClientRect().width < + MIN_WIDTH_TO_SHOW_LEGEND + ) { + legend.style.display = "none"; + } else { + legend.style.display = "flex"; + } + }); } catch (e) { - logger.error('renderfailed', e); - throw e; + logger.error("renderfailed", e); + throw e; } -} - -const renderChart = async (ctx: CustomChartContext): Promise => { + } + + const renderChart = async (ctx: CustomChartContext): Promise => { if (globalChartReference) { - globalChartReference.destroy(); + globalChartReference.destroy(); } try { - ctx.emitEvent(ChartToTSEvent.RenderStart); - render(ctx); + ctx.emitEvent(ChartToTSEvent.RenderStart); + render(ctx); } catch (e) { - ctx.emitEvent(ChartToTSEvent.RenderError, { - hasError: true, - error: e, - }); + ctx.emitEvent(ChartToTSEvent.RenderError, { + hasError: true, + error: e, + }); } finally { - ctx.emitEvent(ChartToTSEvent.RenderComplete); + ctx.emitEvent(ChartToTSEvent.RenderComplete); } -}; - -(async () => { + }; + + (async () => { // populate it for multiple configs, need to populate common x-axis config const ctx = await getChartContext({ - getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => { - const cols = chartModel.columns; - - const measureColumns = _.filter( - cols, - col => col.type === ColumnType.MEASURE, - ); - - const attributeColumns = _.filter( - cols, - col => col.type === ColumnType.ATTRIBUTE, - ); - - const axisConfig: ChartConfig[] = [ - { - key: 'column', - dimensions: [ - { - key: 'x', - columns: [attributeColumns[0]], - }, - { - key: 'y', - columns: measureColumns.slice(0, 2), - }, - ], - }, - { - key: 'line', - dimensions: [ - { - key: 'y', - columns: measureColumns.slice(0, 2), - }, - ], - }, - { - key: 'stacked-column', - dimensions: [ - { - key: 'y', - columns: measureColumns.slice(0, 2), - }, - ], - }, - { - key: 'scatter', - dimensions: [ - { - key: 'y', - columns: measureColumns.slice(0, 2), - }, - ], - }, - ]; - return axisConfig; + getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => { + const cols = chartModel.columns; + + const measureColumns = _.filter( + cols, + (col: { type: any; }) => col.type === ColumnType.MEASURE + ); + + const attributeColumns = _.filter( + cols, + (col: { type: any; }) => col.type === ColumnType.ATTRIBUTE + ); + + const axisConfig: ChartConfig[] = [ + { + key: "xAxis", + dimensions: [ + { + key: "x", + columns: [attributeColumns[0]], + }, + ], + }, + { + key: "bar", + dimensions: [ + { + key: "y", + columns: measureColumns.slice(0, 2), + }, + ], + }, + { + key: "line", + dimensions: [], + }, + { + key: "bar-stack", + dimensions: [], + }, + { + key: "scatter", + dimensions: [], + }, + ]; + return axisConfig; + }, + getQueriesFromChartConfig: (chartConfig: ChartConfig[]): Array => { + const xAxisColumn = chartConfig[0].dimensions.filter( + (col: { key: string; }) => col.key === "x" + )[0].columns; + const queries = chartConfig.map( + (config: ChartConfig): Query => + _.reduce( + config.dimensions, + (acc: Query, dimension: { key: string; columns: any; }) => { + // we want to avoid adding x axis columns multiple times. + if (dimension.key === "x") { + return acc; + } + return { + queryColumns: [...acc.queryColumns, ...dimension.columns], + }; + }, + { + queryColumns: [xAxisColumn[0]], + } as Query + ) + ); + return queries; + }, + renderChart: (ctx: any) => renderChart(ctx), + chartConfigEditorDefinition: [ + { + key: "xAxis", + label: "Common Axis", + descriptionText: "X Axis can only have attributes", + columnSections: [ + { + key: "x", + label: "X Axis", + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: true, + maxColumnCount: 1, + }, + ], }, - getQueriesFromChartConfig: ( - chartConfig: ChartConfig[], - ): Array => { - const xAxisColumn = chartConfig[0].dimensions.filter( - col => col.key === 'x', - )[0].columns; - const queries = chartConfig.map( - (config: ChartConfig): Query => - _.reduce( - config.dimensions, - (acc: Query, dimension) => { - // we want to avoid adding x axis columns multiple - // times. - if (dimension.key === 'x') { - return acc; - } - return { - queryColumns: [ - ...acc.queryColumns, - ...dimension.columns, - ], - }; - }, - { - queryColumns: [xAxisColumn[0]], - } as Query, - ), - ); - return queries; + { + key: "bar", + label: "Column", + descriptionText: + "X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. " + + "Should have just 1 column in Y axis with colors columns.", + columnSections: [ + { + key: "y", + label: "Y Axis", + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: "legend", + label: "Slice with color", + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], }, - renderChart: ctx => renderChart(ctx), - chartConfigEditorDefinition: [ + { + key: "line", + label: "Line", + descriptionText: + "X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. " + + "Should have just 1 column in Y axis with colors columns.", + columnSections: [ { - key: 'column', - label: 'Custom Column', - descriptionText: - 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + - 'Should have just 1 column in Y axis with colors columns.', - columnSections: [ - { - key: 'x', - label: 'Custom X Axis', - allowAttributeColumns: true, - allowMeasureColumns: false, - allowTimeSeriesColumns: true, - maxColumnCount: 1, - }, - { - key: 'y', - label: 'Custom Y Axis', - allowAttributeColumns: false, - allowMeasureColumns: true, - allowTimeSeriesColumns: false, - }, - { - key: 'legend', - label: 'Slice with color', - allowAttributeColumns: true, - allowMeasureColumns: false, - allowTimeSeriesColumns: false, - }, - ], + key: "y", + label: "Y Axis", + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, }, { - key: 'line', - label: 'Custom Line', - descriptionText: - 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + - 'Should have just 1 column in Y axis with colors columns.', - columnSections: [ - { - key: 'y', - label: 'Custom Y Axis', - allowAttributeColumns: false, - allowMeasureColumns: true, - allowTimeSeriesColumns: false, - }, - { - key: 'legend', - label: 'Slice with color', - allowAttributeColumns: true, - allowMeasureColumns: false, - allowTimeSeriesColumns: false, - }, - ], + key: "legend", + label: "Slice with color", + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, }, + ], + }, + { + key: "bar-stack", + label: "Stacked Column", + descriptionText: + "X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. " + + "Should have just 1 column in Y axis with colors columns.", + columnSections: [ { - key: 'stacked-column', - label: 'Custom Stacked Column', - descriptionText: - 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + - 'Should have just 1 column in Y axis with colors columns.', - columnSections: [ - { - key: 'y', - label: 'Custom Y Axis', - allowAttributeColumns: false, - allowMeasureColumns: true, - allowTimeSeriesColumns: false, - }, - { - key: 'legend', - label: 'Slice with color', - allowAttributeColumns: true, - allowMeasureColumns: false, - allowTimeSeriesColumns: false, - }, - ], + key: "y", + label: "Y Axis", + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, }, { - key: 'scatter', - label: 'Custom Scatter', - descriptionText: - 'X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. ' + - 'Should have just 1 column in Y axis with colors columns.', - columnSections: [ - { - key: 'y', - label: 'Custom Y Axis', - allowAttributeColumns: false, - allowMeasureColumns: true, - allowTimeSeriesColumns: false, - }, - { - key: 'legend', - label: 'Slice with color', - allowAttributeColumns: true, - allowMeasureColumns: false, - allowTimeSeriesColumns: false, - }, - ], + key: "legend", + label: "Slice with color", + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, }, - ], - visualPropEditorDefinition: { - elements: [ - { - key: 'datalabels', - type: 'toggle', - defaultValue: false, - label: 'Data Labels', - }, - { - key: 'hideLegend', - type: 'toggle', - defaultValue: true, - label: 'Legends', - }, - ], + ], }, - validateConfig: (updatedConfig, chartModel): ValidationResponse => { - if (updatedConfig.length <= 0) { - return { - isValid: false, - validationErrorMessage: ['invalid config. no config found'], - }; - } - // assuming 0 is x dimension - const xAxisDimensions = updatedConfig[0]; - const yAxisDimensions = updatedConfig - .map(config => ({ - type: config.key, - dimensions: config.dimensions, - })) - .map(item => { - const filteredColumns = item.dimensions.filter( - column => column.key === 'y', - ); - return { axis: filteredColumns[0], type: item.type }; - }); - - const legendValidation = () => { - const res = { - isValid: true, - errorMessage: undefined, - }; - - for (const chart of updatedConfig) { - const legend = chart.dimensions.find( - dim => dim.key === 'legend', - ); - - if (legend && legend.columns.length > 0) { - // Check if "y" axis column is not empty - const yDimension = chart.dimensions.find( - dim => dim.key === 'y', - ); - - if (yDimension) { - if (yDimension.columns.length === 0) { - res.isValid = false; - res.errorMessage = `Invalid config. Y axis column should not be empty for ${chart.key} chart while slicing with an attribute`; - return res; - } - if (yDimension.columns.length > 1) { - res.isValid = false; - res.errorMessage = `Invalid config. Y axis column should not be more than 1 for ${chart.key} chart while slicing with an attribute`; - return res; - } - } - } + { + key: "scatter", + label: "Scatter", + descriptionText: + "X Axis can only have attributes, Y Axis can only have measures, Color can only have attributes. " + + "Should have just 1 column in Y axis with colors columns.", + columnSections: [ + { + key: "y", + label: "Y Axis", + allowAttributeColumns: false, + allowMeasureColumns: true, + allowTimeSeriesColumns: false, + }, + { + key: "legend", + label: "Slice with color", + allowAttributeColumns: true, + allowMeasureColumns: false, + allowTimeSeriesColumns: false, + }, + ], + }, + ], + visualPropEditorDefinition: { + elements: [ + { + key: "datalabels", + type: "checkbox", + defaultValue: false, + label: "Data Labels", + }, + { + key: "hideLegend", + type: "checkbox", + defaultValue: true, + label: "Legends", + }, + ], + }, + validateConfig: (updatedConfig: any[], chartModel: any): ValidationResponse => { + if (updatedConfig.length <= 0) { + return { + isValid: false, + validationErrorMessage: ["Invalid config. no config found"], + }; + } + + const xAxisDimensions = updatedConfig.find( + (config: { key: string; }) => config.key === "xAxis" + ); + const yAxisDimensions = updatedConfig + .map((config: { key: any; dimensions: any; }) => ({ type: config.key, dimensions: config.dimensions })) + .filter((item: { dimensions: any[]; }) => { + const filteredColumns = item.dimensions.filter( + (column: { key: string; }) => column.key === "y" + ); + return filteredColumns.length; + }); + + const legendValidation = () => { + const res = { + isValid: true, + errorMessage: undefined, + }; + + for (const chart of updatedConfig) { + const legend = chart.dimensions.find((dim: { key: string; }) => dim.key === "legend"); + + if (legend && legend.columns.length > 0) { + // Check if "y" axis column is not empty + const yDimension = chart.dimensions.find((dim: { key: string; }) => dim.key === "y"); + + if (yDimension) { + if (yDimension.columns.length === 0) { + res.isValid = false; + res.errorMessage = `Invalid config. Y axis column should not be empty for ${chart.key} chart while slicing with an attribute`; + return res; } - return res; - }; - - const xAxisValidation = () => { - if (xAxisDimensions.dimensions[0].columns.length === 0) { - return { - isValid: false, - errorMessage: - 'Invalid config. X axis columns cannot be empty', - }; + if (yDimension.columns.length > 1) { + res.isValid = false; + res.errorMessage = `Invalid config. Y axis column should not be more than 1 for ${chart.key} chart while slicing with an attribute`; + return res; } - return { - isValid: true, - }; - }; - - const yAxisValidation = () => { - if ( - find( - yAxisDimensions, - axis => axis.axis.columns.length !== 0, - ) - ) { - return { isValid: true }; + if (legend.columns.length > 1) { + res.isValid = false; + res.errorMessage = `Invalid config. Legend column should not be more than 1 for ${chart.key} chart`; + return res; } - return { - isValid: false, - errorMessage: - 'Invalid config. Y axis column cannot be empty (need one y-axis column for any of the chart type)', - }; - }; - - if ( - !xAxisValidation().isValid || - !yAxisValidation().isValid || - !legendValidation().isValid - ) { - return { - isValid: false, - validationErrorMessage: [ - xAxisValidation().errorMessage || - yAxisValidation().errorMessage || - legendValidation().errorMessage, - ], - }; + } } + } + return res; + }; + + const xAxisValidation = () => { + if (xAxisDimensions.dimensions[0].columns.length === 0) { return { - isValid: true, + isValid: false, + errorMessage: "Invalid config. X axis columns cannot be empty", }; - }, + } else { + return { + isValid: true, + }; + } + }; + + const yAxisValidation = () => { + if (yAxisDimensions.length !== 0) { + return { isValid: true }; + } else { + return { + isValid: false, + errorMessage: + "Invalid config. Y axis column cannot be empty (need one y-axis column for any of the chart type)", + }; + } + }; + + if ( + !xAxisValidation().isValid || + !yAxisValidation().isValid || + !legendValidation().isValid + ) { + return { + isValid: false, + validationErrorMessage: [ + xAxisValidation().errorMessage || + yAxisValidation().errorMessage || + legendValidation().errorMessage, + ], + }; + } + return { + isValid: true, + }; + }, }); - + renderChart(ctx); -})(); + })(); + \ No newline at end of file