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
@@ -1,15 +1,15 @@
1
- /*
2
- * @Author : Lihao leolihao@arizona.edu
3
- * @Date : 2023-11-29 15:35:28
4
- * @FilePath : /visualifyjs/src/_test/App.test.js
5
- * @Description :
6
- * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
- */
8
- import { render, screen } from '@testing-library/react';
9
- import App from '../core/App';
10
-
11
- test('renders learn react link', () => {
12
- render(<App />);
13
- const linkElement = screen.getByText(/learn react/i);
14
- expect(linkElement).toBeInTheDocument();
15
- });
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-11-29 15:35:28
4
+ * @FilePath : /visualifyjs/src/_test/App.test.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import { render, screen } from '@testing-library/react';
9
+ import App from '../core/App';
10
+
11
+ test('renders learn react link', () => {
12
+ render(<App />);
13
+ const linkElement = screen.getByText(/learn react/i);
14
+ expect(linkElement).toBeInTheDocument();
15
+ });
@@ -1,13 +1,13 @@
1
- const reportWebVitals = onPerfEntry => {
2
- if (onPerfEntry && onPerfEntry instanceof Function) {
3
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
- getCLS(onPerfEntry);
5
- getFID(onPerfEntry);
6
- getFCP(onPerfEntry);
7
- getLCP(onPerfEntry);
8
- getTTFB(onPerfEntry);
9
- });
10
- }
11
- };
12
-
13
- export default reportWebVitals;
1
+ const reportWebVitals = onPerfEntry => {
2
+ if (onPerfEntry && onPerfEntry instanceof Function) {
3
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
+ getCLS(onPerfEntry);
5
+ getFID(onPerfEntry);
6
+ getFCP(onPerfEntry);
7
+ getLCP(onPerfEntry);
8
+ getTTFB(onPerfEntry);
9
+ });
10
+ }
11
+ };
12
+
13
+ export default reportWebVitals;
@@ -0,0 +1,177 @@
1
+ # Accessibility (a11y) Module for Visualify.js
2
+
3
+ This module provides comprehensive accessibility support for Visualify.js charts, ensuring compliance with WCAG 2.1 AA standards and compatibility with screen readers and keyboard navigation.
4
+
5
+ ## Features
6
+
7
+ - **ARIA Labels**: Automatic generation of descriptive labels for charts
8
+ - **Keyboard Navigation**: Full keyboard support for navigating and interacting with charts
9
+ - **Screen Reader Support**: Data tables and live regions for screen reader announcements
10
+ - **Color Contrast**: WCAG AA compliance checking with automatic color suggestions
11
+ - **Focus Management**: Visible focus indicators and logical tab order
12
+ - **Motion Preferences**: Respects `prefers-reduced-motion` settings
13
+
14
+ ## Quick Start
15
+
16
+ ```javascript
17
+ import { applyA11yStyles } from 'visualify/a11y';
18
+
19
+ // Apply accessibility styles once in your app
20
+ applyA11yStyles();
21
+ ```
22
+
23
+ ## Components with A11y Support
24
+
25
+ The following components have built-in accessibility support:
26
+
27
+ - `Scatter` - Scatter plots with keyboard navigation
28
+ - `Scatter3D` - 3D scatter plots with accessibility features
29
+ - `Bar` - Bar charts with ARIA labels
30
+ - `Bar3D` - 3D bar charts with keyboard navigation
31
+ - `EChartSwitcher` - Dynamic chart switcher with screen reader support
32
+ - `Vcontroller` - Dashboard controller with skip links
33
+
34
+ ## Configuration
35
+
36
+ ### Chart Configuration
37
+
38
+ ```javascript
39
+ const config = {
40
+ title: 'Sales by Month',
41
+ data: [...],
42
+
43
+ // Accessibility options
44
+ a11y: {
45
+ enabled: true, // Enable accessibility features
46
+ announceLoad: true, // Announce when chart loads
47
+ autoFix: true, // Auto-fix color contrast issues
48
+ enableDownload: true, // Enable CSV download for screen readers
49
+ description: 'Detailed description of the chart',
50
+ }
51
+ };
52
+ ```
53
+
54
+ ### Color Contrast
55
+
56
+ The module automatically checks and fixes color contrast issues:
57
+
58
+ ```javascript
59
+ import { validateChartColors, applyAccessibleColors } from 'visualify/a11y';
60
+
61
+ // Validate colors
62
+ const validation = validateChartColors(config);
63
+ if (!validation.valid) {
64
+ console.log('Issues:', validation.issues);
65
+ console.log('Suggestions:', validation.suggestions);
66
+ }
67
+
68
+ // Auto-fix colors
69
+ const accessibleConfig = applyAccessibleColors(config);
70
+ ```
71
+
72
+ ### Keyboard Navigation
73
+
74
+ Charts support the following keyboard shortcuts:
75
+
76
+ | Key | Action |
77
+ |-----|--------|
78
+ | Tab | Focus the chart |
79
+ | Arrow Right/Down | Navigate to next data point |
80
+ | Arrow Left/Up | Navigate to previous data point |
81
+ | Enter/Space | Activate/select data point |
82
+ | Escape | Clear selection |
83
+ | Home | Jump to first data point |
84
+ | End | Jump to last data point |
85
+ | Page Up | Jump back 10 data points |
86
+ | Page Down | Jump forward 10 data points |
87
+
88
+ ## API Reference
89
+
90
+ ### ARIA Labels (`aria-labels.js`)
91
+
92
+ #### `generateChartLabel(config, chartType)`
93
+ Generates a descriptive ARIA label for a chart.
94
+
95
+ #### `formatDataForScreenReader(data, config, chartType)`
96
+ Formats chart data for screen reader output.
97
+
98
+ #### `generateDataTable(data, config, chartType)`
99
+ Creates a data table structure for screen readers.
100
+
101
+ #### `announceToScreenReader(message, priority)`
102
+ Announces a message via ARIA live region.
103
+
104
+ ### Keyboard Navigation (`keyboard-nav.js`)
105
+
106
+ #### `useChartKeyboardNav(options)`
107
+ React hook for chart keyboard navigation.
108
+
109
+ ```javascript
110
+ const {
111
+ containerRef,
112
+ handleKeyDown,
113
+ handleFocus,
114
+ focusedIndex,
115
+ containerProps
116
+ } = useChartKeyboardNav({
117
+ data: chartData,
118
+ config: chartConfig,
119
+ onFocusChange: (index, dataPoint) => {},
120
+ onActivate: (index, dataPoint) => {},
121
+ });
122
+ ```
123
+
124
+ ### Color Contrast (`color-contrast.js`)
125
+
126
+ #### `checkWCAGCompliance(foreground, background, isLargeText)`
127
+ Checks if color combination meets WCAG AA standards.
128
+
129
+ #### `getContrastRatio(color1, color2)`
130
+ Calculates contrast ratio between two colors.
131
+
132
+ #### `suggestAccessibleColor(background, preferredColor, options)`
133
+ Suggests an accessible color for given background.
134
+
135
+ ## CSS Classes
136
+
137
+ ### `.sr-only`
138
+ Hides content visually but keeps it accessible to screen readers.
139
+
140
+ ### `.visualify-chart`
141
+ Applied to all chart containers. Includes focus styles.
142
+
143
+ ### `.visualify-controller`
144
+ Applied to the dashboard controller. Includes skip link styles.
145
+
146
+ ## Testing Checklist
147
+
148
+ - [ ] Can navigate to charts using Tab key
149
+ - [ ] Focus indicator is clearly visible
150
+ - [ ] Arrow keys navigate between data points
151
+ - [ ] Enter/Space activates data points
152
+ - [ ] Screen reader announces chart title and type
153
+ - [ ] Screen reader announces data values
154
+ - [ ] Data table is accessible to screen readers
155
+ - [ ] Color contrast passes WCAG AA (4.5:1 for normal text)
156
+ - [ ] Works with 200% zoom
157
+ - [ ] Respects reduced motion preferences
158
+
159
+ ## Browser Support
160
+
161
+ - Chrome/Edge 80+
162
+ - Firefox 75+
163
+ - Safari 13+
164
+ - IE 11 (with polyfills)
165
+
166
+ ## Screen Reader Support
167
+
168
+ - NVDA (Windows)
169
+ - JAWS (Windows)
170
+ - VoiceOver (macOS/iOS)
171
+ - TalkBack (Android)
172
+
173
+ ## Resources
174
+
175
+ - [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
176
+ - [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
177
+ - [Inclusive Design Principles](https://inclusivedesignprinciples.org/)
@@ -0,0 +1,339 @@
1
+ /**
2
+ * ARIA Label Generators for Visualify.js
3
+ * Provides accessible labels and descriptions for charts
4
+ * @module a11y/aria-labels
5
+ */
6
+
7
+ /**
8
+ * Generates a descriptive ARIA label for a chart
9
+ * @param {Object} config - Chart configuration
10
+ * @param {string} chartType - Type of chart (scatter, bar, line, etc.)
11
+ * @returns {string} Descriptive label for screen readers
12
+ */
13
+ export const generateChartLabel = (config, chartType = 'chart') => {
14
+ const title = typeof config.title === 'string'
15
+ ? config.title
16
+ : config.title?.text || 'Untitled chart';
17
+
18
+ const dataPoints = config.data?.length || config.series?.[0]?.data?.length || 0;
19
+ const xAxisLabel = config.labels?.x || config.xAxis?.name || 'X axis';
20
+ const yAxisLabel = config.labels?.y || config.yAxis?.name || 'Y axis';
21
+ const zAxisLabel = config.zAxis3D?.name || config.zAxis?.name || 'Z axis';
22
+
23
+ let description = `${title}. ${chartType} chart`;
24
+
25
+ if (dataPoints > 0) {
26
+ description += ` with ${dataPoints} data points`;
27
+ }
28
+
29
+ if (config.is3D || chartType.includes('3D')) {
30
+ description += `. Three-dimensional view showing ${xAxisLabel}, ${yAxisLabel}, and ${zAxisLabel}`;
31
+ } else {
32
+ description += `. X axis shows ${xAxisLabel}, Y axis shows ${yAxisLabel}`;
33
+ }
34
+
35
+ if (config.description) {
36
+ description += `. ${config.description}`;
37
+ }
38
+
39
+ return description;
40
+ };
41
+
42
+ /**
43
+ * Formats data into a readable string for screen readers
44
+ * @param {Array} data - Chart data array
45
+ * @param {Object} config - Chart configuration
46
+ * @param {string} chartType - Type of chart
47
+ * @returns {string} Formatted data description
48
+ */
49
+ export const formatDataForScreenReader = (data, config = {}, chartType = 'chart') => {
50
+ if (!data || !Array.isArray(data) || data.length === 0) {
51
+ return 'No data available';
52
+ }
53
+
54
+ const maxItems = 10; // Limit to avoid overwhelming screen readers
55
+ const dataLength = data.length;
56
+
57
+ let description = `Data summary: ${dataLength} total items. `;
58
+
59
+ // Format based on data structure
60
+ const sample = data[0];
61
+
62
+ if (Array.isArray(sample)) {
63
+ // Array format: [[x, y], [x, y, z], etc.]
64
+ const dimensions = sample.length;
65
+ description += `Each data point has ${dimensions} dimensions. `;
66
+
67
+ const items = data.slice(0, maxItems).map((item, index) => {
68
+ if (dimensions === 2) {
69
+ return `Point ${index + 1}: ${formatValue(item[0])}, ${formatValue(item[1])}`;
70
+ } else if (dimensions >= 3) {
71
+ return `Point ${index + 1}: ${formatValue(item[0])}, ${formatValue(item[1])}, ${formatValue(item[2])}`;
72
+ }
73
+ return `Point ${index + 1}: ${formatValue(item[0])}`;
74
+ }).join('; ');
75
+
76
+ description += items;
77
+ } else if (typeof sample === 'object') {
78
+ // Object format: {x, y, z, name, value, etc.}
79
+ const items = data.slice(0, maxItems).map((item, index) => {
80
+ const name = item.name || item.label || `Item ${index + 1}`;
81
+ const value = item.value !== undefined ? item.value :
82
+ (item.z !== undefined ? `(${item.x}, ${item.y}, ${item.z})` :
83
+ `(${item.x}, ${item.y})`);
84
+ return `${name}: ${formatValue(value)}`;
85
+ }).join('; ');
86
+
87
+ description += items;
88
+ } else {
89
+ // Simple value array
90
+ const items = data.slice(0, maxItems).map((item, index) =>
91
+ `Item ${index + 1}: ${formatValue(item)}`
92
+ ).join('; ');
93
+
94
+ description += items;
95
+ }
96
+
97
+ if (dataLength > maxItems) {
98
+ description += `. And ${dataLength - maxItems} more items`;
99
+ }
100
+
101
+ return description;
102
+ };
103
+
104
+ /**
105
+ * Generates an accessible name for a data point
106
+ * @param {Object|Array} dataPoint - Single data point
107
+ * @param {number} index - Index of the data point
108
+ * @param {Object} config - Chart configuration
109
+ * @returns {string} Accessible name for the data point
110
+ */
111
+ export const generateDataPointLabel = (dataPoint, index, config = {}) => {
112
+ if (Array.isArray(dataPoint)) {
113
+ const xLabel = config.labels?.x || 'X';
114
+ const yLabel = config.labels?.y || 'Y';
115
+ const zLabel = config.zAxis3D?.name || config.zAxis?.name || 'Z';
116
+
117
+ if (dataPoint.length >= 3) {
118
+ return `Data point ${index + 1}: ${xLabel} ${formatValue(dataPoint[0])}, ${yLabel} ${formatValue(dataPoint[1])}, ${zLabel} ${formatValue(dataPoint[2])}`;
119
+ }
120
+ return `Data point ${index + 1}: ${xLabel} ${formatValue(dataPoint[0])}, ${yLabel} ${formatValue(dataPoint[1])}`;
121
+ }
122
+
123
+ if (typeof dataPoint === 'object') {
124
+ const name = dataPoint.name || dataPoint.label || `Data point ${index + 1}`;
125
+ const value = dataPoint.value !== undefined ? formatValue(dataPoint.value) : '';
126
+ const x = dataPoint.x !== undefined ? `${config.labels?.x || 'X'}: ${formatValue(dataPoint.x)}` : '';
127
+ const y = dataPoint.y !== undefined ? `${config.labels?.y || 'Y'}: ${formatValue(dataPoint.y)}` : '';
128
+ const z = dataPoint.z !== undefined ? `${config.zAxis3D?.name || 'Z'}: ${formatValue(dataPoint.z)}` : '';
129
+
130
+ const coords = [x, y, z].filter(Boolean).join(', ');
131
+
132
+ if (value) {
133
+ return `${name}: ${value}${coords ? ` at ${coords}` : ''}`;
134
+ }
135
+ return `${name}${coords ? `: ${coords}` : ''}`;
136
+ }
137
+
138
+ return `Data point ${index + 1}: ${formatValue(dataPoint)}`;
139
+ };
140
+
141
+ /**
142
+ * Formats a value for screen reader output
143
+ * @param {*} value - Value to format
144
+ * @returns {string} Formatted value
145
+ */
146
+ const formatValue = (value) => {
147
+ if (value === null || value === undefined) {
148
+ return 'unknown';
149
+ }
150
+
151
+ if (typeof value === 'number') {
152
+ // Format large numbers with commas
153
+ if (Math.abs(value) >= 1000) {
154
+ return value.toLocaleString();
155
+ }
156
+ // Round decimals to 2 places
157
+ if (value % 1 !== 0) {
158
+ return value.toFixed(2);
159
+ }
160
+ }
161
+
162
+ if (value instanceof Date) {
163
+ return value.toLocaleDateString();
164
+ }
165
+
166
+ return String(value);
167
+ };
168
+
169
+ /**
170
+ * Generates ARIA attributes for a chart container
171
+ * @param {Object} config - Chart configuration
172
+ * @param {string} chartType - Type of chart
173
+ * @param {string} uniqueId - Unique identifier for the chart
174
+ * @returns {Object} ARIA attributes object
175
+ */
176
+ export const generateChartAriaAttributes = (config, chartType = 'chart', uniqueId = '') => {
177
+ const label = generateChartLabel(config, chartType);
178
+ const dataDescription = formatDataForScreenReader(
179
+ config.data || config.series?.[0]?.data,
180
+ config,
181
+ chartType
182
+ );
183
+
184
+ const descriptionId = uniqueId ? `${uniqueId}-description` : 'chart-description';
185
+
186
+ return {
187
+ role: 'img',
188
+ 'aria-label': label,
189
+ 'aria-describedby': descriptionId,
190
+ tabIndex: 0,
191
+ 'data-description-id': descriptionId,
192
+ 'data-chart-type': chartType,
193
+ };
194
+ };
195
+
196
+ /**
197
+ * Generates a data table for screen readers from chart data
198
+ * @param {Array} data - Chart data
199
+ * @param {Object} config - Chart configuration
200
+ * @param {string} chartType - Type of chart
201
+ * @returns {Object} Table data structure with headers and rows
202
+ */
203
+ export const generateDataTable = (data, config = {}, chartType = 'chart') => {
204
+ if (!data || !Array.isArray(data) || data.length === 0) {
205
+ return {
206
+ caption: config.title || 'Chart Data',
207
+ headers: [],
208
+ rows: [],
209
+ };
210
+ }
211
+
212
+ const sample = data[0];
213
+ let headers = [];
214
+ let rows = [];
215
+
216
+ if (Array.isArray(sample)) {
217
+ // Array format
218
+ const dimensions = sample.length;
219
+ headers = [
220
+ config.labels?.x || 'X',
221
+ config.labels?.y || 'Y',
222
+ ...(dimensions > 2 ? [config.zAxis3D?.name || config.zAxis?.name || 'Z'] : []),
223
+ ].slice(0, dimensions);
224
+
225
+ rows = data.map((item, index) => ({
226
+ id: `row-${index}`,
227
+ cells: item.map(val => formatValue(val)),
228
+ }));
229
+ } else if (typeof sample === 'object') {
230
+ // Object format
231
+ const keys = Object.keys(sample);
232
+ headers = keys.map(key => {
233
+ switch(key) {
234
+ case 'x': return config.labels?.x || 'X';
235
+ case 'y': return config.labels?.y || 'Y';
236
+ case 'z': return config.zAxis3D?.name || config.zAxis?.name || 'Z';
237
+ case 'name': return 'Name';
238
+ case 'value': return 'Value';
239
+ default: return key.charAt(0).toUpperCase() + key.slice(1);
240
+ }
241
+ });
242
+
243
+ rows = data.map((item, index) => ({
244
+ id: `row-${index}`,
245
+ cells: keys.map(key => formatValue(item[key])),
246
+ }));
247
+ } else {
248
+ // Simple value array
249
+ headers = ['Index', 'Value'];
250
+ rows = data.map((item, index) => ({
251
+ id: `row-${index}`,
252
+ cells: [String(index + 1), formatValue(item)],
253
+ }));
254
+ }
255
+
256
+ return {
257
+ caption: typeof config.title === 'string' ? config.title : config.title?.text || 'Chart Data',
258
+ headers,
259
+ rows,
260
+ };
261
+ };
262
+
263
+ /**
264
+ * Announces a message to screen readers via a live region
265
+ * @param {string} message - Message to announce
266
+ * @param {string} priority - Priority level ('polite' or 'assertive')
267
+ */
268
+ export const announceToScreenReader = (message, priority = 'polite') => {
269
+ // Look for existing live region or create one
270
+ let liveRegion = document.getElementById('visualify-a11y-announcer');
271
+
272
+ if (!liveRegion) {
273
+ liveRegion = document.createElement('div');
274
+ liveRegion.id = 'visualify-a11y-announcer';
275
+ liveRegion.setAttribute('aria-live', priority);
276
+ liveRegion.setAttribute('aria-atomic', 'true');
277
+ liveRegion.className = 'sr-only';
278
+ document.body.appendChild(liveRegion);
279
+ }
280
+
281
+ // Update live region to trigger announcement
282
+ liveRegion.setAttribute('aria-live', priority);
283
+ liveRegion.textContent = message;
284
+
285
+ // Clear after announcement
286
+ setTimeout(() => {
287
+ liveRegion.textContent = '';
288
+ }, 1000);
289
+ };
290
+
291
+ /**
292
+ * Generates a CSV string from chart data for export
293
+ * @param {Array} data - Chart data
294
+ * @param {Object} config - Chart configuration
295
+ * @returns {string} CSV formatted string
296
+ */
297
+ export const generateCSV = (data, config = {}) => {
298
+ if (!data || !Array.isArray(data) || data.length === 0) {
299
+ return '';
300
+ }
301
+
302
+ const table = generateDataTable(data, config);
303
+ const headers = table.headers.join(',');
304
+ const rows = table.rows.map(row => row.cells.join(',')).join('\n');
305
+
306
+ return `${headers}\n${rows}`;
307
+ };
308
+
309
+ /**
310
+ * Creates a download link for CSV data
311
+ * @param {string} csv - CSV content
312
+ * @param {string} filename - Download filename
313
+ */
314
+ export const downloadCSV = (csv, filename = 'chart-data.csv') => {
315
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
316
+ const link = document.createElement('a');
317
+ const url = URL.createObjectURL(blob);
318
+
319
+ link.setAttribute('href', url);
320
+ link.setAttribute('download', filename);
321
+ link.setAttribute('aria-label', `Download data as ${filename}`);
322
+ link.className = 'sr-only';
323
+
324
+ document.body.appendChild(link);
325
+ link.click();
326
+ document.body.removeChild(link);
327
+ URL.revokeObjectURL(url);
328
+ };
329
+
330
+ export default {
331
+ generateChartLabel,
332
+ formatDataForScreenReader,
333
+ generateDataPointLabel,
334
+ generateChartAriaAttributes,
335
+ generateDataTable,
336
+ announceToScreenReader,
337
+ generateCSV,
338
+ downloadCSV,
339
+ };