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,535 @@
1
+ /**
2
+ * Color Contrast Checker for Visualify.js
3
+ * WCAG AA compliance checking and color suggestions
4
+ * @module a11y/color-contrast
5
+ */
6
+
7
+ /**
8
+ * WCAG contrast ratio thresholds
9
+ */
10
+ export const WCAG_THRESHOLDS = {
11
+ // Normal text
12
+ AA_NORMAL: 4.5,
13
+ AAA_NORMAL: 7,
14
+ // Large text (18pt+ or 14pt+ bold)
15
+ AA_LARGE: 3,
16
+ AAA_LARGE: 4.5,
17
+ // UI components and graphical objects
18
+ AA_UI: 3,
19
+ };
20
+
21
+ /**
22
+ * Converts hex color to RGB
23
+ * @param {string} hex - Hex color string (#RGB or #RRGGBB)
24
+ * @returns {Object|null} RGB values {r, g, b} or null if invalid
25
+ */
26
+ export const hexToRgb = (hex) => {
27
+ if (!hex || typeof hex !== 'string') return null;
28
+
29
+ // Remove # if present
30
+ hex = hex.replace(/^#/, '');
31
+
32
+ // Handle shorthand (#RGB)
33
+ if (hex.length === 3) {
34
+ hex = hex.split('').map(char => char + char).join('');
35
+ }
36
+
37
+ // Handle 6-digit hex
38
+ if (hex.length === 6) {
39
+ const r = parseInt(hex.substring(0, 2), 16);
40
+ const g = parseInt(hex.substring(2, 4), 16);
41
+ const b = parseInt(hex.substring(4, 6), 16);
42
+
43
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
44
+
45
+ return { r, g, b };
46
+ }
47
+
48
+ return null;
49
+ };
50
+
51
+ /**
52
+ * Converts RGB to hex color
53
+ * @param {number} r - Red (0-255)
54
+ * @param {number} g - Green (0-255)
55
+ * @param {number} b - Blue (0-255)
56
+ * @returns {string} Hex color string
57
+ */
58
+ export const rgbToHex = (r, g, b) => {
59
+ const toHex = (n) => {
60
+ const hex = Math.max(0, Math.min(255, Math.round(n))).toString(16);
61
+ return hex.length === 1 ? '0' + hex : hex;
62
+ };
63
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
64
+ };
65
+
66
+ /**
67
+ * Parses color string to RGB (supports hex, rgb, rgba, named colors)
68
+ * @param {string} color - Color string
69
+ * @returns {Object|null} RGB values {r, g, b} or null if invalid
70
+ */
71
+ export const parseColor = (color) => {
72
+ if (!color || typeof color !== 'string') return null;
73
+
74
+ color = color.trim().toLowerCase();
75
+
76
+ // Hex color
77
+ if (color.startsWith('#')) {
78
+ return hexToRgb(color);
79
+ }
80
+
81
+ // RGB/RGBA color
82
+ const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
83
+ if (rgbMatch) {
84
+ return {
85
+ r: parseInt(rgbMatch[1], 10),
86
+ g: parseInt(rgbMatch[2], 10),
87
+ b: parseInt(rgbMatch[3], 10),
88
+ };
89
+ }
90
+
91
+ // Named colors
92
+ const namedColors = {
93
+ black: { r: 0, g: 0, b: 0 },
94
+ white: { r: 255, g: 255, b: 255 },
95
+ red: { r: 255, g: 0, b: 0 },
96
+ green: { r: 0, g: 128, b: 0 },
97
+ blue: { r: 0, g: 0, b: 255 },
98
+ yellow: { r: 255, g: 255, b: 0 },
99
+ cyan: { r: 0, g: 255, b: 255 },
100
+ magenta: { r: 255, g: 0, b: 255 },
101
+ silver: { r: 192, g: 192, b: 192 },
102
+ gray: { r: 128, g: 128, b: 128 },
103
+ grey: { r: 128, g: 128, b: 128 },
104
+ maroon: { r: 128, g: 0, b: 0 },
105
+ olive: { r: 128, g: 128, b: 0 },
106
+ lime: { r: 0, g: 255, b: 0 },
107
+ aqua: { r: 0, g: 255, b: 255 },
108
+ teal: { r: 0, g: 128, b: 128 },
109
+ navy: { r: 0, g: 0, b: 128 },
110
+ fuchsia: { r: 255, g: 0, b: 255 },
111
+ purple: { r: 128, g: 0, b: 128 },
112
+ orange: { r: 255, g: 165, b: 0 },
113
+ };
114
+
115
+ if (namedColors[color]) {
116
+ return namedColors[color];
117
+ }
118
+
119
+ return null;
120
+ };
121
+
122
+ /**
123
+ * Calculates relative luminance of a color
124
+ * @param {Object} rgb - RGB values {r, g, b}
125
+ * @returns {number} Relative luminance (0-1)
126
+ */
127
+ export const getLuminance = (rgb) => {
128
+ if (!rgb) return 0;
129
+
130
+ const { r, g, b } = rgb;
131
+
132
+ // Convert to sRGB
133
+ const rsRGB = r / 255;
134
+ const gsRGB = g / 255;
135
+ const bsRGB = b / 255;
136
+
137
+ // Apply gamma correction
138
+ const rLinear = rsRGB <= 0.03928 ? rsRGB / 12.92 : Math.pow((rsRGB + 0.055) / 1.055, 2.4);
139
+ const gLinear = gsRGB <= 0.03928 ? gsRGB / 12.92 : Math.pow((gsRGB + 0.055) / 1.055, 2.4);
140
+ const bLinear = bsRGB <= 0.03928 ? bsRGB / 12.92 : Math.pow((bsRGB + 0.055) / 1.055, 2.4);
141
+
142
+ return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
143
+ };
144
+
145
+ /**
146
+ * Calculates contrast ratio between two colors
147
+ * @param {string|Object} color1 - First color
148
+ * @param {string|Object} color2 - Second color
149
+ * @returns {number} Contrast ratio (1-21)
150
+ */
151
+ export const getContrastRatio = (color1, color2) => {
152
+ const rgb1 = typeof color1 === 'string' ? parseColor(color1) : color1;
153
+ const rgb2 = typeof color2 === 'string' ? parseColor(color2) : color2;
154
+
155
+ if (!rgb1 || !rgb2) return 1;
156
+
157
+ const lum1 = getLuminance(rgb1);
158
+ const lum2 = getLuminance(rgb2);
159
+
160
+ const lighter = Math.max(lum1, lum2);
161
+ const darker = Math.min(lum1, lum2);
162
+
163
+ return (lighter + 0.05) / (darker + 0.05);
164
+ };
165
+
166
+ /**
167
+ * Checks if color combination meets WCAG AA standards
168
+ * @param {string} foreground - Foreground color
169
+ * @param {string} background - Background color
170
+ * @param {boolean} isLargeText - Whether text is large (18pt+ or 14pt+ bold)
171
+ * @returns {Object} Compliance result
172
+ */
173
+ export const checkWCAGCompliance = (foreground, background, isLargeText = false) => {
174
+ const ratio = getContrastRatio(foreground, background);
175
+ const threshold = isLargeText ? WCAG_THRESHOLDS.AA_LARGE : WCAG_THRESHOLDS.AA_NORMAL;
176
+
177
+ return {
178
+ ratio: Math.round(ratio * 100) / 100,
179
+ passesAA: ratio >= threshold,
180
+ passesAAA: ratio >= (isLargeText ? WCAG_THRESHOLDS.AAA_LARGE : WCAG_THRESHOLDS.AAA_NORMAL),
181
+ threshold,
182
+ isLargeText,
183
+ };
184
+ };
185
+
186
+ /**
187
+ * Suggests an accessible color for given background
188
+ * @param {string} background - Background color
189
+ * @param {string} preferredForeground - Preferred foreground color
190
+ * @param {Object} options - Options
191
+ * @returns {string} Suggested accessible color
192
+ */
193
+ export const suggestAccessibleColor = (background, preferredForeground = null, options = {}) => {
194
+ const { minContrast = WCAG_THRESHOLDS.AA_NORMAL, darkenAmount = 20 } = options;
195
+
196
+ const bgRgb = parseColor(background);
197
+ if (!bgRgb) return '#000000';
198
+
199
+ // If preferred color is provided, try to adjust it
200
+ if (preferredForeground) {
201
+ const fgRgb = parseColor(preferredForeground);
202
+ if (fgRgb) {
203
+ const currentRatio = getContrastRatio(bgRgb, fgRgb);
204
+ if (currentRatio >= minContrast) {
205
+ return preferredForeground;
206
+ }
207
+
208
+ // Adjust the color to meet contrast
209
+ return adjustColorForContrast(bgRgb, fgRgb, minContrast);
210
+ }
211
+ }
212
+
213
+ // Determine if we need a light or dark color
214
+ const bgLuminance = getLuminance(bgRgb);
215
+
216
+ if (bgLuminance > 0.5) {
217
+ // Dark background needs light text
218
+ return '#000000';
219
+ } else {
220
+ // Light background needs dark text
221
+ return '#ffffff';
222
+ }
223
+ };
224
+
225
+ /**
226
+ * Adjusts a color to meet contrast requirements
227
+ * @param {Object} bgRgb - Background RGB
228
+ * @param {Object} fgRgb - Foreground RGB
229
+ * @param {number} minContrast - Minimum contrast ratio
230
+ * @returns {string} Adjusted color hex
231
+ */
232
+ const adjustColorForContrast = (bgRgb, fgRgb, minContrast) => {
233
+ const bgLuminance = getLuminance(bgRgb);
234
+ let adjustedRgb = { ...fgRgb };
235
+
236
+ // Try lightening or darkening
237
+ const isLightBackground = bgLuminance > 0.5;
238
+
239
+ for (let i = 0; i < 20; i++) {
240
+ const currentRatio = getContrastRatio(bgRgb, adjustedRgb);
241
+
242
+ if (currentRatio >= minContrast) {
243
+ return rgbToHex(adjustedRgb.r, adjustedRgb.g, adjustedRgb.b);
244
+ }
245
+
246
+ if (isLightBackground) {
247
+ // Darken the foreground
248
+ adjustedRgb.r = Math.max(0, adjustedRgb.r - 15);
249
+ adjustedRgb.g = Math.max(0, adjustedRgb.g - 15);
250
+ adjustedRgb.b = Math.max(0, adjustedRgb.b - 15);
251
+ } else {
252
+ // Lighten the foreground
253
+ adjustedRgb.r = Math.min(255, adjustedRgb.r + 15);
254
+ adjustedRgb.g = Math.min(255, adjustedRgb.g + 15);
255
+ adjustedRgb.b = Math.min(255, adjustedRgb.b + 15);
256
+ }
257
+ }
258
+
259
+ // Fallback to black or white
260
+ return isLightBackground ? '#000000' : '#ffffff';
261
+ };
262
+
263
+ /**
264
+ * Generates accessible color palette from base colors
265
+ * @param {Array} colors - Array of color strings
266
+ * @param {string} background - Background color
267
+ * @returns {Array} Accessible color palette
268
+ */
269
+ export const generateAccessiblePalette = (colors, background = '#ffffff') => {
270
+ if (!colors || !Array.isArray(colors)) return [];
271
+
272
+ return colors.map(color => {
273
+ const compliance = checkWCAGCompliance(color, background);
274
+
275
+ if (compliance.passesAA) {
276
+ return {
277
+ original: color,
278
+ accessible: color,
279
+ compliance,
280
+ };
281
+ }
282
+
283
+ const accessibleColor = suggestAccessibleColor(background, color);
284
+ return {
285
+ original: color,
286
+ accessible: accessibleColor,
287
+ compliance: checkWCAGCompliance(accessibleColor, background),
288
+ };
289
+ });
290
+ };
291
+
292
+ /**
293
+ * Pattern definitions for texture alternatives to color
294
+ */
295
+ export const PATTERNS = {
296
+ SOLID: 'solid',
297
+ STRIPED: 'striped',
298
+ DOTTED: 'dotted',
299
+ CROSSED: 'crossed',
300
+ DIAGONAL: 'diagonal',
301
+ WAVY: 'wavy',
302
+ };
303
+
304
+ /**
305
+ * Generates SVG pattern for texture fill
306
+ * @param {string} color - Base color
307
+ * @param {string} patternType - Pattern type
308
+ * @param {string} id - Pattern ID
309
+ * @returns {string} SVG pattern string
310
+ */
311
+ export const generatePattern = (color, patternType = PATTERNS.SOLID, id = 'pattern') => {
312
+ switch (patternType) {
313
+ case PATTERNS.STRIPED:
314
+ return `
315
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="8" height="8">
316
+ <rect width="8" height="8" fill="${color}"/>
317
+ <path d="M0,8 L8,0 M-2,2 L2,-2 M6,10 L10,6" stroke="rgba(255,255,255,0.5)" stroke-width="1"/>
318
+ </pattern>
319
+ `;
320
+
321
+ case PATTERNS.DOTTED:
322
+ return `
323
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="8" height="8">
324
+ <rect width="8" height="8" fill="${color}"/>
325
+ <circle cx="4" cy="4" r="1.5" fill="rgba(255,255,255,0.5)"/>
326
+ </pattern>
327
+ `;
328
+
329
+ case PATTERNS.CROSSED:
330
+ return `
331
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="8" height="8">
332
+ <rect width="8" height="8" fill="${color}"/>
333
+ <path d="M0,0 L8,8 M0,8 L8,0" stroke="rgba(255,255,255,0.5)" stroke-width="1"/>
334
+ </pattern>
335
+ `;
336
+
337
+ case PATTERNS.DIAGONAL:
338
+ return `
339
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="8" height="8">
340
+ <rect width="8" height="8" fill="${color}"/>
341
+ <path d="M0,8 L8,0" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
342
+ </pattern>
343
+ `;
344
+
345
+ case PATTERNS.WAVY:
346
+ return `
347
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="10" height="8">
348
+ <rect width="10" height="8" fill="${color}"/>
349
+ <path d="M0,4 Q2.5,0 5,4 T10,4" stroke="rgba(255,255,255,0.5)" stroke-width="1" fill="none"/>
350
+ </pattern>
351
+ `;
352
+
353
+ case PATTERNS.SOLID:
354
+ default:
355
+ return `
356
+ <pattern id="${id}" patternUnits="userSpaceOnUse" width="4" height="4">
357
+ <rect width="4" height="4" fill="${color}"/>
358
+ </pattern>
359
+ `;
360
+ }
361
+ };
362
+
363
+ /**
364
+ * Generates pattern fills for chart series to distinguish without color
365
+ * @param {Array} colors - Array of colors
366
+ * @param {string} baseId - Base ID for patterns
367
+ * @returns {Object} Pattern configuration for charts
368
+ */
369
+ export const generatePatternFills = (colors, baseId = 'chart-pattern') => {
370
+ if (!colors || !Array.isArray(colors)) return { patterns: [], fills: [] };
371
+
372
+ const patternTypes = [
373
+ PATTERNS.SOLID,
374
+ PATTERNS.STRIPED,
375
+ PATTERNS.DOTTED,
376
+ PATTERNS.CROSSED,
377
+ PATTERNS.DIAGONAL,
378
+ PATTERNS.WAVY,
379
+ ];
380
+
381
+ const patterns = [];
382
+ const fills = [];
383
+
384
+ colors.forEach((color, index) => {
385
+ const patternType = patternTypes[index % patternTypes.length];
386
+ const patternId = `${baseId}-${index}`;
387
+
388
+ patterns.push(generatePattern(color, patternType, patternId));
389
+ fills.push(`url(#${patternId})`);
390
+ });
391
+
392
+ return { patterns, fills };
393
+ };
394
+
395
+ /**
396
+ * Validates chart color configuration for accessibility
397
+ * @param {Object} config - Chart configuration
398
+ * @returns {Object} Validation results with suggestions
399
+ */
400
+ export const validateChartColors = (config) => {
401
+ const results = {
402
+ valid: true,
403
+ issues: [],
404
+ suggestions: {},
405
+ };
406
+
407
+ const backgroundColor = config.backgroundColor || '#ffffff';
408
+
409
+ // Check series colors
410
+ if (config.color && Array.isArray(config.color)) {
411
+ config.color.forEach((color, index) => {
412
+ const compliance = checkWCAGCompliance(color, backgroundColor);
413
+ if (!compliance.passesAA) {
414
+ results.valid = false;
415
+ results.issues.push({
416
+ type: 'series-color',
417
+ index,
418
+ color,
419
+ message: `Series color ${color} does not meet WCAG AA contrast requirements (${compliance.ratio}:1)`,
420
+ });
421
+
422
+ results.suggestions[`series-${index}`] = suggestAccessibleColor(backgroundColor, color);
423
+ }
424
+ });
425
+ }
426
+
427
+ // Check title color
428
+ if (config.title?.textStyle?.color) {
429
+ const titleCompliance = checkWCAGCompliance(config.title.textStyle.color, backgroundColor);
430
+ if (!titleCompliance.passesAA) {
431
+ results.valid = false;
432
+ results.issues.push({
433
+ type: 'title-color',
434
+ color: config.title.textStyle.color,
435
+ message: `Title color does not meet WCAG AA contrast requirements`,
436
+ });
437
+ results.suggestions.title = suggestAccessibleColor(backgroundColor, config.title.textStyle.color);
438
+ }
439
+ }
440
+
441
+ // Check axis labels
442
+ if (config.xAxis?.axisLabel?.color) {
443
+ const xAxisCompliance = checkWCAGCompliance(config.xAxis.axisLabel.color, backgroundColor);
444
+ if (!xAxisCompliance.passesAA) {
445
+ results.valid = false;
446
+ results.issues.push({
447
+ type: 'xaxis-color',
448
+ color: config.xAxis.axisLabel.color,
449
+ message: `X-axis label color does not meet WCAG AA contrast requirements`,
450
+ });
451
+ }
452
+ }
453
+
454
+ if (config.yAxis?.axisLabel?.color) {
455
+ const yAxisCompliance = checkWCAGCompliance(config.yAxis.axisLabel.color, backgroundColor);
456
+ if (!yAxisCompliance.passesAA) {
457
+ results.valid = false;
458
+ results.issues.push({
459
+ type: 'yaxis-color',
460
+ color: config.yAxis.axisLabel.color,
461
+ message: `Y-axis label color does not meet WCAG AA contrast requirements`,
462
+ });
463
+ }
464
+ }
465
+
466
+ return results;
467
+ };
468
+
469
+ /**
470
+ * Applies accessibility fixes to chart configuration
471
+ * @param {Object} config - Original chart configuration
472
+ * @returns {Object} Fixed configuration
473
+ */
474
+ export const applyAccessibleColors = (config) => {
475
+ const fixed = { ...config };
476
+ const backgroundColor = config.backgroundColor || '#ffffff';
477
+
478
+ const validation = validateChartColors(config);
479
+
480
+ // Fix series colors
481
+ if (validation.suggestions) {
482
+ if (fixed.color && Array.isArray(fixed.color)) {
483
+ fixed.color = fixed.color.map((color, index) => {
484
+ return validation.suggestions[`series-${index}`] || color;
485
+ });
486
+ }
487
+
488
+ if (validation.suggestions.title && fixed.title?.textStyle) {
489
+ fixed.title.textStyle.color = validation.suggestions.title;
490
+ }
491
+ }
492
+
493
+ return fixed;
494
+ };
495
+
496
+ /**
497
+ * Determines if a color is light or dark
498
+ * @param {string} color - Color string
499
+ * @returns {string} 'light' or 'dark'
500
+ */
501
+ export const getColorBrightness = (color) => {
502
+ const rgb = parseColor(color);
503
+ if (!rgb) return 'dark';
504
+
505
+ const luminance = getLuminance(rgb);
506
+ return luminance > 0.5 ? 'light' : 'dark';
507
+ };
508
+
509
+ /**
510
+ * Gets contrasting text color for a background
511
+ * @param {string} background - Background color
512
+ * @returns {string} '#000000' or '#ffffff'
513
+ */
514
+ export const getContrastingTextColor = (background) => {
515
+ return getColorBrightness(background) === 'light' ? '#000000' : '#ffffff';
516
+ };
517
+
518
+ export default {
519
+ WCAG_THRESHOLDS,
520
+ hexToRgb,
521
+ rgbToHex,
522
+ parseColor,
523
+ getLuminance,
524
+ getContrastRatio,
525
+ checkWCAGCompliance,
526
+ suggestAccessibleColor,
527
+ generateAccessiblePalette,
528
+ PATTERNS,
529
+ generatePattern,
530
+ generatePatternFills,
531
+ validateChartColors,
532
+ applyAccessibleColors,
533
+ getColorBrightness,
534
+ getContrastingTextColor,
535
+ };