@blukavantui/charts-community 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +103 -0
- package/README.md +98 -2
- package/THIRD_PARTY_NOTICES.txt +288 -0
- package/dist/accessibility.d.ts +53 -0
- package/dist/async.d.ts +2 -0
- package/dist/bar.css +1 -0
- package/dist/bar.d.ts +5 -0
- package/dist/bar.js +1 -0
- package/dist/browser/charts-community.css +1 -0
- package/dist/browser/charts-community.js +52 -0
- package/dist/build-audit.json +209 -0
- package/dist/builders/bar.d.ts +6 -0
- package/dist/builders/doughnut.d.ts +6 -0
- package/dist/builders/heatmap.d.ts +16 -0
- package/dist/builders/line.d.ts +6 -0
- package/dist/builders/pie.d.ts +6 -0
- package/dist/builders/radar.d.ts +6 -0
- package/dist/builders/scatter.d.ts +6 -0
- package/dist/catalogue-model.d.ts +11 -0
- package/dist/catalogue-runtime.d.ts +23 -0
- package/dist/catalogue-types.d.ts +167 -0
- package/dist/chart-state.d.ts +18 -0
- package/dist/chunk-2GUWWKHW.js +1 -0
- package/dist/chunk-3PITHTEO.js +1 -0
- package/dist/chunk-4B5MQ6GZ.js +1 -0
- package/dist/chunk-4EQMOBBN.js +1 -0
- package/dist/chunk-4WDZUJDS.js +1 -0
- package/dist/chunk-5NUOOMHV.js +1 -0
- package/dist/chunk-7OCMRHIH.js +1 -0
- package/dist/chunk-CF5Q2DDP.js +1 -0
- package/dist/chunk-EITBQBN2.js +1 -0
- package/dist/chunk-FXNKAHRG.js +1 -0
- package/dist/chunk-IDMAFTWV.js +1 -0
- package/dist/chunk-ILTENI35.js +1 -0
- package/dist/chunk-KRMM7EWI.js +1 -0
- package/dist/chunk-LUUPBBD4.js +1 -0
- package/dist/chunk-MR5VITXX.js +1 -0
- package/dist/chunk-NASBRHIW.js +1 -0
- package/dist/chunk-NXDBBMF5.js +1 -0
- package/dist/chunk-OQ6MP7PY.js +1 -0
- package/dist/chunk-RC2TCMJF.js +1 -0
- package/dist/chunk-RXOGAN23.js +1 -0
- package/dist/chunk-UGXWOWAG.js +2 -0
- package/dist/chunk-UO2KNHTU.js +2 -0
- package/dist/chunk-UQMMBG26.js +3 -0
- package/dist/chunk-VTJ4XPRV.js +1 -0
- package/dist/chunk-WJ3UHH6J.js +1 -0
- package/dist/chunk-XLJUSNGV.js +1 -0
- package/dist/colors.d.ts +15 -0
- package/dist/community.d.ts +11 -0
- package/dist/doughnut.css +1 -0
- package/dist/doughnut.d.ts +5 -0
- package/dist/doughnut.js +1 -0
- package/dist/export.d.ts +5 -0
- package/dist/fonts/dm-sans-LICENSE.txt +93 -0
- package/dist/fonts/dm-sans-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/dm-sans-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/dm-sans-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/dm-sans-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-LICENSE.txt +93 -0
- package/dist/fonts/ibm-plex-mono-latin-100-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-100-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-200-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-200-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-300-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-500-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-600-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-700-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-700-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-100-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-100-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-200-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-200-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-300-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-300-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-400-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-500-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-600-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-700-italic.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-latin-ext-700-normal.woff2 +0 -0
- package/dist/fonts/inter-LICENSE.txt +93 -0
- package/dist/fonts/inter-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-LICENSE.txt +93 -0
- package/dist/fonts/jetbrains-mono-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/lora-LICENSE.txt +93 -0
- package/dist/fonts/lora-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/lora-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/lora-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/lora-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/manifest.json +618 -0
- package/dist/fonts/manrope-LICENSE.txt +93 -0
- package/dist/fonts/manrope-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/manrope-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/merriweather-LICENSE.txt +93 -0
- package/dist/fonts/merriweather-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/merriweather-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/merriweather-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/merriweather-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/montserrat-LICENSE.txt +93 -0
- package/dist/fonts/montserrat-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/montserrat-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/montserrat-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/montserrat-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/playfair-display-LICENSE.txt +93 -0
- package/dist/fonts/playfair-display-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/playfair-display-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/playfair-display-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/playfair-display-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/roboto-LICENSE.txt +93 -0
- package/dist/fonts/roboto-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/roboto-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/roboto-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/roboto-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/source-sans-3-LICENSE.txt +93 -0
- package/dist/fonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
- package/dist/heatmap.css +1 -0
- package/dist/heatmap.d.ts +3 -0
- package/dist/heatmap.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/legend-export.d.ts +9 -0
- package/dist/legend-model.d.ts +39 -0
- package/dist/legend.d.ts +19 -0
- package/dist/limits.d.ts +16 -0
- package/dist/line.css +1 -0
- package/dist/line.d.ts +5 -0
- package/dist/line.js +1 -0
- package/dist/loading-state.d.ts +6 -0
- package/dist/map-assets.d.ts +4 -0
- package/dist/map-resources.d.ts +24 -0
- package/dist/messages.d.ts +3 -0
- package/dist/model.d.ts +41 -0
- package/dist/motion/bar.css +1 -0
- package/dist/motion/bar.d.ts +8 -0
- package/dist/motion/bar.js +1 -0
- package/dist/motion/doughnut.css +1 -0
- package/dist/motion/doughnut.d.ts +8 -0
- package/dist/motion/doughnut.js +1 -0
- package/dist/motion/heatmap.css +1 -0
- package/dist/motion/heatmap.d.ts +7 -0
- package/dist/motion/heatmap.js +1 -0
- package/dist/motion/line.css +1 -0
- package/dist/motion/line.d.ts +8 -0
- package/dist/motion/line.js +1 -0
- package/dist/motion/pie.css +1 -0
- package/dist/motion/pie.d.ts +8 -0
- package/dist/motion/pie.js +1 -0
- package/dist/motion/radar.css +1 -0
- package/dist/motion/radar.d.ts +8 -0
- package/dist/motion/radar.js +1 -0
- package/dist/motion/scatter.css +1 -0
- package/dist/motion/scatter.d.ts +8 -0
- package/dist/motion/scatter.js +1 -0
- package/dist/motion/sparkline.d.ts +7 -0
- package/dist/motion/sparkline.js +1 -0
- package/dist/motion-collection.d.ts +7 -0
- package/dist/motion-community.d.ts +40 -0
- package/dist/motion-policy.d.ts +14 -0
- package/dist/motion.css +1 -0
- package/dist/motion.d.ts +40 -0
- package/dist/motion.js +1 -0
- package/dist/options.d.ts +11 -0
- package/dist/pie.css +1 -0
- package/dist/pie.d.ts +5 -0
- package/dist/pie.js +1 -0
- package/dist/radar.css +1 -0
- package/dist/radar.d.ts +4 -0
- package/dist/radar.js +1 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/scatter.css +1 -0
- package/dist/scatter.d.ts +5 -0
- package/dist/scatter.js +1 -0
- package/dist/sparkline-runtime/runtime.d.ts +10 -0
- package/dist/sparkline-validation.d.ts +3 -0
- package/dist/sparkline.css +25 -0
- package/dist/sparkline.d.ts +44 -0
- package/dist/sparkline.js +1 -0
- package/dist/state-announcement.d.ts +6 -0
- package/dist/styles.css +496 -0
- package/dist/theme.css +1391 -0
- package/dist/types.d.ts +137 -0
- package/dist/validation.d.ts +8 -0
- package/dist/web-api.json +4305 -0
- package/dist/web-motion-sparkline.d.ts +4 -0
- package/edition.json +48 -0
- package/package.json +103 -4
- package/src/accessibility.ts +81 -0
- package/src/assets.d.ts +1 -0
- package/src/async.ts +12 -0
- package/src/bar.ts +13 -0
- package/src/builders/bar.ts +39 -0
- package/src/builders/doughnut.ts +66 -0
- package/src/builders/heatmap.ts +60 -0
- package/src/builders/line.ts +39 -0
- package/src/builders/pie.ts +66 -0
- package/src/builders/radar.ts +65 -0
- package/src/builders/scatter.ts +34 -0
- package/src/catalogue-model.ts +235 -0
- package/src/catalogue-runtime.ts +244 -0
- package/src/catalogue-types.ts +163 -0
- package/src/chart-state.ts +35 -0
- package/src/charts.css +135 -0
- package/src/colors.ts +277 -0
- package/src/community.ts +11 -0
- package/src/doughnut.ts +12 -0
- package/src/export.ts +50 -0
- package/src/heatmap.ts +11 -0
- package/src/index.ts +11 -0
- package/src/legend-export.ts +36 -0
- package/src/legend-model.ts +76 -0
- package/src/legend.css +64 -0
- package/src/legend.ts +90 -0
- package/src/limits.ts +7 -0
- package/src/line.ts +13 -0
- package/src/loading-state.ts +39 -0
- package/src/map-assets.ts +1 -0
- package/src/map-resources.ts +46 -0
- package/src/messages.ts +5 -0
- package/src/model.ts +32 -0
- package/src/motion/bar.ts +15 -0
- package/src/motion/doughnut.ts +14 -0
- package/src/motion/heatmap.ts +14 -0
- package/src/motion/line.ts +15 -0
- package/src/motion/pie.ts +14 -0
- package/src/motion/radar.ts +15 -0
- package/src/motion/scatter.ts +15 -0
- package/src/motion/sparkline.ts +10 -0
- package/src/motion-collection.ts +20 -0
- package/src/motion-community.ts +22 -0
- package/src/motion-policy.ts +36 -0
- package/src/options.ts +24 -0
- package/src/pie.ts +12 -0
- package/src/radar.ts +12 -0
- package/src/runtime.ts +7 -0
- package/src/scatter.ts +13 -0
- package/src/sparkline-runtime/runtime.d.ts +10 -0
- package/src/sparkline-runtime/runtime.js +263 -0
- package/src/sparkline-validation.ts +19 -0
- package/src/sparkline.css +25 -0
- package/src/sparkline.ts +68 -0
- package/src/state-announcement.ts +17 -0
- package/src/types.ts +104 -0
- package/src/validation.ts +75 -0
- package/src/web-motion-sparkline.ts +37 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Local opt-in adapter. The compact SVG runtime and its ordinary defaults stay intact. */
|
|
2
|
+
import { type SparklineOptions, type SparklineEvents } from './sparkline.ts';
|
|
3
|
+
import type { ChartPresentation } from './catalogue-runtime.ts';
|
|
4
|
+
export declare function createMotionSparkline(host: HTMLElement, options: SparklineOptions, events: SparklineEvents, policy: ChartPresentation): import("./sparkline.ts").SparklineInstance;
|
package/edition.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"edition": "community",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"platform": "web",
|
|
5
|
+
"charts": [
|
|
6
|
+
{
|
|
7
|
+
"slug": "sparkline",
|
|
8
|
+
"edition": "community",
|
|
9
|
+
"factory": "createSparkline"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"slug": "line",
|
|
13
|
+
"edition": "community",
|
|
14
|
+
"factory": "createLineChart"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"slug": "bar",
|
|
18
|
+
"edition": "community",
|
|
19
|
+
"factory": "createBarChart"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"slug": "pie",
|
|
23
|
+
"edition": "community",
|
|
24
|
+
"factory": "createPieChart"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"slug": "doughnut",
|
|
28
|
+
"edition": "community",
|
|
29
|
+
"factory": "createDoughnutChart"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"slug": "scatter",
|
|
33
|
+
"edition": "community",
|
|
34
|
+
"factory": "createScatterChart"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"slug": "radar",
|
|
38
|
+
"edition": "community",
|
|
39
|
+
"factory": "createRadarChart"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"slug": "heatmap",
|
|
43
|
+
"edition": "community",
|
|
44
|
+
"factory": "createHeatmap"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"licensing": "BluKavantUI Community license."
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,105 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blukavantui/charts-community",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "BluKavantUI Charts Community: 8 modular web charts. A separate chart bundle.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
"./motion": {
|
|
14
|
+
"types": "./dist/motion.d.ts",
|
|
15
|
+
"import": "./dist/motion.js"
|
|
16
|
+
},
|
|
17
|
+
"./motion/sparkline": {
|
|
18
|
+
"types": "./dist/motion/sparkline.d.ts",
|
|
19
|
+
"import": "./dist/motion/sparkline.js"
|
|
20
|
+
},
|
|
21
|
+
"./motion/line": {
|
|
22
|
+
"types": "./dist/motion/line.d.ts",
|
|
23
|
+
"import": "./dist/motion/line.js"
|
|
24
|
+
},
|
|
25
|
+
"./motion/bar": {
|
|
26
|
+
"types": "./dist/motion/bar.d.ts",
|
|
27
|
+
"import": "./dist/motion/bar.js"
|
|
28
|
+
},
|
|
29
|
+
"./motion/pie": {
|
|
30
|
+
"types": "./dist/motion/pie.d.ts",
|
|
31
|
+
"import": "./dist/motion/pie.js"
|
|
32
|
+
},
|
|
33
|
+
"./motion/doughnut": {
|
|
34
|
+
"types": "./dist/motion/doughnut.d.ts",
|
|
35
|
+
"import": "./dist/motion/doughnut.js"
|
|
36
|
+
},
|
|
37
|
+
"./motion/scatter": {
|
|
38
|
+
"types": "./dist/motion/scatter.d.ts",
|
|
39
|
+
"import": "./dist/motion/scatter.js"
|
|
40
|
+
},
|
|
41
|
+
"./motion/radar": {
|
|
42
|
+
"types": "./dist/motion/radar.d.ts",
|
|
43
|
+
"import": "./dist/motion/radar.js"
|
|
44
|
+
},
|
|
45
|
+
"./motion/heatmap": {
|
|
46
|
+
"types": "./dist/motion/heatmap.d.ts",
|
|
47
|
+
"import": "./dist/motion/heatmap.js"
|
|
48
|
+
},
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
|
+
"import": "./dist/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./sparkline": {
|
|
54
|
+
"types": "./dist/sparkline.d.ts",
|
|
55
|
+
"import": "./dist/sparkline.js"
|
|
56
|
+
},
|
|
57
|
+
"./line": {
|
|
58
|
+
"types": "./dist/line.d.ts",
|
|
59
|
+
"import": "./dist/line.js"
|
|
60
|
+
},
|
|
61
|
+
"./bar": {
|
|
62
|
+
"types": "./dist/bar.d.ts",
|
|
63
|
+
"import": "./dist/bar.js"
|
|
64
|
+
},
|
|
65
|
+
"./pie": {
|
|
66
|
+
"types": "./dist/pie.d.ts",
|
|
67
|
+
"import": "./dist/pie.js"
|
|
68
|
+
},
|
|
69
|
+
"./doughnut": {
|
|
70
|
+
"types": "./dist/doughnut.d.ts",
|
|
71
|
+
"import": "./dist/doughnut.js"
|
|
72
|
+
},
|
|
73
|
+
"./scatter": {
|
|
74
|
+
"types": "./dist/scatter.d.ts",
|
|
75
|
+
"import": "./dist/scatter.js"
|
|
76
|
+
},
|
|
77
|
+
"./radar": {
|
|
78
|
+
"types": "./dist/radar.d.ts",
|
|
79
|
+
"import": "./dist/radar.js"
|
|
80
|
+
},
|
|
81
|
+
"./heatmap": {
|
|
82
|
+
"types": "./dist/heatmap.d.ts",
|
|
83
|
+
"import": "./dist/heatmap.js"
|
|
84
|
+
},
|
|
85
|
+
"./styles.css": "./dist/styles.css",
|
|
86
|
+
"./theme.css": "./dist/theme.css",
|
|
87
|
+
"./browser/charts-community.js": "./dist/browser/charts-community.js",
|
|
88
|
+
"./sparkline.css": "./dist/sparkline.css"
|
|
89
|
+
},
|
|
90
|
+
"files": [
|
|
91
|
+
"dist",
|
|
92
|
+
"src",
|
|
93
|
+
"README.md",
|
|
94
|
+
"LICENSE.txt",
|
|
95
|
+
"THIRD_PARTY_NOTICES.txt",
|
|
96
|
+
"edition.json"
|
|
97
|
+
],
|
|
98
|
+
"sideEffects": [
|
|
99
|
+
"**/*.css"
|
|
100
|
+
],
|
|
101
|
+
"dependencies": {
|
|
102
|
+
"@blukavantui/community": "^1.2.0",
|
|
103
|
+
"echarts": "6.1.0"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { contrastText, interpolateColor } from './colors.ts';
|
|
2
|
+
|
|
3
|
+
// Rendering policy only: input colors and stable data keys are never rewritten.
|
|
4
|
+
// Use a margin above 3:1 so channel rounding cannot turn a pass into a failure.
|
|
5
|
+
export const MARK_CONTRAST = 3.2;
|
|
6
|
+
function rgba(value: string): number[] {
|
|
7
|
+
if (value.startsWith('#')) {
|
|
8
|
+
let h = value.slice(1);
|
|
9
|
+
if (h.length <= 4) h = h.split('').map(c => c + c).join('');
|
|
10
|
+
return [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16)).concat(h.length === 8 ? parseInt(h.slice(6), 16) / 255 : 1);
|
|
11
|
+
}
|
|
12
|
+
const channels = value.match(/[\d.]+/g)?.map(Number);
|
|
13
|
+
return channels && channels.length >= 3 ? [...channels.slice(0, 3), channels[3] ?? 1] : [24, 34, 48, 1];
|
|
14
|
+
}
|
|
15
|
+
function rgb(channels: number[]) { return `rgb(${channels.slice(0, 3).map(v => Math.round(v)).join(',')})`; }
|
|
16
|
+
export function opaqueColor(value: string, surface: string): string {
|
|
17
|
+
const c = rgba(value), bg = rgba(surface);
|
|
18
|
+
return rgb(c.slice(0, 3).map((v, i) => v * c[3] + bg[i] * (1 - c[3])));
|
|
19
|
+
}
|
|
20
|
+
function luminance(value: string): number {
|
|
21
|
+
const c = rgba(value).slice(0, 3).map(v => { const n = v / 255; return n <= .04045 ? n / 12.92 : ((n + .055) / 1.055) ** 2.4; });
|
|
22
|
+
return c[0] * .2126 + c[1] * .7152 + c[2] * .0722;
|
|
23
|
+
}
|
|
24
|
+
export function contrastRatio(a: string, b: string): number {
|
|
25
|
+
const x = luminance(a), y = luminance(b);
|
|
26
|
+
return (Math.max(x, y) + .05) / (Math.min(x, y) + .05);
|
|
27
|
+
}
|
|
28
|
+
export function markColor(value: string, surface: string, minimum = MARK_CONTRAST): string {
|
|
29
|
+
const original = opaqueColor(value, surface);
|
|
30
|
+
if (contrastRatio(original, surface) >= minimum) return original;
|
|
31
|
+
const end = contrastRatio('#000', surface) >= contrastRatio('#fff', surface) ? '#000' : '#fff';
|
|
32
|
+
const target = Math.min(minimum, contrastRatio(end, surface));
|
|
33
|
+
let lo = 0, hi = 1;
|
|
34
|
+
for (let i = 0; i < 20; i++) {
|
|
35
|
+
const mid = (lo + hi) / 2;
|
|
36
|
+
if (contrastRatio(interpolateColor([original, end], mid), surface) >= target) hi = mid;
|
|
37
|
+
else lo = mid;
|
|
38
|
+
}
|
|
39
|
+
return interpolateColor([original, end], hi);
|
|
40
|
+
}
|
|
41
|
+
/** A single-hue luminance ramp also works when hues cannot be distinguished. */
|
|
42
|
+
export function sequentialScale(value: string, surface: string): string[] {
|
|
43
|
+
const end = markColor(value, surface, 9);
|
|
44
|
+
return [markColor(interpolateColor([surface, end], .12), surface), end];
|
|
45
|
+
}
|
|
46
|
+
export const seriesSymbol = (index: number) => ['circle', 'diamond', 'triangle', 'rect', 'roundRect', 'pin', 'arrow', 'emptyCircle', 'emptyDiamond', 'emptyTriangle', 'emptyRect', 'emptyRoundRect'][index % 12];
|
|
47
|
+
export const seriesDash = (index: number) => (['solid', 'dashed', 'dotted'] as const)[(index + Math.floor(index / 12)) % 3];
|
|
48
|
+
export function seriesDecal(index: number, fill: string) {
|
|
49
|
+
const variants = [
|
|
50
|
+
{ symbol: 'rect', symbolSize: 0, dashArrayX: [1, 0], dashArrayY: [1, 8], rotation: 0 },
|
|
51
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [1, 0], dashArrayY: [1, 8], rotation: Math.PI / 4 },
|
|
52
|
+
{ symbol: 'circle', symbolSize: 1, dashArrayX: [2, 7], dashArrayY: [2, 7], rotation: 0 },
|
|
53
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [1, 0], dashArrayY: [1, 8], rotation: -Math.PI / 4 },
|
|
54
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [1, 0], dashArrayY: [1, 6], rotation: 0 },
|
|
55
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [3, 5], dashArrayY: [3, 5], rotation: Math.PI / 4 },
|
|
56
|
+
{ symbol: 'triangle', symbolSize: 1, dashArrayX: [4, 6], dashArrayY: [4, 6], rotation: 0 },
|
|
57
|
+
{ symbol: 'diamond', symbolSize: 1, dashArrayX: [4, 6], dashArrayY: [4, 6], rotation: 0 },
|
|
58
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [1, 0], dashArrayY: [1, 3, 1, 7], rotation: Math.PI / 4 },
|
|
59
|
+
{ symbol: 'circle', symbolSize: 1, dashArrayX: [2, 3], dashArrayY: [2, 3], rotation: 0 },
|
|
60
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [1, 0], dashArrayY: [3, 4], rotation: -Math.PI / 4 },
|
|
61
|
+
{ symbol: 'rect', symbolSize: 1, dashArrayX: [4, 4], dashArrayY: [1, 5], rotation: 0 },
|
|
62
|
+
];
|
|
63
|
+
return { ...variants[index % variants.length], color: contrastText(fill), backgroundColor: 'none' };
|
|
64
|
+
}
|
|
65
|
+
/** Selection and pointer emphasis share a quiet, solid contrast cue. */
|
|
66
|
+
export function highlightStyle(fill: string) {
|
|
67
|
+
return { color: fill, borderColor: contrastText(fill), borderWidth: 2, borderType: 'solid' as const };
|
|
68
|
+
}
|
|
69
|
+
export function markStyle(fill: string, surface: string, selected = false, pattern?: number) {
|
|
70
|
+
return { color: fill, opacity: 1, borderColor: surface, borderWidth: 1.5, borderType: 'solid' as const, ...(selected ? highlightStyle(fill) : {}),
|
|
71
|
+
...(pattern === undefined ? {} : { decal: seriesDecal(pattern, fill) }) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Keep callouts inside the plot and out of its key; the HTML table retains full text. */
|
|
75
|
+
export function boundedLabels(width: number, height: number, top = 8) {
|
|
76
|
+
return ({ labelRect: r }: { labelRect: { x: number; y: number; width: number; height: number } }) => ({
|
|
77
|
+
hideOverlap: true,
|
|
78
|
+
dx: Math.max(0, 8 - r.x) + Math.min(0, width - 8 - r.x - r.width),
|
|
79
|
+
dy: Math.max(0, top - r.y) + Math.min(0, height - 8 - r.y - r.height),
|
|
80
|
+
});
|
|
81
|
+
}
|
package/src/assets.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.css';
|
package/src/async.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Settle even if the underlying browser API ignores cancellation. */
|
|
2
|
+
export async function boundedAwait<T>(work:Promise<T>,signal:AbortSignal|undefined,milliseconds:number,timeoutMessage:string):Promise<T> {
|
|
3
|
+
if(signal?.aborted){void work.catch(()=>{});signal.throwIfAborted();}
|
|
4
|
+
let timer:ReturnType<typeof setTimeout>|undefined,abort:()=>void=()=>{};
|
|
5
|
+
try {
|
|
6
|
+
return await Promise.race([work,new Promise<never>((_,reject)=>{
|
|
7
|
+
abort=()=>reject(signal?.reason??new DOMException('Operation aborted.','AbortError'));
|
|
8
|
+
signal?.addEventListener('abort',abort,{once:true});
|
|
9
|
+
timer=setTimeout(()=>reject(new Error(timeoutMessage)),milliseconds);
|
|
10
|
+
})]);
|
|
11
|
+
} finally {clearTimeout(timer);signal?.removeEventListener('abort',abort);}
|
|
12
|
+
}
|
package/src/bar.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { buildOption } from './builders/bar.ts';
|
|
2
|
+
import { use } from 'echarts/core';
|
|
3
|
+
import { BarChart } from 'echarts/charts';
|
|
4
|
+
import { GridComponent } from 'echarts/components';
|
|
5
|
+
import { createCatalogueChart } from './catalogue-runtime.ts';
|
|
6
|
+
import type { OptionsByKind, ChartPoint } from './catalogue-types.ts';
|
|
7
|
+
import type { ChartEvents } from './types.ts';
|
|
8
|
+
export type { OptionsByKind, ChartPoint } from './catalogue-types.ts';
|
|
9
|
+
export type BarChartOptions = OptionsByKind['bar'];
|
|
10
|
+
export function createBarChart(host: HTMLElement, options: BarChartOptions, events: ChartEvents<ChartPoint> = {}) {
|
|
11
|
+
use([BarChart, GridComponent]);
|
|
12
|
+
return createCatalogueChart(host, 'bar', options, events, buildOption);
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { format, type EChartsCoreOption } from 'echarts/core';
|
|
2
|
+
import type { CatalogueKind, CatalogueOptions, CartesianOptions, RadarOptions, CircularOptions, ScatterOptions, TreemapOptions, SankeyOptions, FunnelOptions, GeoMapOptions, CalendarHeatmapOptions, ChartPoint, TreeNode } from '../catalogue-types.ts';
|
|
3
|
+
import { cataloguePoints, treeValue } from '../catalogue-model.ts';
|
|
4
|
+
import { resolveChartColor, contrastText, interpolateColor } from '../colors.ts';
|
|
5
|
+
import { formatValue } from '../model.ts';
|
|
6
|
+
import { escapeText } from '../validation.ts';
|
|
7
|
+
import { markColor, markStyle, highlightStyle, sequentialScale, seriesSymbol, seriesDash, seriesDecal, boundedLabels } from '../accessibility.ts';
|
|
8
|
+
import type { themeTokens } from '../options.ts';
|
|
9
|
+
type Tokens = ReturnType<typeof themeTokens>;
|
|
10
|
+
export function buildOption(options: CatalogueOptions, t: Tokens, mapName?: string, reducedMotion = false, width = 640): EChartsCoreOption {
|
|
11
|
+
const kind = 'bar' as CatalogueKind;
|
|
12
|
+
const o = options as any, points = cataloguePoints(kind, options);
|
|
13
|
+
const decal = (index: number, fill: string) => o.ShowPatterns ? seriesDecal(index, fill) : undefined;
|
|
14
|
+
const colors = t.palette;
|
|
15
|
+
const color = (c: any, i: number) => markColor(t.forcedColors ? t.text : resolveChartColor(c, colors[i % colors.length], t.dark, t.read), t.surface, kind === 'line' || kind === 'radar' || kind === 'polar' && o.PolarType === 'Line' ? 4.5 : 3.2);
|
|
16
|
+
const style = (key: string, c: any, i: number) => markStyle(color(c, i), t.surface, o.SelectedKey === key);
|
|
17
|
+
const point = (key: string, seriesKey?: string) => points.find(p => p.Key === key && (!seriesKey || p.SeriesKey === seriesKey));
|
|
18
|
+
const namesForLegend: string[] = ['line','radar','bar','polar'].includes(kind) ? o.Series.map((s: any) => s.Label) : kind === 'scatter' ? [...new Set<string>(o.Points.map((p: any) => p.Group || 'Observations'))] : ['pie','doughnut','nightingale','funnel'].includes(kind) ? [...new Set<string>(o.Items.map((i: any) => i.Label))] : [];
|
|
19
|
+
const legend = { data: namesForLegend, show: o.ShowLegend, type: 'scroll', top: 6, left: 10, right: 10, itemWidth: 24, itemHeight: 12, selectedMode: false, pageTextStyle: { color: t.muted }, pageIconColor: t.text, textStyle: { color: t.muted, fontSize: 11 } };
|
|
20
|
+
const label = { show: o.ShowLabels, color: contrastText(t.surface), fontSize: 11, backgroundColor: t.surface, padding: [2, 3], borderRadius: 2 };
|
|
21
|
+
const tooltip = { show: o.ShowTooltip, confine: true, enterable: true, backgroundColor: t.surface, borderColor: t.border, textStyle: { color: t.text, fontSize: 12 }, formatter: (p: any) => {
|
|
22
|
+
const d = p.data?.point as ChartPoint | undefined;
|
|
23
|
+
if (!d)
|
|
24
|
+
return `${escapeText(p.name || '')}${p.value == null || Number.isNaN(p.value) ? ' · No data' : ''}`;
|
|
25
|
+
const extra = d.Date ? `<br/>${escapeText(d.Date)}<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>` : d.X !== undefined ? `<br/>${escapeText(o.XLabel || 'X')}: ${d.X}<br/>${escapeText(o.YLabel || 'Y')}: ${d.Y}${d.Size === undefined ? '' : `<br/>Size: ${d.Size}`}` : `<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>`;
|
|
26
|
+
return `${escapeText(d.SeriesName ? `${d.SeriesName} · ${d.Label}` : d.Label)}${extra}${p.data?.conversion ? `<br/>${escapeText(p.data.conversion)}` : ''}`;
|
|
27
|
+
} };
|
|
28
|
+
const base = { backgroundColor: t.surface, color: colors.map((c, i) => color(c, i)), animation: !!o.EnableAnimation && !reducedMotion, animationDuration: 300, animationDurationUpdate: 150, textStyle: { fontFamily: 'Inter,system-ui,sans-serif', color: t.text }, aria: { enabled: true, description: `${o.Title}. ${o.Description || ''} Exact values and keyboard selection are in the data table.` }, tooltip };
|
|
29
|
+
const axis = { axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: t.muted, fontSize: 10, hideOverlap: true }, splitLine: { lineStyle: { color: t.border, type: 'dashed' } } };
|
|
30
|
+
|
|
31
|
+
const c = options as CartesianOptions;
|
|
32
|
+
const percent = c.Stacking === 'Percent';
|
|
33
|
+
const cat = { ...axis, type: 'category', data: c.Categories.map(x => x.Label), axisLabel: { ...axis.axisLabel, width: Math.min(kind === 'polar' ? 100 : 140, width * .2), overflow: 'truncate' }, splitLine: { show: false } };
|
|
34
|
+
const numeric = { ...axis, type: 'value', max: percent ? 1 : undefined, axisLabel: { ...axis.axisLabel, formatter: (v: number) => percent ? `${Math.round(v * 100)}%` : formatValue(v, o, true) } };
|
|
35
|
+
const insideBars = (kind === 'bar' || (kind === 'polar' && c.PolarType !== 'Line')) && (kind === 'polar' || (!!c.Stacking && c.Stacking !== 'None'));
|
|
36
|
+
const series = c.Series.map((s, si) => ({ id: s.Key, name: s.Label, type: kind === 'line' || (kind === 'polar' && c.PolarType === 'Line') ? 'line' : 'bar', coordinateSystem: kind === 'polar' ? 'polar' : 'cartesian2d', stack: c.Stacking && c.Stacking !== 'None' ? 'total' : undefined, smooth: c.LineStyle === 'Smooth', step: c.LineStyle === 'Step' ? 'middle' : false, connectNulls: false, areaStyle: c.ShowArea ? { opacity: .10 } : undefined, barMaxWidth: 38, symbol: seriesSymbol(si), symbolSize: 8, itemStyle: { ...markStyle(color(s.Color, si), t.surface), decal: decal(si, color(s.Color, si)) }, lineStyle: { width: 2.5, color: color(s.Color, si), type: seriesDash(si) }, label: { ...label, color: insideBars ? contrastText(color(s.Color, si)) : contrastText(t.surface), backgroundColor: insideBars ? color(s.Color, si) : t.surface, position: insideBars ? 'inside' : kind === 'bar' && c.Orientation === 'Horizontal' ? 'right' : 'top', formatter: (p: any) => percent ? `${Math.round(Number(p.value) * 100)}%` : formatValue(p.data.point.Value, o, true) }, labelLayout: { hideOverlap: true }, emphasis: { focus: 'none', scale: false }, blur: { itemStyle: { opacity: 1 }, lineStyle: { opacity: 1 }, label: { opacity: 1 } }, data: s.Values.map((v, i) => ({ value: v === null ? null : percent ? v / (c.Series.reduce((sum, s) => sum + (s.Values[i] || 0), 0) || 1) : v, point: point(c.Categories[i].Key, s.Key), itemStyle: { ...style(c.Categories[i].Key, s.Color, si), decal: decal(si, color(s.Color, si)) }, emphasis: { itemStyle: highlightStyle(color(s.Color, si)) } })) }));
|
|
37
|
+
return kind === 'polar' ? { ...base, legend, polar: { radius: ['12%', '72%'], center: ['50%', '55%'] }, angleAxis: { ...cat, startAngle: 90 }, radiusAxis: numeric, series } : { ...base, legend, grid: { left: 14, right: 22, top: o.ShowLegend ? 48 : 20, bottom: 16, containLabel: true }, xAxis: c.Orientation === 'Horizontal' ? numeric : cat, yAxis: c.Orientation === 'Horizontal' ? { ...cat, inverse: true } : numeric, series };
|
|
38
|
+
|
|
39
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { format, type EChartsCoreOption } from 'echarts/core';
|
|
2
|
+
import type { CatalogueKind, CatalogueOptions, CartesianOptions, RadarOptions, CircularOptions, ScatterOptions, TreemapOptions, SankeyOptions, FunnelOptions, GeoMapOptions, CalendarHeatmapOptions, ChartPoint, TreeNode } from '../catalogue-types.ts';
|
|
3
|
+
import { cataloguePoints, treeValue } from '../catalogue-model.ts';
|
|
4
|
+
import { resolveChartColor, contrastText, interpolateColor } from '../colors.ts';
|
|
5
|
+
import { formatValue } from '../model.ts';
|
|
6
|
+
import { escapeText } from '../validation.ts';
|
|
7
|
+
import { markColor, markStyle, highlightStyle, sequentialScale, seriesSymbol, seriesDash, seriesDecal, boundedLabels } from '../accessibility.ts';
|
|
8
|
+
import type { themeTokens } from '../options.ts';
|
|
9
|
+
type Tokens = ReturnType<typeof themeTokens>;
|
|
10
|
+
export function buildOption(options: CatalogueOptions, t: Tokens, mapName?: string, reducedMotion = false, width = 640): EChartsCoreOption {
|
|
11
|
+
const kind = 'doughnut' as CatalogueKind;
|
|
12
|
+
const o = options as any, points = cataloguePoints(kind, options);
|
|
13
|
+
const decal = (index: number, fill: string) => o.ShowPatterns ? seriesDecal(index, fill) : undefined;
|
|
14
|
+
const colors = t.palette;
|
|
15
|
+
const color = (c: any, i: number) => markColor(t.forcedColors ? t.text : resolveChartColor(c, colors[i % colors.length], t.dark, t.read), t.surface, kind === 'line' || kind === 'radar' || kind === 'polar' && o.PolarType === 'Line' ? 4.5 : 3.2);
|
|
16
|
+
const style = (key: string, c: any, i: number) => markStyle(color(c, i), t.surface, o.SelectedKey === key);
|
|
17
|
+
const point = (key: string, seriesKey?: string) => points.find(p => p.Key === key && (!seriesKey || p.SeriesKey === seriesKey));
|
|
18
|
+
const namesForLegend: string[] = ['line','radar','bar','polar'].includes(kind) ? o.Series.map((s: any) => s.Label) : kind === 'scatter' ? [...new Set<string>(o.Points.map((p: any) => p.Group || 'Observations'))] : ['pie','doughnut','nightingale','funnel'].includes(kind) ? [...new Set<string>(o.Items.map((i: any) => i.Label))] : [];
|
|
19
|
+
const legend = { data: namesForLegend, show: o.ShowLegend, type: 'scroll', top: 6, left: 10, right: 10, itemWidth: 24, itemHeight: 12, selectedMode: false, pageTextStyle: { color: t.muted }, pageIconColor: t.text, textStyle: { color: t.muted, fontSize: 11 } };
|
|
20
|
+
const label = { show: o.ShowLabels, color: contrastText(t.surface), fontSize: 11, backgroundColor: t.surface, padding: [2, 3], borderRadius: 2 };
|
|
21
|
+
const tooltip = { show: o.ShowTooltip, confine: true, enterable: true, backgroundColor: t.surface, borderColor: t.border, textStyle: { color: t.text, fontSize: 12 }, formatter: (p: any) => {
|
|
22
|
+
const d = p.data?.point as ChartPoint | undefined;
|
|
23
|
+
if (!d)
|
|
24
|
+
return `${escapeText(p.name || '')}${p.value == null || Number.isNaN(p.value) ? ' · No data' : ''}`;
|
|
25
|
+
const extra = d.Date ? `<br/>${escapeText(d.Date)}<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>` : d.X !== undefined ? `<br/>${escapeText(o.XLabel || 'X')}: ${d.X}<br/>${escapeText(o.YLabel || 'Y')}: ${d.Y}${d.Size === undefined ? '' : `<br/>Size: ${d.Size}`}` : `<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>`;
|
|
26
|
+
return `${escapeText(d.SeriesName ? `${d.SeriesName} · ${d.Label}` : d.Label)}${extra}${p.data?.conversion ? `<br/>${escapeText(p.data.conversion)}` : ''}`;
|
|
27
|
+
} };
|
|
28
|
+
const base = { backgroundColor: t.surface, color: colors.map((c, i) => color(c, i)), animation: !!o.EnableAnimation && !reducedMotion, animationDuration: 300, animationDurationUpdate: 150, textStyle: { fontFamily: 'Inter,system-ui,sans-serif', color: t.text }, aria: { enabled: true, description: `${o.Title}. ${o.Description || ''} Exact values and keyboard selection are in the data table.` }, tooltip };
|
|
29
|
+
const axis = { axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: t.muted, fontSize: 10, hideOverlap: true }, splitLine: { lineStyle: { color: t.border, type: 'dashed' } } };
|
|
30
|
+
|
|
31
|
+
const c = options as CircularOptions;
|
|
32
|
+
const rings = c.Nested ? c.Rings! : [{ Key: '', Label: '' }];
|
|
33
|
+
const half = c.Shape === 'Half', nested = !!c.Nested && rings.length > 1;
|
|
34
|
+
const inner = kind === 'pie' ? 0 : c.InnerRadius ?? (kind === 'doughnut' ? 48 : 18);
|
|
35
|
+
const outside = c.LabelPosition !== 'Inside';
|
|
36
|
+
const outer = outside ? 66 : 76;
|
|
37
|
+
const names = [...new Set(c.Items.map(item => item.Label))];
|
|
38
|
+
const series = rings.map((ring, ri) => {
|
|
39
|
+
// Match ECharts' nested-pie composition: a solid core, a visible gap, then the detail ring.
|
|
40
|
+
const radius = nested ? ri === 0 ? [0, 32] : [44 + (outer - 44) * (ri - 1) / (rings.length - 1), 44 + (outer - 44) * ri / (rings.length - 1) - (ri === rings.length - 1 ? 0 : 2)] : [inner, Math.max(outer, inner + 5)];
|
|
41
|
+
const inside = !outside || (nested && ri < rings.length - 1);
|
|
42
|
+
return {
|
|
43
|
+
type: 'pie', id: ring.Key || kind, name: ring.Label, top: o.ShowLegend ? 28 : 0, bottom: 2,
|
|
44
|
+
center: ['50%', half ? '72%' : '55%'], radius: radius.map(r => `${r}%`),
|
|
45
|
+
startAngle: half ? 180 : 90, endAngle: half ? 360 : 'auto',
|
|
46
|
+
roseType: kind === 'nightingale' ? (c.RoseMode === 'Radius' ? 'radius' : 'area') : false,
|
|
47
|
+
avoidLabelOverlap: true, stillShowZeroSum: false,
|
|
48
|
+
label: { ...label, position: inside ? 'inside' : 'outside', backgroundColor: inside ? undefined : t.surface,
|
|
49
|
+
alignTo: 'none', bleedMargin: 6, distanceToLabelLine: 4,
|
|
50
|
+
formatter: '{b}\n{d}%', lineHeight: 15 },
|
|
51
|
+
labelLine: { show: !!o.ShowLabels && !inside, length: 12, length2: 8, smooth: .15 },
|
|
52
|
+
labelLayout: { hideOverlap: true }, emphasis: { scale: true },
|
|
53
|
+
data: c.Items.filter(i => !c.Nested || i.RingKey === ring.Key).map(i => {
|
|
54
|
+
const index = names.indexOf(i.Label), fill = color(i.Color, index);
|
|
55
|
+
return { id: i.Key, name: i.Label, value: i.Value, point: point(i.Key),
|
|
56
|
+
label: { color: contrastText(inside ? fill : t.surface), backgroundColor: inside ? fill : t.surface },
|
|
57
|
+
labelLine: { lineStyle: { color: fill, width: 1.25 } },
|
|
58
|
+
itemStyle: { ...style(i.Key, i.Color, index), decal: decal(index, fill), borderRadius: 3 }, emphasis: { itemStyle: highlightStyle(fill) } };
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const selected = points.find(p => p.Key === o.SelectedKey);
|
|
63
|
+
const total = selected?.Value ?? c.Items.reduce((n, i) => n + (i.Value || 0), 0);
|
|
64
|
+
return { ...base, legend, series, media: outside ? [{ query: { maxWidth: 420 }, option: { series: series.map(s => ({ id: s.id, radius: s.radius.map(r => `${parseFloat(r) * .84}%`) })) } }] : [], graphic: kind === 'doughnut' && !nested && inner >= 35 ? [{ type: 'text', left: 'center', top: half ? '63%' : '48%', silent: true, style: { text: `${formatValue(total, o, true)}\n${selected?.Label || c.CenterLabel || 'Total'}`, textAlign: 'center', fill: t.text, font: '600 16px Inter,system-ui', lineHeight: 24, width: Math.max(24, Math.min(width, o.Height || 320) * inner / 100 - 12), overflow: 'truncate' } }] : [] };
|
|
65
|
+
|
|
66
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { EChartsCoreOption } from 'echarts/core';
|
|
2
|
+
import type { MixedChartOptions, HeatmapOptions, ChartOptions } from '../types.ts';
|
|
3
|
+
import { formatValue, heatmapRange, heatmapPoint } from '../model.ts';
|
|
4
|
+
import { automaticPalette, componentPalette, isComponentColor, resolveChartColor, contrastText, interpolateColor } from '../colors.ts';
|
|
5
|
+
import { markColor, markStyle, highlightStyle, sequentialScale, seriesSymbol, seriesDash, seriesDecal } from '../accessibility.ts';
|
|
6
|
+
import { escapeText } from '../validation.ts';
|
|
7
|
+
|
|
8
|
+
export interface ChartTheme { surface: string; text: string; muted: string; border: string; dark: boolean; forcedColors?: boolean; read: (name: string) => string; palette: string[]; }
|
|
9
|
+
export function themeTokens(host: HTMLElement): ChartTheme {
|
|
10
|
+
const style = getComputedStyle(host);
|
|
11
|
+
const read = (name: string, fallback: string) => style.getPropertyValue(name).trim() || fallback;
|
|
12
|
+
const dark = document.documentElement.dataset.bluuiTheme === 'dark';
|
|
13
|
+
const forcedColors = matchMedia('(forced-colors: active)').matches;
|
|
14
|
+
if (forcedColors) {
|
|
15
|
+
const probe = document.createElement('span');
|
|
16
|
+
probe.style.cssText = 'position:absolute;visibility:hidden;color:Canvas;background-color:CanvasText;forced-color-adjust:none';
|
|
17
|
+
host.append(probe);
|
|
18
|
+
const computed = getComputedStyle(probe), surface = computed.color, text = computed.backgroundColor;
|
|
19
|
+
probe.remove();
|
|
20
|
+
return { surface, text, muted: text, border: text, dark, forcedColors, read: () => '', palette: Array(8).fill(text) };
|
|
21
|
+
}
|
|
22
|
+
return { forcedColors, surface: read('--bluui-surface', '#fff'), text: read('--bluui-text', '#182230'), muted: read('--bluui-text-muted', '#667085'), border: read('--bluui-border', '#e3e5ed'), dark, read: (name: string) => style.getPropertyValue(name),
|
|
23
|
+
palette: automaticPalette(dark) };
|
|
24
|
+
}
|
|
25
|
+
type Tokens = ReturnType<typeof themeTokens>;
|
|
26
|
+
function base(o: ChartOptions, t: Tokens): EChartsCoreOption {
|
|
27
|
+
return {
|
|
28
|
+
backgroundColor: t.surface,
|
|
29
|
+
animation: o.EnableAnimation && !matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
30
|
+
animationDuration: 350, animationDurationUpdate: 180,
|
|
31
|
+
textStyle: { fontFamily: 'Inter, system-ui, sans-serif', color: t.text },
|
|
32
|
+
aria: { enabled: true, description: `${o.Title}. ${o.Description || ''} Exact values and keyboard selections are available in the chart data table.` },
|
|
33
|
+
tooltip: { show: o.ShowTooltip, confine: true, enterable: true, backgroundColor: t.surface, borderColor: t.border, textStyle: { color: t.text, fontSize: 12 }, extraCssText: 'border-radius:8px;box-shadow:0 8px 28px #0002;' },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function axis(t: Tokens) { return { axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: t.muted, fontSize: 11 } }; }
|
|
37
|
+
export function heatmapOption(o: HeatmapOptions, t: Tokens): EChartsCoreOption {
|
|
38
|
+
const { min, max } = heatmapRange(o);
|
|
39
|
+
const palette = componentPalette(isComponentColor(o.ColorScale) ? o.ColorScale : 'Purple', t.dark, t.read);
|
|
40
|
+
const scaleColor = t.forcedColors ? t.text : o.ColorScale === 'Automatic' ? t.palette[0] : o.ColorScale === 'Teal' ? '#126e5b' : palette.solid;
|
|
41
|
+
// Diverging keeps a neutral midpoint; signed values remain available in the table.
|
|
42
|
+
const colors = o.ColorScale === 'Diverging' && !t.forcedColors ? [markColor('#ba5943', t.surface, 9), markColor(t.surface, t.surface), markColor('#6850bf', t.surface, 9)] : sequentialScale(scaleColor, t.surface);
|
|
43
|
+
return {
|
|
44
|
+
...base(o, t),
|
|
45
|
+
grid: { left: 12, right: 12, top: 16, bottom: o.ShowLegend ? 62 : 12, containLabel: true },
|
|
46
|
+
tooltip: { ...(base(o, t).tooltip as object), formatter: (params: unknown) => { const point = heatmapPoint(o, (params as { dataIndex: number }).dataIndex)!; return `${escapeText(point.YLabel)} · ${escapeText(point.XLabel)}<br/><strong>${escapeText(formatValue(point.Value, o))}</strong>`; } },
|
|
47
|
+
xAxis: { ...axis(t), type: 'category', data: o.XCategories.map(c => c.Label), axisLabel: { ...axis(t).axisLabel, hideOverlap: true, width: 120, overflow: 'truncate' } },
|
|
48
|
+
yAxis: { ...axis(t), type: 'category', inverse: true, data: o.YCategories.map(c => c.Label), axisLabel: { ...axis(t).axisLabel, width: 80, overflow: 'truncate' } },
|
|
49
|
+
visualMap: { show: o.ShowLegend, min, max, calculable: false, orient: 'horizontal', left: 'center', bottom: 6, itemWidth: 9, itemHeight: 130, text: ['Higher', 'Lower'], textGap: 9, textStyle: { color: t.muted, fontSize: 10 }, inRange: { color: colors } },
|
|
50
|
+
series: [{ type: 'heatmap',
|
|
51
|
+
data: o.Cells.map(c => ({ name: c.Key, value: [o.XCategories.findIndex(x => x.Key === c.XKey), o.YCategories.findIndex(y => y.Key === c.YKey), c.Value ?? '-'],
|
|
52
|
+
itemStyle: { ...markStyle(interpolateColor(colors, ((c.Value ?? min) - min) / (max - min)), t.surface, o.SelectedKey === c.Key), borderRadius: 4 },
|
|
53
|
+
label: { color: contrastText(interpolateColor(colors, ((c.Value ?? min) - min) / (max - min))) },
|
|
54
|
+
emphasis: { itemStyle: highlightStyle(interpolateColor(colors, ((c.Value ?? min) - min) / (max - min))) },
|
|
55
|
+
})),
|
|
56
|
+
label: { show: o.ShowLabels, fontSize: 10, formatter: (p: { dataIndex: number }) => formatValue(o.Cells[p.dataIndex].Value, o, true) },
|
|
57
|
+
emphasis: { itemStyle: { borderColor: t.text, borderWidth: 2, borderType: 'solid' } },
|
|
58
|
+
}],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { format, type EChartsCoreOption } from 'echarts/core';
|
|
2
|
+
import type { CatalogueKind, CatalogueOptions, CartesianOptions, RadarOptions, CircularOptions, ScatterOptions, TreemapOptions, SankeyOptions, FunnelOptions, GeoMapOptions, CalendarHeatmapOptions, ChartPoint, TreeNode } from '../catalogue-types.ts';
|
|
3
|
+
import { cataloguePoints, treeValue } from '../catalogue-model.ts';
|
|
4
|
+
import { resolveChartColor, contrastText, interpolateColor } from '../colors.ts';
|
|
5
|
+
import { formatValue } from '../model.ts';
|
|
6
|
+
import { escapeText } from '../validation.ts';
|
|
7
|
+
import { markColor, markStyle, highlightStyle, sequentialScale, seriesSymbol, seriesDash, seriesDecal, boundedLabels } from '../accessibility.ts';
|
|
8
|
+
import type { themeTokens } from '../options.ts';
|
|
9
|
+
type Tokens = ReturnType<typeof themeTokens>;
|
|
10
|
+
export function buildOption(options: CatalogueOptions, t: Tokens, mapName?: string, reducedMotion = false, width = 640): EChartsCoreOption {
|
|
11
|
+
const kind = 'line' as CatalogueKind;
|
|
12
|
+
const o = options as any, points = cataloguePoints(kind, options);
|
|
13
|
+
const decal = (index: number, fill: string) => o.ShowPatterns ? seriesDecal(index, fill) : undefined;
|
|
14
|
+
const colors = t.palette;
|
|
15
|
+
const color = (c: any, i: number) => markColor(t.forcedColors ? t.text : resolveChartColor(c, colors[i % colors.length], t.dark, t.read), t.surface, kind === 'line' || kind === 'radar' || kind === 'polar' && o.PolarType === 'Line' ? 4.5 : 3.2);
|
|
16
|
+
const style = (key: string, c: any, i: number) => markStyle(color(c, i), t.surface, o.SelectedKey === key);
|
|
17
|
+
const point = (key: string, seriesKey?: string) => points.find(p => p.Key === key && (!seriesKey || p.SeriesKey === seriesKey));
|
|
18
|
+
const namesForLegend: string[] = ['line','radar','bar','polar'].includes(kind) ? o.Series.map((s: any) => s.Label) : kind === 'scatter' ? [...new Set<string>(o.Points.map((p: any) => p.Group || 'Observations'))] : ['pie','doughnut','nightingale','funnel'].includes(kind) ? [...new Set<string>(o.Items.map((i: any) => i.Label))] : [];
|
|
19
|
+
const legend = { data: namesForLegend, show: o.ShowLegend, type: 'scroll', top: 6, left: 10, right: 10, itemWidth: 24, itemHeight: 12, selectedMode: false, pageTextStyle: { color: t.muted }, pageIconColor: t.text, textStyle: { color: t.muted, fontSize: 11 } };
|
|
20
|
+
const label = { show: o.ShowLabels, color: contrastText(t.surface), fontSize: 11, backgroundColor: t.surface, padding: [2, 3], borderRadius: 2 };
|
|
21
|
+
const tooltip = { show: o.ShowTooltip, confine: true, enterable: true, backgroundColor: t.surface, borderColor: t.border, textStyle: { color: t.text, fontSize: 12 }, formatter: (p: any) => {
|
|
22
|
+
const d = p.data?.point as ChartPoint | undefined;
|
|
23
|
+
if (!d)
|
|
24
|
+
return `${escapeText(p.name || '')}${p.value == null || Number.isNaN(p.value) ? ' · No data' : ''}`;
|
|
25
|
+
const extra = d.Date ? `<br/>${escapeText(d.Date)}<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>` : d.X !== undefined ? `<br/>${escapeText(o.XLabel || 'X')}: ${d.X}<br/>${escapeText(o.YLabel || 'Y')}: ${d.Y}${d.Size === undefined ? '' : `<br/>Size: ${d.Size}`}` : `<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>`;
|
|
26
|
+
return `${escapeText(d.SeriesName ? `${d.SeriesName} · ${d.Label}` : d.Label)}${extra}${p.data?.conversion ? `<br/>${escapeText(p.data.conversion)}` : ''}`;
|
|
27
|
+
} };
|
|
28
|
+
const base = { backgroundColor: t.surface, color: colors.map((c, i) => color(c, i)), animation: !!o.EnableAnimation && !reducedMotion, animationDuration: 300, animationDurationUpdate: 150, textStyle: { fontFamily: 'Inter,system-ui,sans-serif', color: t.text }, aria: { enabled: true, description: `${o.Title}. ${o.Description || ''} Exact values and keyboard selection are in the data table.` }, tooltip };
|
|
29
|
+
const axis = { axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: t.muted, fontSize: 10, hideOverlap: true }, splitLine: { lineStyle: { color: t.border, type: 'dashed' } } };
|
|
30
|
+
|
|
31
|
+
const c = options as CartesianOptions;
|
|
32
|
+
const percent = c.Stacking === 'Percent';
|
|
33
|
+
const cat = { ...axis, type: 'category', data: c.Categories.map(x => x.Label), axisLabel: { ...axis.axisLabel, width: Math.min(kind === 'polar' ? 100 : 140, width * .2), overflow: 'truncate' }, splitLine: { show: false } };
|
|
34
|
+
const numeric = { ...axis, type: 'value', max: percent ? 1 : undefined, axisLabel: { ...axis.axisLabel, formatter: (v: number) => percent ? `${Math.round(v * 100)}%` : formatValue(v, o, true) } };
|
|
35
|
+
const insideBars = (kind === 'bar' || (kind === 'polar' && c.PolarType !== 'Line')) && (kind === 'polar' || (!!c.Stacking && c.Stacking !== 'None'));
|
|
36
|
+
const series = c.Series.map((s, si) => ({ id: s.Key, name: s.Label, type: kind === 'line' || (kind === 'polar' && c.PolarType === 'Line') ? 'line' : 'bar', coordinateSystem: kind === 'polar' ? 'polar' : 'cartesian2d', stack: c.Stacking && c.Stacking !== 'None' ? 'total' : undefined, smooth: c.LineStyle === 'Smooth', step: c.LineStyle === 'Step' ? 'middle' : false, connectNulls: false, areaStyle: c.ShowArea ? { opacity: .10 } : undefined, barMaxWidth: 38, symbol: seriesSymbol(si), symbolSize: 8, itemStyle: { ...markStyle(color(s.Color, si), t.surface), decal: decal(si, color(s.Color, si)) }, lineStyle: { width: 2.5, color: color(s.Color, si), type: seriesDash(si) }, label: { ...label, color: insideBars ? contrastText(color(s.Color, si)) : contrastText(t.surface), backgroundColor: insideBars ? color(s.Color, si) : t.surface, position: insideBars ? 'inside' : kind === 'bar' && c.Orientation === 'Horizontal' ? 'right' : 'top', formatter: (p: any) => percent ? `${Math.round(Number(p.value) * 100)}%` : formatValue(p.data.point.Value, o, true) }, labelLayout: { hideOverlap: true }, emphasis: { focus: 'none', scale: false }, blur: { itemStyle: { opacity: 1 }, lineStyle: { opacity: 1 }, label: { opacity: 1 } }, data: s.Values.map((v, i) => ({ value: v === null ? null : percent ? v / (c.Series.reduce((sum, s) => sum + (s.Values[i] || 0), 0) || 1) : v, point: point(c.Categories[i].Key, s.Key), itemStyle: { ...style(c.Categories[i].Key, s.Color, si), decal: decal(si, color(s.Color, si)) }, emphasis: { itemStyle: highlightStyle(color(s.Color, si)) } })) }));
|
|
37
|
+
return kind === 'polar' ? { ...base, legend, polar: { radius: ['12%', '72%'], center: ['50%', '55%'] }, angleAxis: { ...cat, startAngle: 90 }, radiusAxis: numeric, series } : { ...base, legend, grid: { left: 14, right: 22, top: o.ShowLegend ? 48 : 20, bottom: 16, containLabel: true }, xAxis: c.Orientation === 'Horizontal' ? numeric : cat, yAxis: c.Orientation === 'Horizontal' ? { ...cat, inverse: true } : numeric, series };
|
|
38
|
+
|
|
39
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { format, type EChartsCoreOption } from 'echarts/core';
|
|
2
|
+
import type { CatalogueKind, CatalogueOptions, CartesianOptions, RadarOptions, CircularOptions, ScatterOptions, TreemapOptions, SankeyOptions, FunnelOptions, GeoMapOptions, CalendarHeatmapOptions, ChartPoint, TreeNode } from '../catalogue-types.ts';
|
|
3
|
+
import { cataloguePoints, treeValue } from '../catalogue-model.ts';
|
|
4
|
+
import { resolveChartColor, contrastText, interpolateColor } from '../colors.ts';
|
|
5
|
+
import { formatValue } from '../model.ts';
|
|
6
|
+
import { escapeText } from '../validation.ts';
|
|
7
|
+
import { markColor, markStyle, highlightStyle, sequentialScale, seriesSymbol, seriesDash, seriesDecal, boundedLabels } from '../accessibility.ts';
|
|
8
|
+
import type { themeTokens } from '../options.ts';
|
|
9
|
+
type Tokens = ReturnType<typeof themeTokens>;
|
|
10
|
+
export function buildOption(options: CatalogueOptions, t: Tokens, mapName?: string, reducedMotion = false, width = 640): EChartsCoreOption {
|
|
11
|
+
const kind = 'pie' as CatalogueKind;
|
|
12
|
+
const o = options as any, points = cataloguePoints(kind, options);
|
|
13
|
+
const decal = (index: number, fill: string) => o.ShowPatterns ? seriesDecal(index, fill) : undefined;
|
|
14
|
+
const colors = t.palette;
|
|
15
|
+
const color = (c: any, i: number) => markColor(t.forcedColors ? t.text : resolveChartColor(c, colors[i % colors.length], t.dark, t.read), t.surface, kind === 'line' || kind === 'radar' || kind === 'polar' && o.PolarType === 'Line' ? 4.5 : 3.2);
|
|
16
|
+
const style = (key: string, c: any, i: number) => markStyle(color(c, i), t.surface, o.SelectedKey === key);
|
|
17
|
+
const point = (key: string, seriesKey?: string) => points.find(p => p.Key === key && (!seriesKey || p.SeriesKey === seriesKey));
|
|
18
|
+
const namesForLegend: string[] = ['line','radar','bar','polar'].includes(kind) ? o.Series.map((s: any) => s.Label) : kind === 'scatter' ? [...new Set<string>(o.Points.map((p: any) => p.Group || 'Observations'))] : ['pie','doughnut','nightingale','funnel'].includes(kind) ? [...new Set<string>(o.Items.map((i: any) => i.Label))] : [];
|
|
19
|
+
const legend = { data: namesForLegend, show: o.ShowLegend, type: 'scroll', top: 6, left: 10, right: 10, itemWidth: 24, itemHeight: 12, selectedMode: false, pageTextStyle: { color: t.muted }, pageIconColor: t.text, textStyle: { color: t.muted, fontSize: 11 } };
|
|
20
|
+
const label = { show: o.ShowLabels, color: contrastText(t.surface), fontSize: 11, backgroundColor: t.surface, padding: [2, 3], borderRadius: 2 };
|
|
21
|
+
const tooltip = { show: o.ShowTooltip, confine: true, enterable: true, backgroundColor: t.surface, borderColor: t.border, textStyle: { color: t.text, fontSize: 12 }, formatter: (p: any) => {
|
|
22
|
+
const d = p.data?.point as ChartPoint | undefined;
|
|
23
|
+
if (!d)
|
|
24
|
+
return `${escapeText(p.name || '')}${p.value == null || Number.isNaN(p.value) ? ' · No data' : ''}`;
|
|
25
|
+
const extra = d.Date ? `<br/>${escapeText(d.Date)}<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>` : d.X !== undefined ? `<br/>${escapeText(o.XLabel || 'X')}: ${d.X}<br/>${escapeText(o.YLabel || 'Y')}: ${d.Y}${d.Size === undefined ? '' : `<br/>Size: ${d.Size}`}` : `<br/><strong>${escapeText(formatValue(d.Value, o))}</strong>`;
|
|
26
|
+
return `${escapeText(d.SeriesName ? `${d.SeriesName} · ${d.Label}` : d.Label)}${extra}${p.data?.conversion ? `<br/>${escapeText(p.data.conversion)}` : ''}`;
|
|
27
|
+
} };
|
|
28
|
+
const base = { backgroundColor: t.surface, color: colors.map((c, i) => color(c, i)), animation: !!o.EnableAnimation && !reducedMotion, animationDuration: 300, animationDurationUpdate: 150, textStyle: { fontFamily: 'Inter,system-ui,sans-serif', color: t.text }, aria: { enabled: true, description: `${o.Title}. ${o.Description || ''} Exact values and keyboard selection are in the data table.` }, tooltip };
|
|
29
|
+
const axis = { axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: t.muted, fontSize: 10, hideOverlap: true }, splitLine: { lineStyle: { color: t.border, type: 'dashed' } } };
|
|
30
|
+
|
|
31
|
+
const c = options as CircularOptions;
|
|
32
|
+
const rings = c.Nested ? c.Rings! : [{ Key: '', Label: '' }];
|
|
33
|
+
const half = c.Shape === 'Half', nested = !!c.Nested && rings.length > 1;
|
|
34
|
+
const inner = kind === 'pie' ? 0 : c.InnerRadius ?? (kind === 'doughnut' ? 48 : 18);
|
|
35
|
+
const outside = c.LabelPosition !== 'Inside';
|
|
36
|
+
const outer = outside ? 66 : 76;
|
|
37
|
+
const names = [...new Set(c.Items.map(item => item.Label))];
|
|
38
|
+
const series = rings.map((ring, ri) => {
|
|
39
|
+
// Match ECharts' nested-pie composition: a solid core, a visible gap, then the detail ring.
|
|
40
|
+
const radius = nested ? ri === 0 ? [0, 32] : [44 + (outer - 44) * (ri - 1) / (rings.length - 1), 44 + (outer - 44) * ri / (rings.length - 1) - (ri === rings.length - 1 ? 0 : 2)] : [inner, Math.max(outer, inner + 5)];
|
|
41
|
+
const inside = !outside || (nested && ri < rings.length - 1);
|
|
42
|
+
return {
|
|
43
|
+
type: 'pie', id: ring.Key || kind, name: ring.Label, top: o.ShowLegend ? 28 : 0, bottom: 2,
|
|
44
|
+
center: ['50%', half ? '72%' : '55%'], radius: radius.map(r => `${r}%`),
|
|
45
|
+
startAngle: half ? 180 : 90, endAngle: half ? 360 : 'auto',
|
|
46
|
+
roseType: kind === 'nightingale' ? (c.RoseMode === 'Radius' ? 'radius' : 'area') : false,
|
|
47
|
+
avoidLabelOverlap: true, stillShowZeroSum: false,
|
|
48
|
+
label: { ...label, position: inside ? 'inside' : 'outside', backgroundColor: inside ? undefined : t.surface,
|
|
49
|
+
alignTo: 'none', bleedMargin: 6, distanceToLabelLine: 4,
|
|
50
|
+
formatter: '{b}\n{d}%', lineHeight: 15 },
|
|
51
|
+
labelLine: { show: !!o.ShowLabels && !inside, length: 12, length2: 8, smooth: .15 },
|
|
52
|
+
labelLayout: { hideOverlap: true }, emphasis: { scale: true },
|
|
53
|
+
data: c.Items.filter(i => !c.Nested || i.RingKey === ring.Key).map(i => {
|
|
54
|
+
const index = names.indexOf(i.Label), fill = color(i.Color, index);
|
|
55
|
+
return { id: i.Key, name: i.Label, value: i.Value, point: point(i.Key),
|
|
56
|
+
label: { color: contrastText(inside ? fill : t.surface), backgroundColor: inside ? fill : t.surface },
|
|
57
|
+
labelLine: { lineStyle: { color: fill, width: 1.25 } },
|
|
58
|
+
itemStyle: { ...style(i.Key, i.Color, index), decal: decal(index, fill), borderRadius: 3 }, emphasis: { itemStyle: highlightStyle(fill) } };
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const selected = points.find(p => p.Key === o.SelectedKey);
|
|
63
|
+
const total = selected?.Value ?? c.Items.reduce((n, i) => n + (i.Value || 0), 0);
|
|
64
|
+
return { ...base, legend, series, media: outside ? [{ query: { maxWidth: 420 }, option: { series: series.map(s => ({ id: s.id, radius: s.radius.map(r => `${parseFloat(r) * .84}%`) })) } }] : [], graphic: kind === 'doughnut' && !nested && inner >= 35 ? [{ type: 'text', left: 'center', top: half ? '63%' : '48%', silent: true, style: { text: `${formatValue(total, o, true)}\n${selected?.Label || c.CenterLabel || 'Total'}`, textAlign: 'center', fill: t.text, font: '600 16px Inter,system-ui', lineHeight: 24, width: Math.max(24, Math.min(width, o.Height || 320) * inner / 100 - 12), overflow: 'truncate' } }] : [] };
|
|
65
|
+
|
|
66
|
+
}
|