visualifyjs 2.5.3 → 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 -193
  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 -0
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -43
  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 -123
  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 -1
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -119
  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 -579
  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 -143
  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,473 @@
1
+ /**
2
+ * Bar3D Component
3
+ * 3D bar chart visualization using ECharts GL
4
+ * @module components/Bar3D
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 bar charts
29
+ */
30
+ const DEFAULT_CONFIG = {
31
+ shading: 'lambert',
32
+ opacity: 1,
33
+ };
34
+
35
+ /**
36
+ * Generates ECharts option for 3D bar chart
37
+ * @param {Object} config - Component configuration
38
+ * @returns {Object} ECharts option object
39
+ */
40
+ const getOptions = (config) => {
41
+ const {
42
+ title,
43
+ data,
44
+ xAxis3D,
45
+ yAxis3D,
46
+ zAxis3D,
47
+ grid3D,
48
+ visualMap,
49
+ series,
50
+ backgroundColor,
51
+ color,
52
+ tooltip,
53
+ legend,
54
+ toolbox,
55
+ } = config;
56
+
57
+ // Validate required data
58
+ if (!data) {
59
+ throw new Error('Bar3D requires data');
60
+ }
61
+
62
+ // Process data - support multiple formats
63
+ // Format 1: [[x, y, z], ...] - 3D coordinates
64
+ // Format 2: [{x, y, z}, ...] - Object format
65
+ // Format 3: 2D grid with height values
66
+ // Format 4: {x: [], y: [], z: []} - Object with coordinate arrays
67
+ let processedData = data;
68
+
69
+ // Handle object format: { x: [], y: [], z: [] }
70
+ if (!Array.isArray(data) && data.x && data.y && data.z) {
71
+ const len = Math.min(data.x.length, data.y.length, data.z.length);
72
+ processedData = [];
73
+ for (let i = 0; i < len; i++) {
74
+ processedData.push([data.x[i], data.y[i], data.z[i]]);
75
+ }
76
+ }
77
+ // Handle array of objects format: [{x, y, z}, ...]
78
+ else if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object' && !Array.isArray(data[0])) {
79
+ processedData = data.map((item) => [item.x, item.y, item.z]);
80
+ }
81
+
82
+ const options = {
83
+ title:
84
+ typeof title === 'string'
85
+ ? { text: title }
86
+ : { left: 'center', top: 0, ...title },
87
+ tooltip: {
88
+ trigger: 'item',
89
+ formatter:
90
+ tooltip?.formatter ||
91
+ ((params) => {
92
+ const [x, y, z] = params.value;
93
+ return `X: ${x}<br/>Y: ${y}<br/>Height: ${z}`;
94
+ }),
95
+ ...tooltip,
96
+ },
97
+ legend: legend ?? undefined,
98
+ visualMap: visualMap ?? undefined,
99
+ xAxis3D: {
100
+ name: xAxis3D?.name || 'X',
101
+ type: xAxis3D?.type || 'category',
102
+ nameGap: 25,
103
+ ...xAxis3D,
104
+ },
105
+ yAxis3D: {
106
+ name: yAxis3D?.name || 'Y',
107
+ type: yAxis3D?.type || 'category',
108
+ nameGap: 25,
109
+ ...yAxis3D,
110
+ },
111
+ zAxis3D: {
112
+ name: zAxis3D?.name || 'Z',
113
+ type: zAxis3D?.type || 'value',
114
+ nameGap: 25,
115
+ ...zAxis3D,
116
+ },
117
+ grid3D: {
118
+ boxWidth: 100,
119
+ boxDepth: 80,
120
+ boxHeight: 60,
121
+ viewControl: {
122
+ autoRotate: false,
123
+ projection: 'perspective',
124
+ ...grid3D?.viewControl,
125
+ },
126
+ light: {
127
+ main: {
128
+ intensity: 1.2,
129
+ shadow: true,
130
+ },
131
+ ambient: {
132
+ intensity: 0.3,
133
+ },
134
+ },
135
+ ...grid3D,
136
+ },
137
+ series: [
138
+ {
139
+ type: 'bar3D',
140
+ data: processedData,
141
+ shading: series?.shading ?? DEFAULT_CONFIG.shading,
142
+ itemStyle: {
143
+ opacity: series?.opacity ?? DEFAULT_CONFIG.opacity,
144
+ ...series?.itemStyle,
145
+ },
146
+ label: {
147
+ show: series?.label?.show ?? false,
148
+ ...series?.label,
149
+ },
150
+ emphasis: {
151
+ label: {
152
+ show: true,
153
+ fontSize: 16,
154
+ color: '#fff',
155
+ },
156
+ itemStyle: {
157
+ color: '#ff7f50',
158
+ },
159
+ },
160
+ ...series,
161
+ },
162
+ ],
163
+ };
164
+
165
+ if (backgroundColor) {
166
+ options.backgroundColor = backgroundColor;
167
+ }
168
+
169
+ if (color) {
170
+ options.color = color;
171
+ }
172
+
173
+ if (toolbox) {
174
+ options.toolbox = {
175
+ feature: {
176
+ ...toolbox,
177
+ saveAsImage: { show: toolbox.saveAsImage?.show ?? false },
178
+ },
179
+ };
180
+ }
181
+
182
+ return options;
183
+ };
184
+
185
+ /**
186
+ * Bar3D Component - 3D bar chart visualization
187
+ * @param {Object} props - Component props
188
+ * @param {Object} props.props - Configuration object
189
+ * @param {Object} props.style - CSS styles
190
+ * @param {React.Ref} ref - Forwarded ref
191
+ */
192
+ const Bar3D = forwardRef(({ props, style }, ref) => {
193
+ const { config = {} } = props;
194
+ const chartId = props.id || `bar3d-chart-${Math.random().toString(36).substr(2, 9)}`;
195
+ const descriptionId = `${chartId}-description`;
196
+ const tableId = `${chartId}-data-table`;
197
+
198
+ // Accessibility: Validate and fix colors
199
+ const accessibleConfig = React.useMemo(() => {
200
+ const validation = validateChartColors(config);
201
+ if (!validation.valid && config.a11y?.autoFix !== false) {
202
+ return applyAccessibleColors(config);
203
+ }
204
+ return config;
205
+ }, [config]);
206
+
207
+ const [options, setOptions] = useState(null);
208
+ const [loading, setLoading] = useState({
209
+ active: true,
210
+ message: 'Loading 3D library...',
211
+ });
212
+ const [error, setError] = useState(null);
213
+ const [focusedDataPoint, setFocusedDataPoint] = useState(null);
214
+ const chartRef = useRef(null);
215
+
216
+ // Get chart data for accessibility
217
+ const chartData = React.useMemo(() => {
218
+ return accessibleConfig.data || [];
219
+ }, [accessibleConfig]);
220
+
221
+ // Keyboard navigation
222
+ const {
223
+ containerRef,
224
+ handleKeyDown,
225
+ handleFocus,
226
+ focusedIndex,
227
+ } = useChartKeyboardNav({
228
+ data: chartData,
229
+ config: accessibleConfig,
230
+ onFocusChange: (index, dataPoint) => {
231
+ setFocusedDataPoint(dataPoint);
232
+ if (chartRef.current) {
233
+ const chart = chartRef.current.getEchartsInstance?.();
234
+ if (chart) {
235
+ chart.dispatchAction({
236
+ type: 'highlight',
237
+ seriesIndex: 0,
238
+ dataIndex: index,
239
+ });
240
+ }
241
+ }
242
+ },
243
+ onActivate: (index, dataPoint) => {
244
+ if (chartRef.current) {
245
+ const chart = chartRef.current.getEchartsInstance?.();
246
+ if (chart) {
247
+ chart.dispatchAction({
248
+ type: 'showTip',
249
+ seriesIndex: 0,
250
+ dataIndex: index,
251
+ });
252
+ }
253
+ }
254
+ },
255
+ });
256
+
257
+ // Generate ARIA attributes
258
+ const ariaAttributes = React.useMemo(() =>
259
+ generateChartAriaAttributes(accessibleConfig, 'bar3D', chartId),
260
+ [accessibleConfig, chartId]
261
+ );
262
+
263
+ // Generate data description
264
+ const dataDescription = React.useMemo(() =>
265
+ formatDataForScreenReader(chartData, accessibleConfig, 'bar3D'),
266
+ [chartData, accessibleConfig]
267
+ );
268
+
269
+ // Generate data table for screen readers
270
+ const dataTable = React.useMemo(() =>
271
+ generateDataTable(chartData, accessibleConfig, 'bar3D'),
272
+ [chartData, accessibleConfig]
273
+ );
274
+
275
+ // Handle CSV download
276
+ const handleDownloadData = useCallback(() => {
277
+ const csv = generateCSV(chartData, accessibleConfig);
278
+ if (csv) {
279
+ downloadCSV(csv, `${accessibleConfig.title || 'bar3d-chart'}-data.csv`);
280
+ announceToScreenReader('Data downloaded as CSV file', 'polite');
281
+ }
282
+ }, [chartData, accessibleConfig]);
283
+
284
+ /**
285
+ * Initialize ECharts GL and set up chart options
286
+ */
287
+ const initChart = useCallback(async () => {
288
+ try {
289
+ setLoading({ active: true, message: 'Loading 3D library...' });
290
+ setError(null);
291
+
292
+ // Check WebGL support
293
+ if (!isWebGLSupported()) {
294
+ throw new Error('WebGL not supported');
295
+ }
296
+
297
+ // Lazy load ECharts GL
298
+ await loadEChartsGL();
299
+
300
+ // Generate chart options
301
+ const chartOptions = getOptions(accessibleConfig);
302
+ setOptions(chartOptions);
303
+ setLoading({ active: false, message: null });
304
+
305
+ // Announce chart load
306
+ if (accessibleConfig.a11y?.announceLoad !== false) {
307
+ announceToScreenReader(
308
+ `${ariaAttributes['aria-label']}. 3D bar chart. Use arrow keys to navigate data points. Press Enter to select.`,
309
+ 'polite'
310
+ );
311
+ }
312
+ } catch (err) {
313
+ const errorMessage = display3DError(err, { useToast: true });
314
+ setError(errorMessage);
315
+ setLoading({ active: true, message: errorMessage });
316
+
317
+ // Announce error to screen readers
318
+ announceToScreenReader(`Error loading 3D chart: ${errorMessage}`, 'assertive');
319
+ }
320
+ }, [accessibleConfig, ariaAttributes]);
321
+
322
+ // Initialize chart on mount and config change
323
+ useEffect(() => {
324
+ initChart();
325
+ }, [initChart]);
326
+
327
+ // Cleanup on unmount
328
+ useEffect(() => {
329
+ return () => {
330
+ if (ref?.current) {
331
+ cleanupWebGL(ref.current.getEchartsInstance?.());
332
+ }
333
+ };
334
+ }, [ref]);
335
+
336
+ // Handle resize
337
+ useEffect(() => {
338
+ const handleResize = () => {
339
+ if (ref?.current) {
340
+ const chart = ref.current.getEchartsInstance?.();
341
+ if (chart && !chart.isDisposed()) {
342
+ chart.resize();
343
+ }
344
+ }
345
+ };
346
+
347
+ window.addEventListener('resize', handleResize);
348
+ return () => window.removeEventListener('resize', handleResize);
349
+ }, [ref]);
350
+
351
+ // Render error state
352
+ if (error) {
353
+ return (
354
+ <div
355
+ id={chartId}
356
+ ref={containerRef}
357
+ role="alert"
358
+ aria-live="assertive"
359
+ style={{
360
+ ...style,
361
+ display: 'flex',
362
+ alignItems: 'center',
363
+ justifyContent: 'center',
364
+ backgroundColor: '#f5f5f5',
365
+ border: '1px solid #ddd',
366
+ borderRadius: '4px',
367
+ padding: '20px',
368
+ textAlign: 'center',
369
+ }}
370
+ tabIndex={0}
371
+ >
372
+ <div>
373
+ <h4>3D Chart Unavailable</h4>
374
+ <p style={{ color: '#666', marginTop: '10px' }}>{error}</p>
375
+ </div>
376
+ </div>
377
+ );
378
+ }
379
+
380
+ return (
381
+ <div
382
+ id={chartId}
383
+ ref={containerRef}
384
+ style={{ ...style, position: 'relative' }}
385
+ className="visualify-chart visualify-bar3d-chart"
386
+ {...ariaAttributes}
387
+ onKeyDown={handleKeyDown}
388
+ onFocus={handleFocus}
389
+ data-testid="bar3d-chart"
390
+ >
391
+ {/* Screen reader description */}
392
+ <div id={descriptionId} className="sr-only">
393
+ {dataDescription}
394
+ {accessibleConfig.description && (
395
+ <span>. {accessibleConfig.description}</span>
396
+ )}
397
+ <p>3D bar chart visualization. Use Tab to enter chart, then arrow keys to navigate data points.</p>
398
+ </div>
399
+
400
+ {/* Screen reader data table */}
401
+ {dataTable.rows.length > 0 && (
402
+ <table id={tableId} className="sr-only">
403
+ <caption>{dataTable.caption} - Data Table</caption>
404
+ <thead>
405
+ <tr>
406
+ {dataTable.headers.map((header, i) => (
407
+ <th key={i} scope="col">{header}</th>
408
+ ))}
409
+ </tr>
410
+ </thead>
411
+ <tbody>
412
+ {dataTable.rows.map((row) => (
413
+ <tr key={row.id}>
414
+ {row.cells.map((cell, i) => (
415
+ <td key={i}>{cell}</td>
416
+ ))}
417
+ </tr>
418
+ ))}
419
+ </tbody>
420
+ </table>
421
+ )}
422
+
423
+ {/* CSV download link for screen readers */}
424
+ {accessibleConfig.a11y?.enableDownload !== false && chartData.length > 0 && (
425
+ <button
426
+ className="sr-only"
427
+ onClick={handleDownloadData}
428
+ aria-label="Download chart data as CSV"
429
+ tabIndex={-1}
430
+ >
431
+ Download Data
432
+ </button>
433
+ )}
434
+
435
+ {/* Live region for updates */}
436
+ <div
437
+ aria-live="polite"
438
+ aria-atomic="true"
439
+ className="sr-only"
440
+ id={`${chartId}-live-region`}
441
+ >
442
+ {focusedDataPoint && `Focused: X ${focusedDataPoint[0] || focusedDataPoint.x}, Y ${focusedDataPoint[1] || focusedDataPoint.y}, Height ${focusedDataPoint[2] || focusedDataPoint.z}`}
443
+ </div>
444
+
445
+ {loading.active && (
446
+ <Loading message={loading.message} style={{ marginTop: '10px' }} />
447
+ )}
448
+ {options && (
449
+ <ReCharts
450
+ ref={(el) => {
451
+ chartRef.current = el;
452
+ if (typeof ref === 'function') {
453
+ ref(el);
454
+ } else if (ref) {
455
+ ref.current = el;
456
+ }
457
+ }}
458
+ options={options}
459
+ style={{
460
+ width: accessibleConfig.chartWidth || '100%',
461
+ height: accessibleConfig.chartHeight || '400px',
462
+ opacity: loading.active ? 0 : 1,
463
+ }}
464
+ aria-hidden="true"
465
+ />
466
+ )}
467
+ </div>
468
+ );
469
+ });
470
+
471
+ Bar3D.displayName = 'Bar3D';
472
+
473
+ export default Bar3D;