@automattic/charts 4.3.0 → 4.5.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 (41) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/TOKENS.md +2 -2
  3. package/dist/index.cjs +584 -209
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +4 -0
  6. package/dist/index.d.cts +14 -3
  7. package/dist/index.d.ts +14 -3
  8. package/dist/index.js +585 -210
  9. package/dist/index.js.map +1 -1
  10. package/package.json +2 -2
  11. package/src/charts/bar-chart/bar-chart.tsx +57 -23
  12. package/src/charts/bar-chart/private/band-tooltip.tsx +18 -12
  13. package/src/charts/bar-chart/private/classified-bar-series.tsx +85 -0
  14. package/src/charts/bar-chart/private/comparison-bars-geometry.ts +3 -6
  15. package/src/charts/bar-chart/private/index.ts +1 -0
  16. package/src/charts/bar-chart/private/test/comparison-bars-geometry.test.ts +2 -3
  17. package/src/charts/bar-chart/test/bar-chart.test.tsx +130 -1
  18. package/src/charts/bar-chart/types.ts +5 -1
  19. package/src/charts/geo-chart/geo-chart.tsx +4 -0
  20. package/src/charts/geo-chart/test/geo-chart.test.tsx +8 -0
  21. package/src/charts/geo-chart/types.ts +5 -0
  22. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +1 -1
  23. package/src/charts/pie-chart/pie-chart.module.scss +4 -0
  24. package/src/charts/pie-chart/pie-chart.tsx +103 -7
  25. package/src/charts/pie-chart/test/pie-chart.test.tsx +149 -0
  26. package/src/charts/private/chart-layout/chart-layout.tsx +5 -1
  27. package/src/components/tooltip/accessible-tooltip.tsx +40 -4
  28. package/src/components/tooltip/test/accessible-tooltip.test.tsx +82 -5
  29. package/src/providers/chart-context/global-charts-provider.tsx +57 -41
  30. package/src/providers/chart-context/private/catalog-pointers.ts +9 -1
  31. package/src/providers/chart-context/private/palette-generator.ts +305 -0
  32. package/src/providers/chart-context/private/perceptual-color.ts +219 -0
  33. package/src/providers/chart-context/private/series-palette.ts +1 -1
  34. package/src/providers/chart-context/test/chart-context.test.tsx +88 -8
  35. package/src/providers/chart-context/test/global-charts-provider-first-render.test.tsx +27 -0
  36. package/src/providers/chart-context/test/palette-generator.test.ts +181 -0
  37. package/src/providers/chart-context/test/perceptual-color.test.ts +75 -0
  38. package/src/test-utils/zoned-parsing-suite.ts +1 -1
  39. package/src/utils/color-utils.ts +5 -3
  40. package/src/utils/test/date-formatting.test.ts +1 -1
  41. package/src/providers/chart-context/private/get-chart-color.ts +0 -223
@@ -1,223 +0,0 @@
1
- import { hsl as d3Hsl } from '@visx/vendor/d3-color';
2
- import { getColorDistance } from '../../../utils';
3
-
4
- export interface ColorCache {
5
- colors: string[];
6
- hues: number[];
7
- existingHslColors: Array< [ number, number, number ] >;
8
- minHue: number;
9
- maxHue: number;
10
- }
11
-
12
- /**
13
- * Golden ratio for mathematically pleasing color distribution
14
- * Used to generate evenly spaced hues that are visually distinct
15
- */
16
- const GOLDEN_RATIO = 0.618033988749;
17
-
18
- /**
19
- * Minimum perceptual distance between colors to ensure visual distinction
20
- * Based on weighted HSL distance calculation optimized for chart readability
21
- */
22
- const MIN_COLOR_DISTANCE = 25;
23
-
24
- /**
25
- * Maximum attempts to find a sufficiently different color
26
- * Prevents infinite loops while allowing reasonable search space
27
- */
28
- const MAX_COLOR_GENERATION_ATTEMPTS = 50;
29
-
30
- /**
31
- * Color variation attempt offset
32
- * Small increment to explore slightly different color variations per attempt
33
- */
34
- const VARIATION_ATTEMPT_OFFSET = 0.1;
35
-
36
- // Saturation configuration for generated colors
37
-
38
- /**
39
- * Base saturation percentage for generated colors
40
- * 45% provides muted, professional colors without being washed out
41
- */
42
- const BASE_SATURATION = 45;
43
-
44
- /**
45
- * Number of saturation variation steps
46
- * Creates 3 different saturation levels for variety
47
- */
48
- const SATURATION_VARIATION_STEPS = 3;
49
-
50
- /**
51
- * Saturation increment per variation step
52
- * 10% increments provide subtle variation while keeping colors muted
53
- * Results in saturation levels: 45%, 55%, 65%
54
- */
55
- const SATURATION_INCREMENT = 10;
56
-
57
- // Lightness configuration for WCAG AA accessibility compliance
58
-
59
- /**
60
- * Base lightness percentage for generated colors
61
- * 35% ensures sufficient contrast against white backgrounds for WCAG AA compliance
62
- * WCAG AA requires 4.5:1 contrast ratio for normal text
63
- */
64
- const BASE_LIGHTNESS = 35;
65
-
66
- /**
67
- * Number of lightness variation steps
68
- * Creates 4 different lightness levels for variety
69
- */
70
- const LIGHTNESS_VARIATION_STEPS = 4;
71
-
72
- /**
73
- * Lightness increment per variation step
74
- * 8% increments provide subtle lightness variation while maintaining accessibility
75
- * Results in lightness levels: 35%, 43%, 51%, 59%
76
- * All levels maintain WCAG AA compliance against white backgrounds
77
- */
78
- const LIGHTNESS_INCREMENT = 8;
79
-
80
- // Hue range expansion and constraints
81
-
82
- /**
83
- * Minimum hue range in degrees to ensure sufficient color variety
84
- * 60 degrees provides reasonable color spread even for narrow palettes
85
- */
86
- const MIN_HUE_RANGE_DEGREES = 60;
87
-
88
- /**
89
- * Hue range expansion factor
90
- * 1.3x expansion provides slightly more variety than the original palette
91
- */
92
- const HUE_RANGE_EXPANSION_FACTOR = 1.3;
93
-
94
- /**
95
- * Threshold for detecting hue wrap-around (color wheel boundary crossing)
96
- * 180 degrees indicates the colors span more than half the color wheel
97
- */
98
- const HUE_WRAP_THRESHOLD_DEGREES = 180;
99
-
100
- /**
101
- * Full color wheel rotation in degrees
102
- */
103
- const FULL_HUE_ROTATION_DEGREES = 360;
104
-
105
- /**
106
- * Factor for single color hue range
107
- */
108
- const SINGLE_COLOR_HUE_RANGE_FACTOR = 0.33;
109
-
110
- /**
111
- * Get a color from the colors array or generate a new color using the golden ratio
112
- *
113
- * @param index - the index of the color to get
114
- * @param colorCache - pre-computed color data for performance
115
- * @return a color from the colors array or a new color using the golden ratio
116
- */
117
- export const getChartColor = ( index: number, colorCache: ColorCache ): string => {
118
- const {
119
- colors,
120
- hues,
121
- existingHslColors,
122
- minHue: cachedMinHue,
123
- maxHue: cachedMaxHue,
124
- } = colorCache;
125
-
126
- if ( index < colors.length ) {
127
- return colors[ index ];
128
- }
129
-
130
- let minHue = cachedMinHue;
131
- let maxHue = cachedMaxHue;
132
-
133
- // Generate additional colors using golden ratio, avoiding similar colors
134
- for ( let attempt = 0; attempt < MAX_COLOR_GENERATION_ATTEMPTS; attempt++ ) {
135
- // Calculate hue using golden ratio distribution with variation per attempt
136
- // Formula: ((base_index + attempt_variation) * golden_ratio * 360°) mod 360°
137
- // This ensures mathematically pleasing spacing while allowing slight shifts per attempt
138
- let hue =
139
- ( ( index - colors.length + attempt * VARIATION_ATTEMPT_OFFSET ) *
140
- GOLDEN_RATIO *
141
- FULL_HUE_ROTATION_DEGREES ) %
142
- FULL_HUE_ROTATION_DEGREES;
143
-
144
- // If we have existing colors, constrain new colors to their hue range
145
- if ( hues.length > 0 ) {
146
- // Handle hue wrap-around (e.g., if colors span across 0 degrees)
147
- let hueRange = maxHue - minHue;
148
-
149
- // If there's only one color, use a set hue range for limited variety
150
- if ( hues.length === 1 ) {
151
- hueRange = FULL_HUE_ROTATION_DEGREES * SINGLE_COLOR_HUE_RANGE_FACTOR;
152
- } else if ( hueRange > HUE_WRAP_THRESHOLD_DEGREES ) {
153
- // If the range is very large, it might be wrapping around the color wheel
154
- // Check if a smaller range exists when considering wrap-around
155
- const altMinHue = Math.min( ...hues.filter( h => h > HUE_WRAP_THRESHOLD_DEGREES ) );
156
- const altMaxHue =
157
- Math.max( ...hues.filter( h => h < HUE_WRAP_THRESHOLD_DEGREES ) ) +
158
- FULL_HUE_ROTATION_DEGREES;
159
- const altRange = altMaxHue - altMinHue;
160
-
161
- if ( altRange < hueRange ) {
162
- minHue = altMinHue;
163
- maxHue = altMaxHue;
164
- hueRange = altRange;
165
- }
166
- }
167
-
168
- // Expand the range slightly to provide some variation
169
- const expandedRange = Math.max(
170
- hueRange * HUE_RANGE_EXPANSION_FACTOR,
171
- MIN_HUE_RANGE_DEGREES
172
- );
173
- const rangeCenter = ( minHue + maxHue ) / 2;
174
- const expandedMin = rangeCenter - expandedRange / 2;
175
-
176
- // Map the generated hue to the expanded range
177
- hue = expandedMin + ( hue / FULL_HUE_ROTATION_DEGREES ) * expandedRange;
178
-
179
- // Normalize to 0-360 range
180
- hue =
181
- ( ( hue % FULL_HUE_ROTATION_DEGREES ) + FULL_HUE_ROTATION_DEGREES ) %
182
- FULL_HUE_ROTATION_DEGREES;
183
- }
184
-
185
- const saturation =
186
- BASE_SATURATION + ( ( index + attempt ) % SATURATION_VARIATION_STEPS ) * SATURATION_INCREMENT;
187
- const lightness =
188
- BASE_LIGHTNESS + ( ( index + attempt ) % LIGHTNESS_VARIATION_STEPS ) * LIGHTNESS_INCREMENT;
189
-
190
- const candidateHsl: [ number, number, number ] = [ hue, saturation, lightness ];
191
-
192
- // Check if this color is sufficiently different from existing colors
193
- let isSufficientlyDifferent = true;
194
- for ( const existingHsl of existingHslColors ) {
195
- if ( getColorDistance( candidateHsl, existingHsl ) < MIN_COLOR_DISTANCE ) {
196
- isSufficientlyDifferent = false;
197
- break;
198
- }
199
- }
200
-
201
- if ( isSufficientlyDifferent ) {
202
- // d3-color uses 0-1 scale for saturation and lightness
203
- return d3Hsl( Math.round( hue ), saturation / 100, lightness / 100 ).formatHex();
204
- }
205
- }
206
-
207
- // Fallback if we couldn't find a sufficiently different color
208
- // Formula: ((base_index) * golden_ratio * 360°) mod 360°
209
- // This ensures mathematically pleasing spacing while maintaining consistency
210
- const fallbackHue =
211
- ( ( index - colors.length ) * GOLDEN_RATIO * FULL_HUE_ROTATION_DEGREES ) %
212
- FULL_HUE_ROTATION_DEGREES;
213
- const fallbackSaturation =
214
- BASE_SATURATION + ( index % SATURATION_VARIATION_STEPS ) * SATURATION_INCREMENT;
215
- const fallbackLightness =
216
- BASE_LIGHTNESS + ( index % LIGHTNESS_VARIATION_STEPS ) * LIGHTNESS_INCREMENT;
217
- // d3-color uses 0-1 scale for saturation and lightness
218
- return d3Hsl(
219
- Math.round( fallbackHue ),
220
- fallbackSaturation / 100,
221
- fallbackLightness / 100
222
- ).formatHex();
223
- };