visualifyjs 2.5.3-2.dev → 3.0.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.
Files changed (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -241
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -298
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -44
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -121
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -9
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -124
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -587
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -147
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
@@ -0,0 +1,455 @@
1
+ /**
2
+ * Scatter3D Component
3
+ * 3D scatter plot visualization using ECharts GL
4
+ * @module components/Scatter3D
5
+ */
6
+
7
+ import React, { useState, useEffect, forwardRef, useCallback, useRef } from 'react';
8
+ import ReCharts from '../modules/echarts';
9
+ import {
10
+ loadEChartsGL,
11
+ isWebGLSupported,
12
+ display3DError,
13
+ cleanupWebGL,
14
+ } from '../modules/echarts/gl';
15
+ import Loading from '../pages/loading';
16
+ import {
17
+ generateChartAriaAttributes,
18
+ formatDataForScreenReader,
19
+ generateDataTable,
20
+ announceToScreenReader,
21
+ generateCSV,
22
+ downloadCSV,
23
+ } from '../../a11y/aria-labels';
24
+ import { useChartKeyboardNav } from '../../a11y/keyboard-nav';
25
+ import { validateChartColors, applyAccessibleColors } from '../../a11y/color-contrast';
26
+
27
+ /**
28
+ * Default configuration for 3D scatter plots
29
+ */
30
+ const DEFAULT_CONFIG = {
31
+ symbolSize: 10,
32
+ opacity: 0.8,
33
+ color: '#5470c6',
34
+ };
35
+
36
+ /**
37
+ * Generates ECharts option for 3D scatter plot
38
+ * @param {Object} config - Component configuration
39
+ * @returns {Object} ECharts option object
40
+ */
41
+ const getOptions = (config) => {
42
+ const {
43
+ title,
44
+ data,
45
+ xAxis3D,
46
+ yAxis3D,
47
+ zAxis3D,
48
+ grid3D,
49
+ visualMap,
50
+ series,
51
+ backgroundColor,
52
+ color,
53
+ tooltip,
54
+ legend,
55
+ toolbox,
56
+ } = config;
57
+
58
+ // Validate required data
59
+ if (!data) {
60
+ throw new Error('Scatter3D requires data');
61
+ }
62
+
63
+ // Process data - support both {x, y, z} object format and array format
64
+ let processedData = data;
65
+
66
+ // Handle object format: { x: [], y: [], z: [] }
67
+ if (!Array.isArray(data) && data.x && data.y && data.z) {
68
+ const len = Math.min(data.x.length, data.y.length, data.z.length);
69
+ processedData = [];
70
+ for (let i = 0; i < len; i++) {
71
+ processedData.push([data.x[i], data.y[i], data.z[i]]);
72
+ }
73
+ }
74
+ // Handle array of objects format: [{x, y, z}, ...]
75
+ else if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object' && !Array.isArray(data[0])) {
76
+ processedData = data.map((item) => [item.x, item.y, item.z]);
77
+ }
78
+
79
+ const options = {
80
+ title: typeof title === 'string' ? { text: title } : { left: 'center', top: 0, ...title },
81
+ tooltip: {
82
+ trigger: 'item',
83
+ formatter: tooltip?.formatter || ((params) => {
84
+ const [x, y, z] = params.value;
85
+ return `X: ${x}<br/>Y: ${y}<br/>Z: ${z}`;
86
+ }),
87
+ ...tooltip,
88
+ },
89
+ legend: legend ?? undefined,
90
+ visualMap: visualMap ?? undefined,
91
+ xAxis3D: {
92
+ name: xAxis3D?.name || 'X',
93
+ type: xAxis3D?.type || 'value',
94
+ nameGap: 25,
95
+ ...xAxis3D,
96
+ },
97
+ yAxis3D: {
98
+ name: yAxis3D?.name || 'Y',
99
+ type: yAxis3D?.type || 'value',
100
+ nameGap: 25,
101
+ ...yAxis3D,
102
+ },
103
+ zAxis3D: {
104
+ name: zAxis3D?.name || 'Z',
105
+ type: zAxis3D?.type || 'value',
106
+ nameGap: 25,
107
+ ...zAxis3D,
108
+ },
109
+ grid3D: {
110
+ boxWidth: 100,
111
+ boxDepth: 80,
112
+ boxHeight: 60,
113
+ viewControl: {
114
+ autoRotate: false,
115
+ projection: 'perspective',
116
+ ...grid3D?.viewControl,
117
+ },
118
+ light: {
119
+ main: {
120
+ intensity: 1.2,
121
+ shadow: true,
122
+ },
123
+ ambient: {
124
+ intensity: 0.3,
125
+ },
126
+ },
127
+ ...grid3D,
128
+ },
129
+ series: [
130
+ {
131
+ type: 'scatter3D',
132
+ symbolSize: series?.symbolSize ?? DEFAULT_CONFIG.symbolSize,
133
+ data: processedData,
134
+ itemStyle: {
135
+ opacity: series?.opacity ?? DEFAULT_CONFIG.opacity,
136
+ color: series?.color ?? DEFAULT_CONFIG.color,
137
+ ...series?.itemStyle,
138
+ },
139
+ emphasis: {
140
+ itemStyle: {
141
+ borderColor: '#fff',
142
+ borderWidth: 1,
143
+ },
144
+ },
145
+ ...series,
146
+ },
147
+ ],
148
+ };
149
+
150
+ if (backgroundColor) {
151
+ options.backgroundColor = backgroundColor;
152
+ }
153
+
154
+ if (color) {
155
+ options.color = color;
156
+ }
157
+
158
+ if (toolbox) {
159
+ options.toolbox = {
160
+ feature: {
161
+ ...toolbox,
162
+ saveAsImage: { show: toolbox.saveAsImage?.show ?? false },
163
+ },
164
+ };
165
+ }
166
+
167
+ return options;
168
+ };
169
+
170
+ /**
171
+ * Scatter3D Component - 3D scatter plot visualization
172
+ * @param {Object} props - Component props
173
+ * @param {Object} props.props - Configuration object
174
+ * @param {Object} props.style - CSS styles
175
+ * @param {React.Ref} ref - Forwarded ref
176
+ */
177
+ const Scatter3D = forwardRef(({ props, style }, ref) => {
178
+ const { config = {} } = props;
179
+ const chartId = props.id || `scatter3d-chart-${Math.random().toString(36).substr(2, 9)}`;
180
+ const descriptionId = `${chartId}-description`;
181
+ const tableId = `${chartId}-data-table`;
182
+
183
+ // Accessibility: Validate and fix colors
184
+ const accessibleConfig = React.useMemo(() => {
185
+ const validation = validateChartColors(config);
186
+ if (!validation.valid && config.a11y?.autoFix !== false) {
187
+ return applyAccessibleColors(config);
188
+ }
189
+ return config;
190
+ }, [config]);
191
+
192
+ const [options, setOptions] = useState(null);
193
+ const [loading, setLoading] = useState({ active: true, message: 'Loading 3D library...' });
194
+ const [error, setError] = useState(null);
195
+ const [focusedDataPoint, setFocusedDataPoint] = useState(null);
196
+ const chartRef = useRef(null);
197
+
198
+ // Get chart data for accessibility
199
+ const chartData = React.useMemo(() => {
200
+ return accessibleConfig.data || [];
201
+ }, [accessibleConfig]);
202
+
203
+ // Keyboard navigation
204
+ const {
205
+ containerRef,
206
+ handleKeyDown,
207
+ handleFocus,
208
+ focusedIndex,
209
+ } = useChartKeyboardNav({
210
+ data: chartData,
211
+ config: accessibleConfig,
212
+ onFocusChange: (index, dataPoint) => {
213
+ setFocusedDataPoint(dataPoint);
214
+ if (chartRef.current) {
215
+ const chart = chartRef.current.getEchartsInstance?.();
216
+ if (chart) {
217
+ chart.dispatchAction({
218
+ type: 'highlight',
219
+ seriesIndex: 0,
220
+ dataIndex: index,
221
+ });
222
+ }
223
+ }
224
+ },
225
+ onActivate: (index, dataPoint) => {
226
+ if (chartRef.current) {
227
+ const chart = chartRef.current.getEchartsInstance?.();
228
+ if (chart) {
229
+ chart.dispatchAction({
230
+ type: 'showTip',
231
+ seriesIndex: 0,
232
+ dataIndex: index,
233
+ });
234
+ }
235
+ }
236
+ },
237
+ });
238
+
239
+ // Generate ARIA attributes
240
+ const ariaAttributes = React.useMemo(() =>
241
+ generateChartAriaAttributes(accessibleConfig, 'scatter3D', chartId),
242
+ [accessibleConfig, chartId]
243
+ );
244
+
245
+ // Generate data description
246
+ const dataDescription = React.useMemo(() =>
247
+ formatDataForScreenReader(chartData, accessibleConfig, 'scatter3D'),
248
+ [chartData, accessibleConfig]
249
+ );
250
+
251
+ // Generate data table for screen readers
252
+ const dataTable = React.useMemo(() =>
253
+ generateDataTable(chartData, accessibleConfig, 'scatter3D'),
254
+ [chartData, accessibleConfig]
255
+ );
256
+
257
+ // Handle CSV download
258
+ const handleDownloadData = useCallback(() => {
259
+ const csv = generateCSV(chartData, accessibleConfig);
260
+ if (csv) {
261
+ downloadCSV(csv, `${accessibleConfig.title || 'scatter3d-chart'}-data.csv`);
262
+ announceToScreenReader('Data downloaded as CSV file', 'polite');
263
+ }
264
+ }, [chartData, accessibleConfig]);
265
+
266
+ /**
267
+ * Initialize ECharts GL and set up chart options
268
+ */
269
+ const initChart = useCallback(async () => {
270
+ try {
271
+ setLoading({ active: true, message: 'Loading 3D library...' });
272
+ setError(null);
273
+
274
+ // Check WebGL support
275
+ if (!isWebGLSupported()) {
276
+ throw new Error('WebGL not supported');
277
+ }
278
+
279
+ // Lazy load ECharts GL
280
+ await loadEChartsGL();
281
+
282
+ // Generate chart options
283
+ const chartOptions = getOptions(accessibleConfig);
284
+ setOptions(chartOptions);
285
+ setLoading({ active: false, message: null });
286
+
287
+ // Announce chart load
288
+ if (accessibleConfig.a11y?.announceLoad !== false) {
289
+ announceToScreenReader(
290
+ `${ariaAttributes['aria-label']}. 3D scatter plot. Use arrow keys to navigate data points. Press Enter to select.`,
291
+ 'polite'
292
+ );
293
+ }
294
+ } catch (err) {
295
+ const errorMessage = display3DError(err, { useToast: true });
296
+ setError(errorMessage);
297
+ setLoading({ active: true, message: errorMessage });
298
+
299
+ // Announce error to screen readers
300
+ announceToScreenReader(`Error loading 3D chart: ${errorMessage}`, 'assertive');
301
+ }
302
+ }, [accessibleConfig, ariaAttributes]);
303
+
304
+ // Initialize chart on mount and config change
305
+ useEffect(() => {
306
+ initChart();
307
+ }, [initChart]);
308
+
309
+ // Cleanup on unmount
310
+ useEffect(() => {
311
+ return () => {
312
+ if (ref?.current) {
313
+ cleanupWebGL(ref.current.getEchartsInstance?.());
314
+ }
315
+ };
316
+ }, [ref]);
317
+
318
+ // Handle resize
319
+ useEffect(() => {
320
+ const handleResize = () => {
321
+ if (ref?.current) {
322
+ const chart = ref.current.getEchartsInstance?.();
323
+ if (chart && !chart.isDisposed()) {
324
+ chart.resize();
325
+ }
326
+ }
327
+ };
328
+
329
+ window.addEventListener('resize', handleResize);
330
+ return () => window.removeEventListener('resize', handleResize);
331
+ }, [ref]);
332
+
333
+ // Render error state
334
+ if (error) {
335
+ return (
336
+ <div
337
+ id={chartId}
338
+ ref={containerRef}
339
+ role="alert"
340
+ aria-live="assertive"
341
+ style={{
342
+ ...style,
343
+ display: 'flex',
344
+ alignItems: 'center',
345
+ justifyContent: 'center',
346
+ backgroundColor: '#f5f5f5',
347
+ border: '1px solid #ddd',
348
+ borderRadius: '4px',
349
+ padding: '20px',
350
+ textAlign: 'center',
351
+ }}
352
+ tabIndex={0}
353
+ >
354
+ <div>
355
+ <h4>3D Chart Unavailable</h4>
356
+ <p style={{ color: '#666', marginTop: '10px' }}>{error}</p>
357
+ </div>
358
+ </div>
359
+ );
360
+ }
361
+
362
+ return (
363
+ <div
364
+ id={chartId}
365
+ ref={containerRef}
366
+ style={{ ...style, position: 'relative' }}
367
+ className="visualify-chart visualify-scatter3d-chart"
368
+ {...ariaAttributes}
369
+ onKeyDown={handleKeyDown}
370
+ onFocus={handleFocus}
371
+ data-testid="scatter3d-chart"
372
+ >
373
+ {/* Screen reader description */}
374
+ <div id={descriptionId} className="sr-only">
375
+ {dataDescription}
376
+ {accessibleConfig.description && (
377
+ <span>. {accessibleConfig.description}</span>
378
+ )}
379
+ <p>3D visualization. Use Tab to enter chart, then arrow keys to navigate data points.</p>
380
+ </div>
381
+
382
+ {/* Screen reader data table */}
383
+ {dataTable.rows.length > 0 && (
384
+ <table id={tableId} className="sr-only">
385
+ <caption>{dataTable.caption} - Data Table</caption>
386
+ <thead>
387
+ <tr>
388
+ {dataTable.headers.map((header, i) => (
389
+ <th key={i} scope="col">{header}</th>
390
+ ))}
391
+ </tr>
392
+ </thead>
393
+ <tbody>
394
+ {dataTable.rows.map((row) => (
395
+ <tr key={row.id}>
396
+ {row.cells.map((cell, i) => (
397
+ <td key={i}>{cell}</td>
398
+ ))}
399
+ </tr>
400
+ ))}
401
+ </tbody>
402
+ </table>
403
+ )}
404
+
405
+ {/* CSV download link for screen readers */}
406
+ {accessibleConfig.a11y?.enableDownload !== false && chartData.length > 0 && (
407
+ <button
408
+ className="sr-only"
409
+ onClick={handleDownloadData}
410
+ aria-label="Download chart data as CSV"
411
+ tabIndex={-1}
412
+ >
413
+ Download Data
414
+ </button>
415
+ )}
416
+
417
+ {/* Live region for updates */}
418
+ <div
419
+ aria-live="polite"
420
+ aria-atomic="true"
421
+ className="sr-only"
422
+ id={`${chartId}-live-region`}
423
+ >
424
+ {focusedDataPoint && `Focused: X ${focusedDataPoint[0] || focusedDataPoint.x}, Y ${focusedDataPoint[1] || focusedDataPoint.y}, Z ${focusedDataPoint[2] || focusedDataPoint.z}`}
425
+ </div>
426
+
427
+ {loading.active && (
428
+ <Loading message={loading.message} style={{ marginTop: '10px' }} />
429
+ )}
430
+ {options && (
431
+ <ReCharts
432
+ ref={(el) => {
433
+ chartRef.current = el;
434
+ if (typeof ref === 'function') {
435
+ ref(el);
436
+ } else if (ref) {
437
+ ref.current = el;
438
+ }
439
+ }}
440
+ options={options}
441
+ style={{
442
+ width: accessibleConfig.chartWidth || '100%',
443
+ height: accessibleConfig.chartHeight || '400px',
444
+ opacity: loading.active ? 0 : 1,
445
+ }}
446
+ aria-hidden="true"
447
+ />
448
+ )}
449
+ </div>
450
+ );
451
+ });
452
+
453
+ Scatter3D.displayName = 'Scatter3D';
454
+
455
+ export default Scatter3D;