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.
- package/.claude/mem/TIMELINE.md +36 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
- package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
- package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
- package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
- package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
- package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
- package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
- package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
- package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
- package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
- package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
- package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
- package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
- package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
- package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
- package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
- package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
- package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
- package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
- package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
- package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
- package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
- package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
- package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
- package/.claude/settings.local.json +21 -0
- package/.github/workflows/static.yml.bak +51 -51
- package/.sisyphus/boulder.json +65 -0
- package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
- package/LICENSE +674 -674
- package/README.md +94 -59
- package/config-overrides.js +31 -31
- package/dist/stats.html +4949 -0
- package/dist/visualify-3d.esm.js +1 -0
- package/dist/visualify-3d.js +1 -0
- package/dist/visualify-core.esm.js +1 -0
- package/dist/visualify-core.js +1 -0
- package/dist/visualify-docs.esm.js +1 -0
- package/dist/visualify-docs.js +1 -0
- package/dist/visualify-loader.js +1 -0
- package/dist/visualify-pages.esm.js +1 -0
- package/dist/visualify-pages.js +1 -0
- package/dist/visualify-portal.esm.js +1 -0
- package/dist/visualify-portal.js +1 -0
- package/dist/visualify-shared.js +26571 -0
- package/dist/visualify.js +1 -188
- package/docs/CHANGELOG.md +148 -0
- package/docs/cli/commands.md +513 -0
- package/docs/configuration/visualify-json.md +474 -0
- package/docs/docs/3d-visualization.md +374 -0
- package/docs/docs/CLI.md +303 -34
- package/docs/docs/README.md +65 -65
- package/docs/docs/Rechart/bar.md +190 -190
- package/docs/docs/Rechart/funnel.md +241 -193
- package/docs/docs/Rechart/line.md +355 -355
- package/docs/docs/Rechart/pie.md +225 -225
- package/docs/docs/Rechart/radar.md +253 -253
- package/docs/docs/Rechart/scatter.md +298 -0
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -43
- package/docs/docs/components/dotBio.md +87 -34
- package/docs/docs/components/echart.md +171 -82
- package/docs/docs/components/html.md +61 -34
- package/docs/docs/components/macaron.md +156 -145
- package/docs/docs/components/markdown.md +42 -0
- package/docs/docs/components/more.md +183 -142
- package/docs/docs/components/plotly.md +132 -62
- package/docs/docs/components/scatterL.md +171 -70
- package/docs/docs/components/visium.md +112 -57
- package/docs/docs/configuration.md +121 -123
- package/docs/docs/deploy.md +31 -31
- package/docs/docs/docsify-plugin.md +655 -0
- package/docs/docs/hmr.md +165 -0
- package/docs/docs/i18n.md +332 -0
- package/docs/docs/log.md +30 -1
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -119
- package/docs/docs/rechart-attributes.md +74 -74
- package/docs/docs/rechart-basic-usage.md +160 -162
- package/docs/docs/theme.md +5 -5
- package/docs/docs/typescript.md +306 -0
- package/docs/docs/visual-editor.md +359 -0
- package/docs/index.html +85 -71
- package/docs/manifest.json +23 -23
- package/docs/migration/v3-migration.md +392 -0
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +6 -25
- package/docs/static/js/3d-viz-examples.js +181 -0
- package/docs/static/js/configuration.js +630 -448
- package/docs/static/js/visualify.js +1 -188
- package/package.json +106 -84
- package/rollup.config.mjs +766 -76
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +350 -25
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/a11y/README.md +177 -0
- package/src/a11y/aria-labels.js +339 -0
- package/src/a11y/color-contrast.js +535 -0
- package/src/a11y/index.js +197 -0
- package/src/a11y/keyboard-nav.js +523 -0
- package/src/a11y/styles.css +165 -0
- package/src/cli/commands/dev.js +214 -0
- package/src/cli/commands/docs.js +521 -0
- package/src/cli/commands/edit.js +379 -0
- package/src/cli/commands/init.js +213 -0
- package/src/cli/commands/portal.js +236 -0
- package/src/cli/dev-server.js +530 -0
- package/src/cli/hmr.js +456 -0
- package/src/cli/index.js +180 -0
- package/src/cli/utils/config.js +207 -0
- package/src/cli/utils/logger.js +241 -0
- package/src/config/defaults.ts +122 -0
- package/src/config/index.ts +72 -0
- package/src/config/loader.ts +478 -0
- package/src/config/schema.ts +227 -0
- package/src/config/validator.ts +337 -0
- package/src/core/appContext.js +34 -27
- package/src/core/components/Bar.js +383 -0
- package/src/core/components/Bar3D.js +473 -0
- package/src/core/components/LargeDatasetChart.js +296 -0
- package/src/core/components/Line3D.js +310 -0
- package/src/core/components/Scatter.js +392 -188
- package/src/core/components/Scatter3D.js +455 -0
- package/src/core/components/ScatterBio.js +601 -572
- package/src/core/components/Surface3D.js +326 -0
- package/src/core/components/ThreeCustom.js +648 -0
- package/src/core/components/ThreeScene.js +459 -0
- package/src/core/components/VisiumPlot.js +191 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +206 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +582 -579
- package/src/core/components/ratio.js +82 -80
- package/src/core/components/scatterL.js +206 -173
- package/src/core/components/searchbar.js +156 -131
- package/src/core/components/selection.js +310 -193
- package/src/core/components/timeline.js +236 -281
- package/src/core/components/visium.js +114 -97
- package/src/core/data-processor.js +413 -0
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/hmr-client.js +724 -0
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/gl.js +228 -0
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +70 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +227 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +74 -71
- package/src/core/modules/threejs/Camera.js +373 -0
- package/src/core/modules/threejs/Lighting.js +459 -0
- package/src/core/modules/threejs/Renderer.js +364 -0
- package/src/core/modules/threejs/Scene.js +266 -0
- package/src/core/modules/threejs/index.js +155 -0
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +204 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +147 -143
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +361 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +213 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +116 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +185 -177
- package/src/core/widgets/header.js +238 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +56 -42
- package/src/core/workers/data-worker.js +349 -0
- package/src/core/workers/worker-pool.js +396 -0
- package/src/docsify/bundle.js +215 -0
- package/src/docsify/markdown.js +271 -0
- package/src/docsify/plugin.js +268 -0
- package/src/editor/README.md +172 -0
- package/src/editor/components/ChartBuilder.jsx +341 -0
- package/src/editor/components/ChartTypeSidebar.jsx +91 -0
- package/src/editor/components/Editor.jsx +367 -0
- package/src/editor/components/Preview.jsx +446 -0
- package/src/editor/components/PropertyPanel.jsx +468 -0
- package/src/editor/components/StatusBar.jsx +85 -0
- package/src/editor/context/EditorContext.js +248 -0
- package/src/editor/hooks/useDebounce.js +32 -0
- package/src/editor/index.js +315 -0
- package/src/editor/styles/editor.css +637 -0
- package/src/editor/utils/chartValidator.js +263 -0
- package/src/entries/charts3d.js +70 -0
- package/src/entries/core.js +78 -0
- package/src/entries/docs.js +154 -0
- package/src/entries/pages.js +93 -0
- package/src/entries/portal.js +204 -0
- package/src/entries/shared.js +50 -0
- package/src/i18n/formatters.js +455 -0
- package/src/i18n/index.js +169 -0
- package/src/i18n/locales/ar.json +137 -0
- package/src/i18n/locales/de.json +137 -0
- package/src/i18n/locales/en.json +137 -0
- package/src/i18n/locales/es.json +137 -0
- package/src/i18n/locales/he.json +137 -0
- package/src/i18n/locales/zh.json +137 -0
- package/src/i18n/rtl.css +183 -0
- package/src/index.js +82 -62
- package/src/loader.js +103 -0
- package/src/setupTests.js +5 -5
- package/tsconfig.json +51 -0
- package/types/charts.d.ts +569 -0
- package/types/components.d.ts +441 -0
- package/types/config.d.ts +199 -0
- 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
|
+
};
|