@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.
- package/CHANGELOG.md +20 -0
- package/TOKENS.md +2 -2
- package/dist/index.cjs +584 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +4 -0
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +585 -210
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/charts/bar-chart/bar-chart.tsx +57 -23
- package/src/charts/bar-chart/private/band-tooltip.tsx +18 -12
- package/src/charts/bar-chart/private/classified-bar-series.tsx +85 -0
- package/src/charts/bar-chart/private/comparison-bars-geometry.ts +3 -6
- package/src/charts/bar-chart/private/index.ts +1 -0
- package/src/charts/bar-chart/private/test/comparison-bars-geometry.test.ts +2 -3
- package/src/charts/bar-chart/test/bar-chart.test.tsx +130 -1
- package/src/charts/bar-chart/types.ts +5 -1
- package/src/charts/geo-chart/geo-chart.tsx +4 -0
- package/src/charts/geo-chart/test/geo-chart.test.tsx +8 -0
- package/src/charts/geo-chart/types.ts +5 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +1 -1
- package/src/charts/pie-chart/pie-chart.module.scss +4 -0
- package/src/charts/pie-chart/pie-chart.tsx +103 -7
- package/src/charts/pie-chart/test/pie-chart.test.tsx +149 -0
- package/src/charts/private/chart-layout/chart-layout.tsx +5 -1
- package/src/components/tooltip/accessible-tooltip.tsx +40 -4
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +82 -5
- package/src/providers/chart-context/global-charts-provider.tsx +57 -41
- package/src/providers/chart-context/private/catalog-pointers.ts +9 -1
- package/src/providers/chart-context/private/palette-generator.ts +305 -0
- package/src/providers/chart-context/private/perceptual-color.ts +219 -0
- package/src/providers/chart-context/private/series-palette.ts +1 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +88 -8
- package/src/providers/chart-context/test/global-charts-provider-first-render.test.tsx +27 -0
- package/src/providers/chart-context/test/palette-generator.test.ts +181 -0
- package/src/providers/chart-context/test/perceptual-color.test.ts +75 -0
- package/src/test-utils/zoned-parsing-suite.ts +1 -1
- package/src/utils/color-utils.ts +5 -3
- package/src/utils/test/date-formatting.test.ts +1 -1
- package/src/providers/chart-context/private/get-chart-color.ts +0 -223
package/dist/index.cjs
CHANGED
|
@@ -438,9 +438,11 @@ const hexToRgba = (hex, alpha) => {
|
|
|
438
438
|
return (0, _visx_vendor_d3_color.color)(hex).copy({ opacity: alpha }).formatRgb();
|
|
439
439
|
};
|
|
440
440
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
* @
|
|
441
|
+
* Weighted Euclidean distance between two HSL colors.
|
|
442
|
+
*
|
|
443
|
+
* @deprecated Not perceptual and blind to color vision deficiency, so charts no longer use it; it will be removed in a future major version.
|
|
444
|
+
* @param hsl1 - first color in HSL format [h, s, l]
|
|
445
|
+
* @param hsl2 - second color in HSL format [h, s, l]
|
|
444
446
|
* @return distance value (0-100+, lower means more similar)
|
|
445
447
|
*/
|
|
446
448
|
const getColorDistance = (hsl1, hsl2) => {
|
|
@@ -802,145 +804,421 @@ const createZonedClock = (timeZone) => {
|
|
|
802
804
|
* All JS token resolution reads this element rather than `document.documentElement`, so a `getComputedStyle` call sees the same `--a8c-charts-*` values — including any override set inside the provider tree — that a CSS-painted sibling element does.
|
|
803
805
|
*/
|
|
804
806
|
const ChartScopeContext = (0, react.createContext)(null);
|
|
805
|
-
//#endregion
|
|
806
|
-
//#region src/providers/chart-context/private/get-chart-color.ts
|
|
807
|
-
/**
|
|
808
|
-
* Golden ratio for mathematically pleasing color distribution
|
|
809
|
-
* Used to generate evenly spaced hues that are visually distinct
|
|
810
|
-
*/
|
|
811
|
-
const GOLDEN_RATIO = .618033988749;
|
|
812
|
-
/**
|
|
813
|
-
* Minimum perceptual distance between colors to ensure visual distinction
|
|
814
|
-
* Based on weighted HSL distance calculation optimized for chart readability
|
|
815
|
-
*/
|
|
816
|
-
const MIN_COLOR_DISTANCE = 25;
|
|
817
|
-
/**
|
|
818
|
-
* Maximum attempts to find a sufficiently different color
|
|
819
|
-
* Prevents infinite loops while allowing reasonable search space
|
|
820
|
-
*/
|
|
821
|
-
const MAX_COLOR_GENERATION_ATTEMPTS = 50;
|
|
822
|
-
/**
|
|
823
|
-
* Color variation attempt offset
|
|
824
|
-
* Small increment to explore slightly different color variations per attempt
|
|
825
|
-
*/
|
|
826
|
-
const VARIATION_ATTEMPT_OFFSET = .1;
|
|
827
|
-
/**
|
|
828
|
-
* Base saturation percentage for generated colors
|
|
829
|
-
* 45% provides muted, professional colors without being washed out
|
|
830
|
-
*/
|
|
831
|
-
const BASE_SATURATION = 45;
|
|
832
|
-
/**
|
|
833
|
-
* Number of saturation variation steps
|
|
834
|
-
* Creates 3 different saturation levels for variety
|
|
835
|
-
*/
|
|
836
|
-
const SATURATION_VARIATION_STEPS = 3;
|
|
837
807
|
/**
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
|
|
842
|
-
const SATURATION_INCREMENT = 10;
|
|
843
|
-
/**
|
|
844
|
-
* Base lightness percentage for generated colors
|
|
845
|
-
* 35% ensures sufficient contrast against white backgrounds for WCAG AA compliance
|
|
846
|
-
* WCAG AA requires 4.5:1 contrast ratio for normal text
|
|
808
|
+
* The catalog role holding one series-palette slot.
|
|
809
|
+
*
|
|
810
|
+
* @param slot - The one-based slot number.
|
|
811
|
+
* @return The role name.
|
|
847
812
|
*/
|
|
848
|
-
const
|
|
813
|
+
const seriesRole = (slot) => `--a8c-charts-color-series-${slot}`;
|
|
814
|
+
const SERIES_SLOT_1_FALLBACK = "#3858e9";
|
|
815
|
+
/** The catalog pointer for every slot, in slot order. */
|
|
816
|
+
const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
|
|
817
|
+
//#endregion
|
|
818
|
+
//#region src/providers/chart-context/private/catalog-pointers.ts
|
|
819
|
+
/** Terminal literals the provider also generates from before its first read, so SSR and the first client render agree. */
|
|
820
|
+
const BACKGROUND_FALLBACK = "#ffffff";
|
|
821
|
+
const LABEL_FALLBACK = "#1e1e1e";
|
|
822
|
+
const LABEL_INVERSE_FALLBACK = "#f0f0f0";
|
|
823
|
+
const CATALOG_POINTERS = {
|
|
824
|
+
background: `var(--a8c-charts-color-background, ${BACKGROUND_FALLBACK})`,
|
|
825
|
+
label: `var(--a8c-charts-color-label, ${LABEL_FALLBACK})`,
|
|
826
|
+
labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
827
|
+
labelInverse: `var(--a8c-charts-color-label-inverse, ${LABEL_INVERSE_FALLBACK})`,
|
|
828
|
+
labelBackground: "var(--a8c-charts-color-label-background, transparent)",
|
|
829
|
+
grid: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
830
|
+
axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
|
|
831
|
+
tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
|
|
832
|
+
axisY: "var(--a8c-charts-color-axis-y, none)",
|
|
833
|
+
tickY: "var(--a8c-charts-color-tick-y, none)",
|
|
834
|
+
annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
835
|
+
surface: "var(--a8c-charts-color-surface, #fff)",
|
|
836
|
+
surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
837
|
+
trendUp: "var(--a8c-charts-color-trend-up, #008030)",
|
|
838
|
+
trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
|
|
839
|
+
series: SERIES_PALETTE_POINTERS
|
|
840
|
+
};
|
|
849
841
|
/**
|
|
850
|
-
*
|
|
851
|
-
*
|
|
842
|
+
* The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
|
|
843
|
+
* rides along as the base the theme and the per-datum styles merge onto.
|
|
852
844
|
*/
|
|
853
|
-
const
|
|
845
|
+
const ANNOTATION_POINTERS = {
|
|
846
|
+
label: {
|
|
847
|
+
anchorLineStroke: CATALOG_POINTERS.annotation,
|
|
848
|
+
backgroundFill: CATALOG_POINTERS.surface
|
|
849
|
+
},
|
|
850
|
+
connector: { stroke: CATALOG_POINTERS.annotation },
|
|
851
|
+
circleSubject: {
|
|
852
|
+
stroke: "transparent",
|
|
853
|
+
fill: CATALOG_POINTERS.annotation,
|
|
854
|
+
radius: 5
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/providers/chart-context/private/perceptual-color.ts
|
|
859
|
+
const DEUTERANOPIA = [
|
|
860
|
+
.367322,
|
|
861
|
+
.860646,
|
|
862
|
+
-.227968,
|
|
863
|
+
.280085,
|
|
864
|
+
.672501,
|
|
865
|
+
.047413,
|
|
866
|
+
-.01182,
|
|
867
|
+
.04294,
|
|
868
|
+
.968881
|
|
869
|
+
];
|
|
870
|
+
const PROTANOPIA = [
|
|
871
|
+
.152286,
|
|
872
|
+
1.052583,
|
|
873
|
+
-.204868,
|
|
874
|
+
.114503,
|
|
875
|
+
.786281,
|
|
876
|
+
.099216,
|
|
877
|
+
-.003882,
|
|
878
|
+
-.048116,
|
|
879
|
+
1.051998
|
|
880
|
+
];
|
|
881
|
+
const DEGREES = Math.PI / 180;
|
|
882
|
+
const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
|
|
883
|
+
const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
|
|
884
|
+
const hexToLinear = (hex) => {
|
|
885
|
+
validateHexColor(hex);
|
|
886
|
+
return [
|
|
887
|
+
1,
|
|
888
|
+
3,
|
|
889
|
+
5
|
|
890
|
+
].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
|
|
891
|
+
};
|
|
892
|
+
const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
|
|
893
|
+
const simulate = (matrix, [r, g, b]) => [
|
|
894
|
+
0,
|
|
895
|
+
3,
|
|
896
|
+
6
|
|
897
|
+
].map((row) => Math.min(1, Math.max(0, matrix[row] * r + matrix[row + 1] * g + matrix[row + 2] * b)));
|
|
898
|
+
const linearToLab = ([r, g, b]) => {
|
|
899
|
+
const x = (.4124564 * r + .3575761 * g + .1804375 * b) / .95047;
|
|
900
|
+
const y = .2126729 * r + .7151522 * g + .072175 * b;
|
|
901
|
+
const z = (.0193339 * r + .119192 * g + .9503041 * b) / 1.08883;
|
|
902
|
+
const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
903
|
+
return [
|
|
904
|
+
116 * f(y) - 16,
|
|
905
|
+
500 * (f(x) - f(y)),
|
|
906
|
+
200 * (f(y) - f(z))
|
|
907
|
+
];
|
|
908
|
+
};
|
|
854
909
|
/**
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
|
|
860
|
-
|
|
910
|
+
* CIEDE2000 color difference between two CIELAB colors.
|
|
911
|
+
*
|
|
912
|
+
* @param first - First color in CIELAB.
|
|
913
|
+
* @param second - Second color in CIELAB.
|
|
914
|
+
* @return ΔE00; about 10 is where two chart categories stop being reliably distinguishable.
|
|
915
|
+
*/
|
|
916
|
+
const deltaE2000 = (first, second) => {
|
|
917
|
+
const [l1, a1, b1] = first;
|
|
918
|
+
const [l2, a2, b2] = second;
|
|
919
|
+
const chromaMean = (Math.hypot(a1, b1) + Math.hypot(a2, b2)) / 2;
|
|
920
|
+
const g = .5 * (1 - Math.sqrt(chromaMean ** 7 / (chromaMean ** 7 + 25 ** 7)));
|
|
921
|
+
const a1p = a1 * (1 + g);
|
|
922
|
+
const a2p = a2 * (1 + g);
|
|
923
|
+
const c1p = Math.hypot(a1p, b1);
|
|
924
|
+
const c2p = Math.hypot(a2p, b2);
|
|
925
|
+
const hueOf = (a, b) => a === 0 && b === 0 ? 0 : (Math.atan2(b, a) / DEGREES + 360) % 360;
|
|
926
|
+
const h1p = hueOf(a1p, b1);
|
|
927
|
+
const h2p = hueOf(a2p, b2);
|
|
928
|
+
const chromatic = c1p * c2p !== 0;
|
|
929
|
+
let hueDelta = chromatic ? h2p - h1p : 0;
|
|
930
|
+
if (hueDelta > 180) hueDelta -= 360;
|
|
931
|
+
else if (hueDelta < -180) hueDelta += 360;
|
|
932
|
+
const lightnessDelta = l2 - l1;
|
|
933
|
+
const chromaDelta = c2p - c1p;
|
|
934
|
+
const hueDifference = 2 * Math.sqrt(c1p * c2p) * Math.sin(hueDelta * DEGREES / 2);
|
|
935
|
+
const lightnessMean = (l1 + l2) / 2;
|
|
936
|
+
const chromaMeanPrime = (c1p + c2p) / 2;
|
|
937
|
+
let hueMean = h1p + h2p;
|
|
938
|
+
if (chromatic) {
|
|
939
|
+
if (Math.abs(h1p - h2p) > 180) hueMean += hueMean < 360 ? 360 : -360;
|
|
940
|
+
hueMean /= 2;
|
|
941
|
+
}
|
|
942
|
+
const t = 1 - .17 * Math.cos((hueMean - 30) * DEGREES) + .24 * Math.cos(2 * hueMean * DEGREES) + .32 * Math.cos((3 * hueMean + 6) * DEGREES) - .2 * Math.cos((4 * hueMean - 63) * DEGREES);
|
|
943
|
+
const lightnessScale = 1 + .015 * (lightnessMean - 50) ** 2 / Math.sqrt(20 + (lightnessMean - 50) ** 2);
|
|
944
|
+
const chromaScale = 1 + .045 * chromaMeanPrime;
|
|
945
|
+
const hueScale = 1 + .015 * chromaMeanPrime * t;
|
|
946
|
+
const rotation = -2 * Math.sqrt(chromaMeanPrime ** 7 / (chromaMeanPrime ** 7 + 25 ** 7)) * Math.sin(60 * Math.exp(-(((hueMean - 275) / 25) ** 2)) * DEGREES);
|
|
947
|
+
const lightnessTerm = lightnessDelta / lightnessScale;
|
|
948
|
+
const chromaTerm = chromaDelta / chromaScale;
|
|
949
|
+
const hueTerm = hueDifference / hueScale;
|
|
950
|
+
return Math.sqrt(lightnessTerm ** 2 + chromaTerm ** 2 + hueTerm ** 2 + rotation * chromaTerm * hueTerm);
|
|
951
|
+
};
|
|
861
952
|
/**
|
|
862
|
-
*
|
|
863
|
-
*
|
|
953
|
+
* A hex color in CIELAB as seen in normal vision, under deuteranopia and under protanopia.
|
|
954
|
+
*
|
|
955
|
+
* @param hex - Six-digit hex color.
|
|
956
|
+
* @return The three views.
|
|
957
|
+
* @throws {Error} if hex string is malformed
|
|
864
958
|
*/
|
|
865
|
-
const
|
|
959
|
+
const hexToViews = (hex) => {
|
|
960
|
+
const linear = hexToLinear(hex);
|
|
961
|
+
return [
|
|
962
|
+
linearToLab(linear),
|
|
963
|
+
linearToLab(simulate(DEUTERANOPIA, linear)),
|
|
964
|
+
linearToLab(simulate(PROTANOPIA, linear))
|
|
965
|
+
];
|
|
966
|
+
};
|
|
866
967
|
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
968
|
+
* The smallest ΔE00 between two colors across the three views.
|
|
969
|
+
*
|
|
970
|
+
* @param first - First color's views.
|
|
971
|
+
* @param second - Second color's views.
|
|
972
|
+
* @return The distance in the view where the two are hardest to tell apart.
|
|
869
973
|
*/
|
|
870
|
-
const
|
|
974
|
+
const viewDistance = (first, second) => Math.min(deltaE2000(first[0], second[0]), deltaE2000(first[1], second[1]), deltaE2000(first[2], second[2]));
|
|
871
975
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
976
|
+
* Convert an OKLCH color to sRGB hex.
|
|
977
|
+
*
|
|
978
|
+
* @param lightness - OKLab L, 0 to 1.
|
|
979
|
+
* @param chroma - OKLCH C.
|
|
980
|
+
* @param hue - Hue in degrees.
|
|
981
|
+
* @return Hex color, or null when the color is outside the sRGB gamut.
|
|
874
982
|
*/
|
|
875
|
-
const
|
|
983
|
+
const oklchToHex = (lightness, chroma, hue) => {
|
|
984
|
+
const a = chroma * Math.cos(hue * DEGREES);
|
|
985
|
+
const b = chroma * Math.sin(hue * DEGREES);
|
|
986
|
+
const l = (lightness + .3963377774 * a + .2158037573 * b) ** 3;
|
|
987
|
+
const m = (lightness - .1055613458 * a - .0638541728 * b) ** 3;
|
|
988
|
+
const s = (lightness - .0894841775 * a - 1.291485548 * b) ** 3;
|
|
989
|
+
const linear = [
|
|
990
|
+
4.0767416621 * l - 3.3077633306 * m + .2309645873 * s,
|
|
991
|
+
-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s,
|
|
992
|
+
-.0041960863 * l - .7034186147 * m + 1.707614701 * s
|
|
993
|
+
];
|
|
994
|
+
if (linear.some((channel) => channel < -1e-4 || channel > 1.0001)) return null;
|
|
995
|
+
return linearToHex(linear);
|
|
996
|
+
};
|
|
876
997
|
/**
|
|
877
|
-
*
|
|
998
|
+
* OKLCH chroma and hue of a hex color.
|
|
999
|
+
*
|
|
1000
|
+
* @param hex - Six-digit hex color.
|
|
1001
|
+
* @return Chroma, and hue in degrees from 0 to 360.
|
|
1002
|
+
* @throws {Error} if hex string is malformed
|
|
878
1003
|
*/
|
|
879
|
-
const
|
|
1004
|
+
const hexToOklch = (hex) => {
|
|
1005
|
+
const [r, g, b] = hexToLinear(hex);
|
|
1006
|
+
const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
|
|
1007
|
+
const m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
|
|
1008
|
+
const s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
|
|
1009
|
+
const a = 1.9779984951 * l - 2.428592205 * m + .4505937099 * s;
|
|
1010
|
+
const bAxis = .0259040371 * l + .7827717662 * m - .808675766 * s;
|
|
1011
|
+
return {
|
|
1012
|
+
chroma: Math.hypot(a, bAxis),
|
|
1013
|
+
hue: (Math.atan2(bAxis, a) / DEGREES + 360) % 360
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
880
1016
|
/**
|
|
881
|
-
*
|
|
1017
|
+
* WCAG contrast ratio between two relative luminances.
|
|
1018
|
+
*
|
|
1019
|
+
* @param first - First relative luminance.
|
|
1020
|
+
* @param second - Second relative luminance.
|
|
1021
|
+
* @return Ratio from 1 to 21.
|
|
882
1022
|
*/
|
|
883
|
-
const
|
|
1023
|
+
const luminanceContrastRatio = (first, second) => (Math.max(first, second) + .05) / (Math.min(first, second) + .05);
|
|
884
1024
|
/**
|
|
885
|
-
*
|
|
1025
|
+
* WCAG contrast ratio between two hex colors.
|
|
886
1026
|
*
|
|
887
|
-
* @param
|
|
888
|
-
* @param
|
|
889
|
-
* @return
|
|
1027
|
+
* @param first - First hex color.
|
|
1028
|
+
* @param second - Second hex color.
|
|
1029
|
+
* @return Ratio from 1 to 21.
|
|
1030
|
+
* @throws {Error} if either hex string is malformed
|
|
890
1031
|
*/
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1032
|
+
const contrastRatio = (first, second) => luminanceContrastRatio(relativeLuminance(first), relativeLuminance(second));
|
|
1033
|
+
/** WCAG 1.4.3 text contrast, which a label drawn on a fill needs from at least one label color. */
|
|
1034
|
+
const MIN_LABEL_CONTRAST = 4.5;
|
|
1035
|
+
/** Degrees of hue one unit of OKLCH chroma is worth when ranking candidates by hue. */
|
|
1036
|
+
const CHROMA_WEIGHT = 300;
|
|
1037
|
+
/** ΔE00 one unit of OKLCH chroma is worth when ranking candidates by distance. */
|
|
1038
|
+
const FAR_CHROMA_WEIGHT = 100;
|
|
1039
|
+
const toCandidate = (hex, oklch = hexToOklch(hex)) => ({
|
|
1040
|
+
hex,
|
|
1041
|
+
views: hexToViews(hex),
|
|
1042
|
+
luminance: relativeLuminance(hex),
|
|
1043
|
+
hue: oklch.hue,
|
|
1044
|
+
chroma: oklch.chroma
|
|
1045
|
+
});
|
|
1046
|
+
const rank = (candidate, distance, anchorHue) => anchorHue === null ? -(distance + candidate.chroma * FAR_CHROMA_WEIGHT) : (candidate.hue - anchorHue + 360) % 360 - candidate.chroma * CHROMA_WEIGHT;
|
|
1047
|
+
const LIGHTNESS_STEPS = Array.from({ length: 13 }, (_, step) => .45 + step * .025);
|
|
1048
|
+
const CHROMA_STEPS = [
|
|
1049
|
+
.12,
|
|
1050
|
+
.15,
|
|
1051
|
+
.18,
|
|
1052
|
+
.21
|
|
1053
|
+
];
|
|
1054
|
+
const HUE_STEPS = Array.from({ length: 72 }, (_, step) => step * 5);
|
|
1055
|
+
let candidateGrid = null;
|
|
1056
|
+
const getCandidateGrid = () => {
|
|
1057
|
+
if (!candidateGrid) {
|
|
1058
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1059
|
+
candidateGrid = [];
|
|
1060
|
+
for (const lightness of LIGHTNESS_STEPS) for (const chroma of CHROMA_STEPS) for (const hue of HUE_STEPS) {
|
|
1061
|
+
const hex = oklchToHex(lightness, chroma, hue);
|
|
1062
|
+
if (hex && !seen.has(hex)) {
|
|
1063
|
+
seen.add(hex);
|
|
1064
|
+
candidateGrid.push(toCandidate(hex, {
|
|
1065
|
+
hue,
|
|
1066
|
+
chroma
|
|
1067
|
+
}));
|
|
910
1068
|
}
|
|
911
|
-
const expandedRange = Math.max(hueRange * HUE_RANGE_EXPANSION_FACTOR, MIN_HUE_RANGE_DEGREES);
|
|
912
|
-
hue = (minHue + maxHue) / 2 - expandedRange / 2 + hue / FULL_HUE_ROTATION_DEGREES * expandedRange;
|
|
913
|
-
hue = (hue % FULL_HUE_ROTATION_DEGREES + FULL_HUE_ROTATION_DEGREES) % FULL_HUE_ROTATION_DEGREES;
|
|
914
1069
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1070
|
+
}
|
|
1071
|
+
return candidateGrid;
|
|
1072
|
+
};
|
|
1073
|
+
const legiblePools = /* @__PURE__ */ new Map();
|
|
1074
|
+
const legibleCandidatesFor = (rawBackground, labelColors) => {
|
|
1075
|
+
const background = rawBackground.toLowerCase();
|
|
1076
|
+
const key = [background, ...labelColors.map((hex) => hex.toLowerCase())].join("|");
|
|
1077
|
+
let pool = legiblePools.get(key);
|
|
1078
|
+
if (!pool) {
|
|
1079
|
+
const backgroundLuminance = relativeLuminance(background);
|
|
1080
|
+
const labelLuminances = labelColors.map(relativeLuminance);
|
|
1081
|
+
const onBackground = getCandidateGrid().filter((candidate) => luminanceContrastRatio(candidate.luminance, backgroundLuminance) >= 3);
|
|
1082
|
+
const underLabels = onBackground.filter((candidate) => labelLuminances.some((labelLuminance) => luminanceContrastRatio(candidate.luminance, labelLuminance) >= MIN_LABEL_CONTRAST));
|
|
1083
|
+
pool = underLabels.length > 0 ? underLabels : onBackground;
|
|
1084
|
+
legiblePools.set(key, pool);
|
|
1085
|
+
}
|
|
1086
|
+
return pool;
|
|
1087
|
+
};
|
|
1088
|
+
/**
|
|
1089
|
+
* Build a tracker for a candidate pool, seeding each unused candidate's nearest distance against
|
|
1090
|
+
* every color already in the palette.
|
|
1091
|
+
*
|
|
1092
|
+
* @param pool - Candidates to track, in selection-priority order.
|
|
1093
|
+
* @param palette - Colors already placed.
|
|
1094
|
+
* @param usedHexes - Hex values already placed; a used candidate's distance is never read.
|
|
1095
|
+
* @return The tracker.
|
|
1096
|
+
*/
|
|
1097
|
+
const buildTracker = (pool, palette, usedHexes) => {
|
|
1098
|
+
const nearest = new Float64Array(pool.length).fill(Infinity);
|
|
1099
|
+
for (let i = 0; i < pool.length; i++) {
|
|
1100
|
+
if (usedHexes.has(pool[i].hex)) continue;
|
|
1101
|
+
for (const color of palette) {
|
|
1102
|
+
const distance = viewDistance(pool[i].views, color.views);
|
|
1103
|
+
if (distance < nearest[i]) nearest[i] = distance;
|
|
926
1104
|
}
|
|
927
|
-
if (isSufficientlyDifferent) return (0, _visx_vendor_d3_color.hsl)(Math.round(hue), saturation / 100, lightness / 100).formatHex();
|
|
928
1105
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1106
|
+
return {
|
|
1107
|
+
pool,
|
|
1108
|
+
nearest
|
|
1109
|
+
};
|
|
933
1110
|
};
|
|
934
1111
|
/**
|
|
935
|
-
*
|
|
1112
|
+
* Fold one newly placed color into a tracker's nearest distances.
|
|
936
1113
|
*
|
|
937
|
-
* @param
|
|
938
|
-
* @
|
|
1114
|
+
* @param tracker - Tracker to update.
|
|
1115
|
+
* @param addedColor - Color just added to the palette.
|
|
1116
|
+
* @param usedHexes - Hex values already placed; a used candidate's distance is never read.
|
|
939
1117
|
*/
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
1118
|
+
const updateTracker = (tracker, addedColor, usedHexes) => {
|
|
1119
|
+
for (let i = 0; i < tracker.pool.length; i++) {
|
|
1120
|
+
if (usedHexes.has(tracker.pool[i].hex)) continue;
|
|
1121
|
+
const distance = viewDistance(tracker.pool[i].views, addedColor.views);
|
|
1122
|
+
if (distance < tracker.nearest[i]) tracker.nearest[i] = distance;
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
/**
|
|
1126
|
+
* Pick the next color: the best-ranked unused candidate that clears `PREFERRED_SEPARATION` from
|
|
1127
|
+
* every earlier color; otherwise the farthest candidate.
|
|
1128
|
+
*
|
|
1129
|
+
* @param tracker - Tracker to read.
|
|
1130
|
+
* @param usedHexes - Hex values already placed; a used candidate is never picked.
|
|
1131
|
+
* @param anchorHue - Hue to walk the wheel from, or null to rank by distance.
|
|
1132
|
+
* @return The picked candidate, or null if every candidate in the pool is used.
|
|
1133
|
+
*/
|
|
1134
|
+
const pickNext = (tracker, usedHexes, anchorHue) => {
|
|
1135
|
+
let farthestIndex = -1;
|
|
1136
|
+
let farthestDistance = -Infinity;
|
|
1137
|
+
let preferredIndex = -1;
|
|
1138
|
+
let preferredRank = Infinity;
|
|
1139
|
+
for (let i = 0; i < tracker.pool.length; i++) {
|
|
1140
|
+
if (usedHexes.has(tracker.pool[i].hex)) continue;
|
|
1141
|
+
const distance = tracker.nearest[i];
|
|
1142
|
+
if (distance > farthestDistance) {
|
|
1143
|
+
farthestDistance = distance;
|
|
1144
|
+
farthestIndex = i;
|
|
1145
|
+
}
|
|
1146
|
+
if (distance >= 12) {
|
|
1147
|
+
const candidateRank = rank(tracker.pool[i], distance, anchorHue);
|
|
1148
|
+
if (candidateRank < preferredRank || candidateRank === preferredRank && distance > tracker.nearest[preferredIndex]) {
|
|
1149
|
+
preferredRank = candidateRank;
|
|
1150
|
+
preferredIndex = i;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const index = preferredIndex === -1 ? farthestIndex : preferredIndex;
|
|
1155
|
+
return index === -1 ? null : tracker.pool[index];
|
|
1156
|
+
};
|
|
1157
|
+
const generators = /* @__PURE__ */ new Map();
|
|
1158
|
+
const MAX_CACHED_GENERATORS = 32;
|
|
1159
|
+
/**
|
|
1160
|
+
* Build the series palette: the seeds, then colors that stay apart from every earlier color in
|
|
1161
|
+
* normal vision and under deuteranopia and protanopia.
|
|
1162
|
+
*
|
|
1163
|
+
* @param seeds - Resolved hex palette slots, in slot order.
|
|
1164
|
+
* @param background - Resolved hex chart background.
|
|
1165
|
+
* @param labelColors - Resolved hex label colors that may be drawn on a fill; each generated color reaches `MIN_LABEL_CONTRAST` with one of them.
|
|
1166
|
+
* @return The color at a palette index.
|
|
1167
|
+
*/
|
|
1168
|
+
const createPaletteGenerator = (seeds, background, labelColors = []) => {
|
|
1169
|
+
const normalizedSeeds = seeds.map((hex) => hex.toLowerCase());
|
|
1170
|
+
const normalizedBackground = background.toLowerCase();
|
|
1171
|
+
const normalizedLabels = labelColors.map((hex) => hex.toLowerCase());
|
|
1172
|
+
const key = `${normalizedSeeds.join(",")}|${normalizedBackground}|${normalizedLabels.join(",")}`;
|
|
1173
|
+
let colorAt = generators.get(key);
|
|
1174
|
+
if (!colorAt) {
|
|
1175
|
+
if (generators.size >= MAX_CACHED_GENERATORS) generators.delete(generators.keys().next().value);
|
|
1176
|
+
colorAt = buildPaletteGenerator(normalizedSeeds, normalizedBackground, normalizedLabels);
|
|
1177
|
+
generators.set(key, colorAt);
|
|
1178
|
+
}
|
|
1179
|
+
return colorAt;
|
|
1180
|
+
};
|
|
1181
|
+
const buildPaletteGenerator = (normalizedSeeds, background, labelColors) => {
|
|
1182
|
+
const palette = normalizedSeeds.map((hex) => toCandidate(hex));
|
|
1183
|
+
const usedHexes = new Set(normalizedSeeds);
|
|
1184
|
+
const anchorHue = palette.length > 0 ? palette[0].hue : null;
|
|
1185
|
+
let legibleTracker = null;
|
|
1186
|
+
let legibleExhausted = false;
|
|
1187
|
+
let gridTracker = null;
|
|
1188
|
+
let gridExhausted = false;
|
|
1189
|
+
let placedBeforeExhaustion = 0;
|
|
1190
|
+
let repeatCount = 0;
|
|
1191
|
+
const nextColor = () => {
|
|
1192
|
+
const walkHue = (palette.length - normalizedSeeds.length) % 2 === 1 ? anchorHue : null;
|
|
1193
|
+
if (!legibleExhausted) {
|
|
1194
|
+
legibleTracker ??= buildTracker(legibleCandidatesFor(background, labelColors), palette, usedHexes);
|
|
1195
|
+
const picked = pickNext(legibleTracker, usedHexes, walkHue);
|
|
1196
|
+
if (picked) return picked;
|
|
1197
|
+
legibleExhausted = true;
|
|
1198
|
+
}
|
|
1199
|
+
if (!gridExhausted) {
|
|
1200
|
+
gridTracker ??= buildTracker(getCandidateGrid(), palette, usedHexes);
|
|
1201
|
+
const picked = pickNext(gridTracker, usedHexes, walkHue);
|
|
1202
|
+
if (picked) return picked;
|
|
1203
|
+
gridExhausted = true;
|
|
1204
|
+
placedBeforeExhaustion = palette.length;
|
|
1205
|
+
}
|
|
1206
|
+
const repeated = palette[repeatCount % placedBeforeExhaustion];
|
|
1207
|
+
repeatCount++;
|
|
1208
|
+
return repeated;
|
|
1209
|
+
};
|
|
1210
|
+
return (index) => {
|
|
1211
|
+
if (index < palette.length) return palette[index].hex;
|
|
1212
|
+
while (palette.length <= index) {
|
|
1213
|
+
const next = nextColor();
|
|
1214
|
+
usedHexes.add(next.hex);
|
|
1215
|
+
palette.push(next);
|
|
1216
|
+
if (legibleTracker && !legibleExhausted) updateTracker(legibleTracker, next, usedHexes);
|
|
1217
|
+
if (gridTracker && !gridExhausted) updateTracker(gridTracker, next, usedHexes);
|
|
1218
|
+
}
|
|
1219
|
+
return palette[index].hex;
|
|
1220
|
+
};
|
|
1221
|
+
};
|
|
944
1222
|
//#endregion
|
|
945
1223
|
//#region src/providers/chart-context/themes.ts
|
|
946
1224
|
/**
|
|
@@ -990,6 +1268,13 @@ const defaultTheme = {
|
|
|
990
1268
|
};
|
|
991
1269
|
//#endregion
|
|
992
1270
|
//#region src/providers/chart-context/global-charts-provider.tsx
|
|
1271
|
+
const resolveOpaqueHex = (pointer, element) => {
|
|
1272
|
+
const raw = resolveCssVariable(pointer, element);
|
|
1273
|
+
if (!raw || (0, _visx_vendor_d3_color.color)(raw)?.opacity !== 1) return null;
|
|
1274
|
+
const hex = normalizeColorToHex(pointer, element, resolveCssVariable);
|
|
1275
|
+
return isValidHexColor(hex) ? hex : null;
|
|
1276
|
+
};
|
|
1277
|
+
const PLACEHOLDER_LABEL_COLORS = [LABEL_FALLBACK, LABEL_INVERSE_FALLBACK];
|
|
993
1278
|
const GlobalChartsContext = (0, react.createContext)(null);
|
|
994
1279
|
const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
995
1280
|
const [charts, setCharts] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
@@ -1004,50 +1289,32 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
|
1004
1289
|
const providerTheme = (0, react.useMemo)(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
|
|
1005
1290
|
const [colorCache, setColorCache] = (0, react.useState)(() => ({
|
|
1006
1291
|
colors: [],
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
maxHue: 0
|
|
1292
|
+
background: BACKGROUND_FALLBACK,
|
|
1293
|
+
labelColors: PLACEHOLDER_LABEL_COLORS,
|
|
1294
|
+
colorAt: createPaletteGenerator([SERIES_SLOT_1_FALLBACK], BACKGROUND_FALLBACK, PLACEHOLDER_LABEL_COLORS)
|
|
1011
1295
|
}));
|
|
1012
1296
|
const [isColorPaletteResolved, setIsColorPaletteResolved] = (0, react.useState)(false);
|
|
1013
1297
|
(0, react.useLayoutEffect)(() => {
|
|
1014
1298
|
setIsColorPaletteResolved(false);
|
|
1015
1299
|
const resolvedColors = [];
|
|
1016
|
-
const hues = [];
|
|
1017
|
-
const existingHslColors = [];
|
|
1018
|
-
let minHue = 360;
|
|
1019
|
-
let maxHue = 0;
|
|
1020
1300
|
for (const color of SERIES_PALETTE_POINTERS) {
|
|
1021
1301
|
const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
|
|
1022
|
-
if (normalizedColor.
|
|
1023
|
-
resolvedColors.push(normalizedColor);
|
|
1024
|
-
const hslColor = (0, _visx_vendor_d3_color.hsl)(normalizedColor);
|
|
1025
|
-
if (!isNaN(hslColor.h)) {
|
|
1026
|
-
const hslTuple = [
|
|
1027
|
-
hslColor.h,
|
|
1028
|
-
hslColor.s * 100,
|
|
1029
|
-
hslColor.l * 100
|
|
1030
|
-
];
|
|
1031
|
-
hues.push(hslTuple[0]);
|
|
1032
|
-
existingHslColors.push(hslTuple);
|
|
1033
|
-
minHue = Math.min(minHue, hslTuple[0]);
|
|
1034
|
-
maxHue = Math.max(maxHue, hslTuple[0]);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1302
|
+
if (isValidHexColor(normalizedColor)) resolvedColors.push(normalizedColor);
|
|
1037
1303
|
}
|
|
1304
|
+
const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, wrapperRef.current) ?? "#ffffff";
|
|
1305
|
+
const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, wrapperRef.current)).filter((hex) => hex !== null);
|
|
1038
1306
|
setColorCache({
|
|
1039
1307
|
colors: resolvedColors,
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
maxHue
|
|
1308
|
+
background: backgroundHex,
|
|
1309
|
+
labelColors,
|
|
1310
|
+
colorAt: createPaletteGenerator(resolvedColors, backgroundHex, labelColors)
|
|
1044
1311
|
});
|
|
1045
1312
|
}, []);
|
|
1046
1313
|
(0, react.useEffect)(() => {
|
|
1047
1314
|
if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
|
|
1048
1315
|
}, [colorCache]);
|
|
1049
1316
|
const [groupToColorMap, setGroupToColorMap] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
1050
|
-
const paletteKey = colorCache.colors.join(",")
|
|
1317
|
+
const paletteKey = `${colorCache.colors.join(",")}|${colorCache.background}|${colorCache.labelColors.join(",")}`;
|
|
1051
1318
|
(0, react.useEffect)(() => {
|
|
1052
1319
|
setGroupToColorMap(/* @__PURE__ */ new Map());
|
|
1053
1320
|
}, [paletteKey]);
|
|
@@ -1070,11 +1337,11 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
|
1070
1337
|
const existing = groupToColorMap.get(group);
|
|
1071
1338
|
if (existing) return existing;
|
|
1072
1339
|
const assignedCount = groupToColorMap.size;
|
|
1073
|
-
const color =
|
|
1340
|
+
const color = colorCache.colorAt(assignedCount);
|
|
1074
1341
|
groupToColorMap.set(group, color);
|
|
1075
1342
|
return color;
|
|
1076
1343
|
}
|
|
1077
|
-
return
|
|
1344
|
+
return colorCache.colorAt(index);
|
|
1078
1345
|
}, [colorCache, groupToColorMap]);
|
|
1079
1346
|
const getElementStyles = (0, react.useCallback)(({ data, index, overrideColor, legendShape }) => {
|
|
1080
1347
|
const isSeriesData = data && typeof data === "object" && "data" in data;
|
|
@@ -1240,39 +1507,6 @@ const useDeepMemo = (value) => {
|
|
|
1240
1507
|
return ref.current;
|
|
1241
1508
|
};
|
|
1242
1509
|
//#endregion
|
|
1243
|
-
//#region src/providers/chart-context/private/catalog-pointers.ts
|
|
1244
|
-
const CATALOG_POINTERS = {
|
|
1245
|
-
background: "var(--a8c-charts-color-background, #fff)",
|
|
1246
|
-
labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
1247
|
-
grid: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
1248
|
-
axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
|
|
1249
|
-
tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
|
|
1250
|
-
axisY: "var(--a8c-charts-color-axis-y, none)",
|
|
1251
|
-
tickY: "var(--a8c-charts-color-tick-y, none)",
|
|
1252
|
-
annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
1253
|
-
surface: "var(--a8c-charts-color-surface, #fff)",
|
|
1254
|
-
surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
1255
|
-
trendUp: "var(--a8c-charts-color-trend-up, #008030)",
|
|
1256
|
-
trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
|
|
1257
|
-
series: SERIES_PALETTE_POINTERS
|
|
1258
|
-
};
|
|
1259
|
-
/**
|
|
1260
|
-
* The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
|
|
1261
|
-
* rides along as the base the theme and the per-datum styles merge onto.
|
|
1262
|
-
*/
|
|
1263
|
-
const ANNOTATION_POINTERS = {
|
|
1264
|
-
label: {
|
|
1265
|
-
anchorLineStroke: CATALOG_POINTERS.annotation,
|
|
1266
|
-
backgroundFill: CATALOG_POINTERS.surface
|
|
1267
|
-
},
|
|
1268
|
-
connector: { stroke: CATALOG_POINTERS.annotation },
|
|
1269
|
-
circleSubject: {
|
|
1270
|
-
stroke: "transparent",
|
|
1271
|
-
fill: CATALOG_POINTERS.annotation,
|
|
1272
|
-
radius: 5
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
//#endregion
|
|
1276
1510
|
//#region src/providers/chart-scope/use-chart-scope-element.ts
|
|
1277
1511
|
/**
|
|
1278
1512
|
* Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
|
|
@@ -2750,18 +2984,41 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2750
2984
|
getChartRoot,
|
|
2751
2985
|
focusWithoutScrollIfNeeded
|
|
2752
2986
|
]);
|
|
2987
|
+
const onChartFocus = (0, react.useCallback)((event) => {
|
|
2988
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
2989
|
+
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2990
|
+
}, [
|
|
2991
|
+
isNavigating,
|
|
2992
|
+
selectedIndex,
|
|
2993
|
+
setSelectedIndex
|
|
2994
|
+
]);
|
|
2995
|
+
const pointerIndex = (0, react.useRef)(void 0);
|
|
2996
|
+
const onChartPointerMove = (0, react.useCallback)((index) => {
|
|
2997
|
+
if (selectedIndex === void 0) {
|
|
2998
|
+
if (pointerIndex.current !== void 0) pointerIndex.current = index;
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const root = getChartRoot();
|
|
3002
|
+
const activeElement = root?.ownerDocument.activeElement;
|
|
3003
|
+
if (activeElement && root.contains(activeElement)) {
|
|
3004
|
+
pointerIndex.current = index;
|
|
3005
|
+
focusWithoutScrollIfNeeded(root);
|
|
3006
|
+
}
|
|
3007
|
+
setSelectedIndex(void 0);
|
|
3008
|
+
setIsNavigating(false);
|
|
3009
|
+
}, [
|
|
3010
|
+
selectedIndex,
|
|
3011
|
+
setSelectedIndex,
|
|
3012
|
+
setIsNavigating,
|
|
3013
|
+
getChartRoot,
|
|
3014
|
+
focusWithoutScrollIfNeeded
|
|
3015
|
+
]);
|
|
2753
3016
|
return {
|
|
2754
3017
|
tooltipRef,
|
|
2755
|
-
onChartFocus
|
|
2756
|
-
|
|
2757
|
-
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2758
|
-
}, [
|
|
2759
|
-
isNavigating,
|
|
2760
|
-
selectedIndex,
|
|
2761
|
-
setSelectedIndex
|
|
2762
|
-
]),
|
|
2763
|
-
onChartBlur: (0, react.useCallback)(() => {
|
|
3018
|
+
onChartFocus,
|
|
3019
|
+
onChartBlur: (0, react.useCallback)((event) => {
|
|
2764
3020
|
setIsNavigating(false);
|
|
3021
|
+
if (!event.currentTarget.contains(event.relatedTarget)) pointerIndex.current = void 0;
|
|
2765
3022
|
}, [setIsNavigating]),
|
|
2766
3023
|
onChartKeyDown: (0, react.useCallback)((event) => {
|
|
2767
3024
|
if (event.key === "Tab" || event.key === "Escape") {
|
|
@@ -2769,10 +3026,12 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2769
3026
|
focusWithoutScrollIfNeeded(getChartRoot());
|
|
2770
3027
|
setSelectedIndex(void 0);
|
|
2771
3028
|
setIsNavigating(false);
|
|
3029
|
+
pointerIndex.current = void 0;
|
|
2772
3030
|
return;
|
|
2773
3031
|
}
|
|
2774
3032
|
if (totalPoints === 0) return;
|
|
2775
|
-
const currentSelectedIndex = selectedIndex
|
|
3033
|
+
const currentSelectedIndex = selectedIndex ?? pointerIndex.current ?? -1;
|
|
3034
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") pointerIndex.current = void 0;
|
|
2776
3035
|
if (event.key === "ArrowRight") {
|
|
2777
3036
|
event.preventDefault();
|
|
2778
3037
|
setIsNavigating(true);
|
|
@@ -2793,7 +3052,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2793
3052
|
getChartRoot,
|
|
2794
3053
|
focusWithoutScrollIfNeeded,
|
|
2795
3054
|
onActivate
|
|
2796
|
-
])
|
|
3055
|
+
]),
|
|
3056
|
+
onChartPointerMove
|
|
2797
3057
|
};
|
|
2798
3058
|
};
|
|
2799
3059
|
//#endregion
|
|
@@ -2932,7 +3192,7 @@ function useChartChildren(children, chartType) {
|
|
|
2932
3192
|
var chart_layout_module_default = { "chart-layout__content": "a8ccharts-fpNVAq-chart-layout__content" };
|
|
2933
3193
|
//#endregion
|
|
2934
3194
|
//#region src/charts/private/chart-layout/chart-layout.tsx
|
|
2935
|
-
const ChartLayout = ({ legendPosition, legendElement, legendChildren, children, trailingContent, onContentHeightChange, gap, className, style, "data-testid": dataTestId, "data-chart-id": dataChartId }) => {
|
|
3195
|
+
const ChartLayout = ({ legendPosition, legendElement, legendChildren, children, trailingContent, onContentHeightChange, gap, className, rootRef, style, "data-testid": dataTestId, "data-chart-id": dataChartId }) => {
|
|
2936
3196
|
const [contentRef, contentWidth, contentHeight] = useElementSize();
|
|
2937
3197
|
const isRenderProp = typeof children === "function";
|
|
2938
3198
|
const isMeasured = contentHeight > 0;
|
|
@@ -2951,6 +3211,7 @@ const ChartLayout = ({ legendPosition, legendElement, legendChildren, children,
|
|
|
2951
3211
|
isMeasured
|
|
2952
3212
|
}) : children;
|
|
2953
3213
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
|
|
3214
|
+
ref: rootRef,
|
|
2954
3215
|
direction: "column",
|
|
2955
3216
|
gap,
|
|
2956
3217
|
className,
|
|
@@ -5661,9 +5922,8 @@ const createGroupScale = (keys, bandwidth, padding) => (0, _visx_scale.scaleBand
|
|
|
5661
5922
|
//#endregion
|
|
5662
5923
|
//#region src/charts/bar-chart/private/comparison-bars-geometry.ts
|
|
5663
5924
|
/**
|
|
5664
|
-
*
|
|
5665
|
-
*
|
|
5666
|
-
* sit on the same baseline as primary bars.
|
|
5925
|
+
* Mirrors visx's getScaleBaseline so comparison shadows and primary bars
|
|
5926
|
+
* share the same baseline, including out-of-domain zero values.
|
|
5667
5927
|
*
|
|
5668
5928
|
* @param {ValueScale} scale - The continuous value scale.
|
|
5669
5929
|
* @return {number} The baseline output position in pixels.
|
|
@@ -5674,7 +5934,7 @@ function getValueScaleBaseline(scale) {
|
|
|
5674
5934
|
const maybeZero = scale(0);
|
|
5675
5935
|
const [minOutput, maxOutput] = isDescending ? [b, a] : [a, b];
|
|
5676
5936
|
if (isDescending) return Number.isFinite(maybeZero) ? Math.min(Math.max(minOutput, maybeZero), maxOutput) : maxOutput;
|
|
5677
|
-
return Number.isFinite(maybeZero) ? Math.
|
|
5937
|
+
return Number.isFinite(maybeZero) ? Math.max(maybeZero, minOutput) : minOutput;
|
|
5678
5938
|
}
|
|
5679
5939
|
/**
|
|
5680
5940
|
* Compute the rect for a comparison "shadow" bar, centered on the paired
|
|
@@ -5710,6 +5970,51 @@ function computeComparisonRect(params) {
|
|
|
5710
5970
|
height: valueLength
|
|
5711
5971
|
};
|
|
5712
5972
|
}
|
|
5973
|
+
//#endregion
|
|
5974
|
+
//#region src/charts/bar-chart/private/classified-bar-series.tsx
|
|
5975
|
+
const ClassifiedBarSeries = ({ barClassName, primaryKeys, groupPadding, ...props }) => {
|
|
5976
|
+
const { xScale, yScale, horizontal } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
5977
|
+
const bandScale = horizontal ? yScale : xScale;
|
|
5978
|
+
const valueScale = horizontal ? xScale : yScale;
|
|
5979
|
+
const groupScale = (0, react.useMemo)(() => createGroupScale(primaryKeys, bandScale?.bandwidth?.() ?? 0, groupPadding), [
|
|
5980
|
+
primaryKeys,
|
|
5981
|
+
bandScale,
|
|
5982
|
+
groupPadding
|
|
5983
|
+
]);
|
|
5984
|
+
const renderGlyph = (0, react.useCallback)(({ datum, x, y, color, onBlur, onFocus, onPointerMove, onPointerOut, onPointerUp }) => {
|
|
5985
|
+
if (!bandScale?.bandwidth || !valueScale) return null;
|
|
5986
|
+
const baseline = getValueScaleBaseline(valueScale);
|
|
5987
|
+
const position = Number(bandScale((horizontal ? props.yAccessor : props.xAccessor)(datum))) + (groupScale(props.dataKey) ?? 0);
|
|
5988
|
+
const value = horizontal ? x : y;
|
|
5989
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
5990
|
+
className: (0, clsx.default)("visx-bar", barClassName(datum)),
|
|
5991
|
+
tabIndex: onFocus || onBlur ? 0 : void 0,
|
|
5992
|
+
onBlur,
|
|
5993
|
+
onFocus,
|
|
5994
|
+
onPointerMove,
|
|
5995
|
+
onPointerOut,
|
|
5996
|
+
onPointerUp,
|
|
5997
|
+
x: horizontal ? baseline + Math.min(0, value - baseline) : position,
|
|
5998
|
+
y: horizontal ? position : baseline + Math.min(0, value - baseline),
|
|
5999
|
+
width: horizontal ? Math.abs(value - baseline) : groupScale.bandwidth(),
|
|
6000
|
+
height: horizontal ? groupScale.bandwidth() : Math.abs(value - baseline),
|
|
6001
|
+
fill: color
|
|
6002
|
+
});
|
|
6003
|
+
}, [
|
|
6004
|
+
bandScale,
|
|
6005
|
+
valueScale,
|
|
6006
|
+
horizontal,
|
|
6007
|
+
groupScale,
|
|
6008
|
+
props.dataKey,
|
|
6009
|
+
props.xAccessor,
|
|
6010
|
+
props.yAccessor,
|
|
6011
|
+
barClassName
|
|
6012
|
+
]);
|
|
6013
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.GlyphSeries, {
|
|
6014
|
+
...props,
|
|
6015
|
+
renderGlyph
|
|
6016
|
+
});
|
|
6017
|
+
};
|
|
5713
6018
|
/**
|
|
5714
6019
|
* Upper clamp on the computed group padding, so bars can never collapse to zero width
|
|
5715
6020
|
* even at very large `widthFactor` values.
|
|
@@ -5874,9 +6179,10 @@ function nearestBandIndex(data, accessor, scale, position) {
|
|
|
5874
6179
|
* @param root0.withTooltips - Whether pointer events update the tooltip.
|
|
5875
6180
|
* @param root0.onPointerDown - Receives the corrected pointer-down datum.
|
|
5876
6181
|
* @param root0.onPointerUp - Receives the corrected pointer-up datum.
|
|
6182
|
+
* @param root0.onPointerMove - Receives the corrected datum under a moving pointer.
|
|
5877
6183
|
* @return No visual content.
|
|
5878
6184
|
*/
|
|
5879
|
-
function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp }) {
|
|
6185
|
+
function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp, onPointerMove }) {
|
|
5880
6186
|
const { xScale, yScale, dataRegistry, horizontal } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
5881
6187
|
const { showTooltip } = (0, react.useContext)(_visx_xychart.TooltipContext);
|
|
5882
6188
|
const scale = horizontal ? yScale : xScale;
|
|
@@ -5921,20 +6227,22 @@ function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointe
|
|
|
5921
6227
|
]);
|
|
5922
6228
|
const handlePointer = (0, react.useCallback)((params) => {
|
|
5923
6229
|
const selections = getSelections(params);
|
|
5924
|
-
if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
|
|
5925
|
-
const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
|
|
5926
|
-
if (params?.event.type === "pointermove" || !callback) return;
|
|
5927
6230
|
const nearest = selections.reduce((best, selection) => {
|
|
5928
6231
|
const distance = (value) => Math.hypot(value.distanceX, value.distanceY);
|
|
5929
6232
|
return !best || distance(selection) <= distance(best) ? selection : best;
|
|
5930
6233
|
}, void 0);
|
|
6234
|
+
if (params?.event.type === "pointermove" && nearest) onPointerMove?.(nearest);
|
|
6235
|
+
if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
|
|
6236
|
+
const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
|
|
6237
|
+
if (params?.event.type === "pointermove" || !callback) return;
|
|
5931
6238
|
if (nearest) callback(nearest);
|
|
5932
6239
|
}, [
|
|
5933
6240
|
getSelections,
|
|
5934
6241
|
withTooltips,
|
|
5935
6242
|
showTooltip,
|
|
5936
6243
|
onPointerDown,
|
|
5937
|
-
onPointerUp
|
|
6244
|
+
onPointerUp,
|
|
6245
|
+
onPointerMove
|
|
5938
6246
|
]);
|
|
5939
6247
|
(0, _visx_xychart.useEventEmitter)("pointermove", withTooltips ? handlePointer : void 0);
|
|
5940
6248
|
(0, _visx_xychart.useEventEmitter)("pointerdown", handlePointer);
|
|
@@ -5953,7 +6261,7 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
|
|
|
5953
6261
|
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
5954
6262
|
children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
|
|
5955
6263
|
});
|
|
5956
|
-
const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
|
|
6264
|
+
const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, tooltipStyle, barClassName, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
|
|
5957
6265
|
if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
|
|
5958
6266
|
const legendInteractive = legend.interactive ?? false;
|
|
5959
6267
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
@@ -6017,7 +6325,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6017
6325
|
});
|
|
6018
6326
|
}, [primaryEntries, onDatumActivate]);
|
|
6019
6327
|
const visibleSeriesKey = (0, react.useMemo)(() => JSON.stringify(primaryKeys), [primaryKeys]);
|
|
6020
|
-
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
|
|
6328
|
+
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown, onChartPointerMove } = useKeyboardNavigation({
|
|
6021
6329
|
selectedIndex,
|
|
6022
6330
|
setSelectedIndex,
|
|
6023
6331
|
isNavigating,
|
|
@@ -6027,6 +6335,10 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6027
6335
|
onActivate: activateSelectedBar,
|
|
6028
6336
|
visibleSeriesKey
|
|
6029
6337
|
});
|
|
6338
|
+
const handlePointerMove = (0, react.useCallback)(({ key, index }) => {
|
|
6339
|
+
const seriesIndex = primaryKeys.indexOf(key);
|
|
6340
|
+
if (seriesIndex >= 0) onChartPointerMove(index * primaryKeys.length + seriesIndex);
|
|
6341
|
+
}, [primaryKeys, onChartPointerMove]);
|
|
6030
6342
|
const comparisonEntries = (0, react.useMemo)(() => {
|
|
6031
6343
|
const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
|
|
6032
6344
|
label: series.label,
|
|
@@ -6323,7 +6635,19 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6323
6635
|
getElementStyles,
|
|
6324
6636
|
resolveFill: resolveComparisonFill
|
|
6325
6637
|
}),
|
|
6326
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6638
|
+
barClassName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
6639
|
+
className: "visx-bar-group",
|
|
6640
|
+
children: primaryEntries.map(({ series: seriesData, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClassifiedBarSeries, {
|
|
6641
|
+
dataKey: seriesData.label,
|
|
6642
|
+
data: seriesData.data,
|
|
6643
|
+
xAccessor: chartOptions.accessors.xAccessor,
|
|
6644
|
+
yAccessor: chartOptions.accessors.yAccessor,
|
|
6645
|
+
colorAccessor: getBarBackground(index),
|
|
6646
|
+
barClassName,
|
|
6647
|
+
primaryKeys,
|
|
6648
|
+
groupPadding
|
|
6649
|
+
}, seriesData.label))
|
|
6650
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.BarGroup, {
|
|
6327
6651
|
padding: groupPadding,
|
|
6328
6652
|
children: primaryEntries.map(({ series: seriesData, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.BarSeries, {
|
|
6329
6653
|
dataKey: seriesData?.label,
|
|
@@ -6338,7 +6662,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6338
6662
|
groupPadding,
|
|
6339
6663
|
withTooltips,
|
|
6340
6664
|
onPointerDown,
|
|
6341
|
-
onPointerUp
|
|
6665
|
+
onPointerUp,
|
|
6666
|
+
onPointerMove: handlePointerMove
|
|
6342
6667
|
}),
|
|
6343
6668
|
!allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
|
|
6344
6669
|
...wholeNumberTicksProps,
|
|
@@ -6353,6 +6678,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6353
6678
|
withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
|
|
6354
6679
|
tooltipPlacement,
|
|
6355
6680
|
tooltipAnchorTop,
|
|
6681
|
+
style: tooltipStyle,
|
|
6356
6682
|
detectBounds: true,
|
|
6357
6683
|
snapTooltipToDatumX: true,
|
|
6358
6684
|
snapTooltipToDatumY: true,
|
|
@@ -7019,12 +7345,13 @@ function normalizeGeoChartError(eventArgs) {
|
|
|
7019
7345
|
* @param props.height - Height of the chart in pixels
|
|
7020
7346
|
* @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
|
|
7021
7347
|
* @param props.resolution - Resolution level ('countries', 'provinces', or 'metros')
|
|
7348
|
+
* @param props.domain - Country code whose viewpoint sets the disputed borders
|
|
7022
7349
|
* @param props.onError - Optional callback for Google Charts errors
|
|
7023
7350
|
* @param props.className - Additional CSS class name for the chart container
|
|
7024
7351
|
* @param props.renderPlaceholder - Optional render function for the loading placeholder
|
|
7025
7352
|
* @return A React component displaying an interactive map with data visualization
|
|
7026
7353
|
*/
|
|
7027
|
-
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
|
|
7354
|
+
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", domain, onError, renderPlaceholder }) => {
|
|
7028
7355
|
const { getElementStyles } = useGlobalChartsContext();
|
|
7029
7356
|
const scopeElement = useChartScopeElement();
|
|
7030
7357
|
const containerRef = (0, react.useRef)(null);
|
|
@@ -7088,6 +7415,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
7088
7415
|
const options = (0, react.useMemo)(() => ({
|
|
7089
7416
|
...region !== "world" && { region },
|
|
7090
7417
|
...resolution !== "countries" && { resolution },
|
|
7418
|
+
...domain && { domain },
|
|
7091
7419
|
colorAxis: { colors: [lightColorHex, fullColorHex] },
|
|
7092
7420
|
backgroundColor: backgroundColorHex,
|
|
7093
7421
|
datalessRegionColor: defaultFillColorHex,
|
|
@@ -7101,6 +7429,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
7101
7429
|
}), [
|
|
7102
7430
|
region,
|
|
7103
7431
|
resolution,
|
|
7432
|
+
domain,
|
|
7104
7433
|
lightColorHex,
|
|
7105
7434
|
fullColorHex,
|
|
7106
7435
|
backgroundColorHex,
|
|
@@ -11394,6 +11723,7 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
|
|
|
11394
11723
|
var pie_chart_module_default = {
|
|
11395
11724
|
"pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
|
|
11396
11725
|
"pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
|
|
11726
|
+
"pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
|
|
11397
11727
|
"pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
|
|
11398
11728
|
"pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
|
|
11399
11729
|
};
|
|
@@ -11438,6 +11768,25 @@ const validateData$1 = (data) => {
|
|
|
11438
11768
|
};
|
|
11439
11769
|
};
|
|
11440
11770
|
/**
|
|
11771
|
+
* Whether a pie slice's label should use the dark `label` role instead of the default `label-inverse` role.
|
|
11772
|
+
*
|
|
11773
|
+
* A label plate always wins: the text sits on the plate, not the slice fill, so it never flips.
|
|
11774
|
+
* Otherwise the role that contrasts more with the resolved slice fill wins. `null` pointers (not
|
|
11775
|
+
* yet resolved, or labels off) keep the default inverse role.
|
|
11776
|
+
*
|
|
11777
|
+
* @param fill - The slice's resolved fill color.
|
|
11778
|
+
* @param pointers - The label pointers, resolved at the chart's own root element.
|
|
11779
|
+
* @return Whether the label needs dark text.
|
|
11780
|
+
*/
|
|
11781
|
+
const labelNeedsDarkText = (fill, pointers) => {
|
|
11782
|
+
if (!pointers || pointers.hasPlate) return false;
|
|
11783
|
+
const fillHex = normalizeColorToHex(fill);
|
|
11784
|
+
if (!isValidHexColor(fillHex) || !isValidHexColor(pointers.labelHex)) return false;
|
|
11785
|
+
if (pointers.isInverseSeeThrough) return true;
|
|
11786
|
+
if (!isValidHexColor(pointers.labelInverseHex)) return false;
|
|
11787
|
+
return contrastRatio(fillHex, pointers.labelHex) > contrastRatio(fillHex, pointers.labelInverseHex);
|
|
11788
|
+
};
|
|
11789
|
+
/**
|
|
11441
11790
|
* Renders a pie or donut chart using the provided data.
|
|
11442
11791
|
*
|
|
11443
11792
|
* @param {PieChartProps} props - Component props
|
|
@@ -11451,11 +11800,36 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11451
11800
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
|
|
11452
11801
|
const standaloneScopeClass = useStandaloneScopeClass();
|
|
11453
11802
|
const containerRef = (0, react.useRef)(null);
|
|
11803
|
+
const rootRef = (0, react.useRef)(null);
|
|
11804
|
+
const [labelPointers, setLabelPointers] = (0, react.useState)(null);
|
|
11454
11805
|
const onMouseLeave = (0, react.useCallback)(() => {
|
|
11455
11806
|
if (!withTooltips) return;
|
|
11456
11807
|
hideTooltip();
|
|
11457
11808
|
}, [withTooltips, hideTooltip]);
|
|
11458
|
-
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
11809
|
+
const { getElementStyles, isSeriesVisible, isColorPaletteResolved } = useGlobalChartsContext();
|
|
11810
|
+
const { isValid, message } = validateData$1(data);
|
|
11811
|
+
(0, react.useLayoutEffect)(() => {
|
|
11812
|
+
if (!showLabels || !rootRef.current) return;
|
|
11813
|
+
const resolve = createCssVariableResolver(rootRef.current);
|
|
11814
|
+
const rawLabelBackground = resolve(CATALOG_POINTERS.labelBackground);
|
|
11815
|
+
const rawLabel = resolve(CATALOG_POINTERS.label);
|
|
11816
|
+
const isLabelOpaque = rawLabel ? (0, _visx_vendor_d3_color.color)(rawLabel)?.opacity === 1 : false;
|
|
11817
|
+
const rawLabelInverse = resolve(CATALOG_POINTERS.labelInverse);
|
|
11818
|
+
const labelInverseColor = rawLabelInverse ? (0, _visx_vendor_d3_color.color)(rawLabelInverse) : null;
|
|
11819
|
+
const plateColor = rawLabelBackground ? (0, _visx_vendor_d3_color.color)(rawLabelBackground) : null;
|
|
11820
|
+
const next = {
|
|
11821
|
+
hasPlate: rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false,
|
|
11822
|
+
labelHex: isLabelOpaque ? normalizeColorToHex(CATALOG_POINTERS.label, null, resolve) : "",
|
|
11823
|
+
labelInverseHex: normalizeColorToHex(CATALOG_POINTERS.labelInverse, null, resolve),
|
|
11824
|
+
isInverseSeeThrough: labelInverseColor ? labelInverseColor.opacity < 1 : false
|
|
11825
|
+
};
|
|
11826
|
+
setLabelPointers((previous) => previous && previous.hasPlate === next.hasPlate && previous.labelHex === next.labelHex && previous.labelInverseHex === next.labelInverseHex && previous.isInverseSeeThrough === next.isInverseSeeThrough ? previous : next);
|
|
11827
|
+
}, [
|
|
11828
|
+
showLabels,
|
|
11829
|
+
className,
|
|
11830
|
+
isColorPaletteResolved,
|
|
11831
|
+
isValid
|
|
11832
|
+
]);
|
|
11459
11833
|
const dataWithPercentages = useDataWithPercentages(data);
|
|
11460
11834
|
const { visibleData, allSegmentsHidden, legendData } = useLegendVisibilityData({
|
|
11461
11835
|
data: dataWithPercentages,
|
|
@@ -11466,7 +11840,6 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11466
11840
|
showValues: true,
|
|
11467
11841
|
legendValueDisplay
|
|
11468
11842
|
}), [legendValueDisplay]));
|
|
11469
|
-
const { isValid, message } = validateData$1(data);
|
|
11470
11843
|
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(children, "PieChart");
|
|
11471
11844
|
const chartMetadata = (0, react.useMemo)(() => ({
|
|
11472
11845
|
thickness,
|
|
@@ -11528,6 +11901,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11528
11901
|
legendElement,
|
|
11529
11902
|
legendChildren,
|
|
11530
11903
|
gap,
|
|
11904
|
+
rootRef,
|
|
11531
11905
|
className: (0, clsx.default)("pie-chart", pie_chart_module_default["pie-chart"], { [pie_chart_module_default["pie-chart--responsive"]]: !propWidth && !propHeight }, className),
|
|
11532
11906
|
style: {
|
|
11533
11907
|
width: propWidth || void 0,
|
|
@@ -11589,9 +11963,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11589
11963
|
tooltipTop: event.clientY - bounds.top + tooltipOffsetY
|
|
11590
11964
|
});
|
|
11591
11965
|
};
|
|
11966
|
+
const fill = accessors.fill(arc.data);
|
|
11592
11967
|
const pathProps = {
|
|
11593
11968
|
d: pie.path(arc) || "",
|
|
11594
|
-
fill
|
|
11969
|
+
fill,
|
|
11595
11970
|
"data-testid": "pie-segment"
|
|
11596
11971
|
};
|
|
11597
11972
|
const groupProps = {};
|
|
@@ -11619,7 +11994,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11619
11994
|
ry: 4,
|
|
11620
11995
|
pointerEvents: "none"
|
|
11621
11996
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
11622
|
-
className: pie_chart_module_default["pie-chart__label-text"],
|
|
11997
|
+
className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], { [pie_chart_module_default["pie-chart__label-text--on-light"]]: labelNeedsDarkText(fill, labelPointers) }),
|
|
11623
11998
|
x: centroidX,
|
|
11624
11999
|
y: centroidY,
|
|
11625
12000
|
dy: ".33em",
|