@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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getStringWidth as getStringWidth$1 } from "./visx/text/index.js";
|
|
2
2
|
import { formatNumber, formatNumberCompact } from "@automattic/number-formatters";
|
|
3
|
-
import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, Grid, TooltipContext, XYChart, buildChartTheme, useEventEmitter } from "@visx/xychart";
|
|
3
|
+
import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, GlyphSeries, Grid, TooltipContext, XYChart, buildChartTheme, useEventEmitter } from "@visx/xychart";
|
|
4
4
|
import { __, _x, sprintf } from "@wordpress/i18n";
|
|
5
5
|
import clsx from "clsx";
|
|
6
6
|
import * as React from "react";
|
|
@@ -436,9 +436,11 @@ const hexToRgba = (hex, alpha) => {
|
|
|
436
436
|
return color(hex).copy({ opacity: alpha }).formatRgb();
|
|
437
437
|
};
|
|
438
438
|
/**
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* @
|
|
439
|
+
* Weighted Euclidean distance between two HSL colors.
|
|
440
|
+
*
|
|
441
|
+
* @deprecated Not perceptual and blind to color vision deficiency, so charts no longer use it; it will be removed in a future major version.
|
|
442
|
+
* @param hsl1 - first color in HSL format [h, s, l]
|
|
443
|
+
* @param hsl2 - second color in HSL format [h, s, l]
|
|
442
444
|
* @return distance value (0-100+, lower means more similar)
|
|
443
445
|
*/
|
|
444
446
|
const getColorDistance = (hsl1, hsl2) => {
|
|
@@ -800,145 +802,421 @@ const createZonedClock = (timeZone) => {
|
|
|
800
802
|
* 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.
|
|
801
803
|
*/
|
|
802
804
|
const ChartScopeContext = createContext(null);
|
|
803
|
-
//#endregion
|
|
804
|
-
//#region src/providers/chart-context/private/get-chart-color.ts
|
|
805
|
-
/**
|
|
806
|
-
* Golden ratio for mathematically pleasing color distribution
|
|
807
|
-
* Used to generate evenly spaced hues that are visually distinct
|
|
808
|
-
*/
|
|
809
|
-
const GOLDEN_RATIO = .618033988749;
|
|
810
|
-
/**
|
|
811
|
-
* Minimum perceptual distance between colors to ensure visual distinction
|
|
812
|
-
* Based on weighted HSL distance calculation optimized for chart readability
|
|
813
|
-
*/
|
|
814
|
-
const MIN_COLOR_DISTANCE = 25;
|
|
815
|
-
/**
|
|
816
|
-
* Maximum attempts to find a sufficiently different color
|
|
817
|
-
* Prevents infinite loops while allowing reasonable search space
|
|
818
|
-
*/
|
|
819
|
-
const MAX_COLOR_GENERATION_ATTEMPTS = 50;
|
|
820
|
-
/**
|
|
821
|
-
* Color variation attempt offset
|
|
822
|
-
* Small increment to explore slightly different color variations per attempt
|
|
823
|
-
*/
|
|
824
|
-
const VARIATION_ATTEMPT_OFFSET = .1;
|
|
825
|
-
/**
|
|
826
|
-
* Base saturation percentage for generated colors
|
|
827
|
-
* 45% provides muted, professional colors without being washed out
|
|
828
|
-
*/
|
|
829
|
-
const BASE_SATURATION = 45;
|
|
830
|
-
/**
|
|
831
|
-
* Number of saturation variation steps
|
|
832
|
-
* Creates 3 different saturation levels for variety
|
|
833
|
-
*/
|
|
834
|
-
const SATURATION_VARIATION_STEPS = 3;
|
|
835
805
|
/**
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
|
|
840
|
-
const SATURATION_INCREMENT = 10;
|
|
841
|
-
/**
|
|
842
|
-
* Base lightness percentage for generated colors
|
|
843
|
-
* 35% ensures sufficient contrast against white backgrounds for WCAG AA compliance
|
|
844
|
-
* WCAG AA requires 4.5:1 contrast ratio for normal text
|
|
806
|
+
* The catalog role holding one series-palette slot.
|
|
807
|
+
*
|
|
808
|
+
* @param slot - The one-based slot number.
|
|
809
|
+
* @return The role name.
|
|
845
810
|
*/
|
|
846
|
-
const
|
|
811
|
+
const seriesRole = (slot) => `--a8c-charts-color-series-${slot}`;
|
|
812
|
+
const SERIES_SLOT_1_FALLBACK = "#3858e9";
|
|
813
|
+
/** The catalog pointer for every slot, in slot order. */
|
|
814
|
+
const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/providers/chart-context/private/catalog-pointers.ts
|
|
817
|
+
/** Terminal literals the provider also generates from before its first read, so SSR and the first client render agree. */
|
|
818
|
+
const BACKGROUND_FALLBACK = "#ffffff";
|
|
819
|
+
const LABEL_FALLBACK = "#1e1e1e";
|
|
820
|
+
const LABEL_INVERSE_FALLBACK = "#f0f0f0";
|
|
821
|
+
const CATALOG_POINTERS = {
|
|
822
|
+
background: `var(--a8c-charts-color-background, ${BACKGROUND_FALLBACK})`,
|
|
823
|
+
label: `var(--a8c-charts-color-label, ${LABEL_FALLBACK})`,
|
|
824
|
+
labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
825
|
+
labelInverse: `var(--a8c-charts-color-label-inverse, ${LABEL_INVERSE_FALLBACK})`,
|
|
826
|
+
labelBackground: "var(--a8c-charts-color-label-background, transparent)",
|
|
827
|
+
grid: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
828
|
+
axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
|
|
829
|
+
tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
|
|
830
|
+
axisY: "var(--a8c-charts-color-axis-y, none)",
|
|
831
|
+
tickY: "var(--a8c-charts-color-tick-y, none)",
|
|
832
|
+
annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
833
|
+
surface: "var(--a8c-charts-color-surface, #fff)",
|
|
834
|
+
surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
835
|
+
trendUp: "var(--a8c-charts-color-trend-up, #008030)",
|
|
836
|
+
trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
|
|
837
|
+
series: SERIES_PALETTE_POINTERS
|
|
838
|
+
};
|
|
847
839
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
840
|
+
* The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
|
|
841
|
+
* rides along as the base the theme and the per-datum styles merge onto.
|
|
850
842
|
*/
|
|
851
|
-
const
|
|
843
|
+
const ANNOTATION_POINTERS = {
|
|
844
|
+
label: {
|
|
845
|
+
anchorLineStroke: CATALOG_POINTERS.annotation,
|
|
846
|
+
backgroundFill: CATALOG_POINTERS.surface
|
|
847
|
+
},
|
|
848
|
+
connector: { stroke: CATALOG_POINTERS.annotation },
|
|
849
|
+
circleSubject: {
|
|
850
|
+
stroke: "transparent",
|
|
851
|
+
fill: CATALOG_POINTERS.annotation,
|
|
852
|
+
radius: 5
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region src/providers/chart-context/private/perceptual-color.ts
|
|
857
|
+
const DEUTERANOPIA = [
|
|
858
|
+
.367322,
|
|
859
|
+
.860646,
|
|
860
|
+
-.227968,
|
|
861
|
+
.280085,
|
|
862
|
+
.672501,
|
|
863
|
+
.047413,
|
|
864
|
+
-.01182,
|
|
865
|
+
.04294,
|
|
866
|
+
.968881
|
|
867
|
+
];
|
|
868
|
+
const PROTANOPIA = [
|
|
869
|
+
.152286,
|
|
870
|
+
1.052583,
|
|
871
|
+
-.204868,
|
|
872
|
+
.114503,
|
|
873
|
+
.786281,
|
|
874
|
+
.099216,
|
|
875
|
+
-.003882,
|
|
876
|
+
-.048116,
|
|
877
|
+
1.051998
|
|
878
|
+
];
|
|
879
|
+
const DEGREES = Math.PI / 180;
|
|
880
|
+
const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
|
|
881
|
+
const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
|
|
882
|
+
const hexToLinear = (hex) => {
|
|
883
|
+
validateHexColor(hex);
|
|
884
|
+
return [
|
|
885
|
+
1,
|
|
886
|
+
3,
|
|
887
|
+
5
|
|
888
|
+
].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
|
|
889
|
+
};
|
|
890
|
+
const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
|
|
891
|
+
const simulate = (matrix, [r, g, b]) => [
|
|
892
|
+
0,
|
|
893
|
+
3,
|
|
894
|
+
6
|
|
895
|
+
].map((row) => Math.min(1, Math.max(0, matrix[row] * r + matrix[row + 1] * g + matrix[row + 2] * b)));
|
|
896
|
+
const linearToLab = ([r, g, b]) => {
|
|
897
|
+
const x = (.4124564 * r + .3575761 * g + .1804375 * b) / .95047;
|
|
898
|
+
const y = .2126729 * r + .7151522 * g + .072175 * b;
|
|
899
|
+
const z = (.0193339 * r + .119192 * g + .9503041 * b) / 1.08883;
|
|
900
|
+
const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
901
|
+
return [
|
|
902
|
+
116 * f(y) - 16,
|
|
903
|
+
500 * (f(x) - f(y)),
|
|
904
|
+
200 * (f(y) - f(z))
|
|
905
|
+
];
|
|
906
|
+
};
|
|
852
907
|
/**
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
|
|
858
|
-
|
|
908
|
+
* CIEDE2000 color difference between two CIELAB colors.
|
|
909
|
+
*
|
|
910
|
+
* @param first - First color in CIELAB.
|
|
911
|
+
* @param second - Second color in CIELAB.
|
|
912
|
+
* @return ΔE00; about 10 is where two chart categories stop being reliably distinguishable.
|
|
913
|
+
*/
|
|
914
|
+
const deltaE2000 = (first, second) => {
|
|
915
|
+
const [l1, a1, b1] = first;
|
|
916
|
+
const [l2, a2, b2] = second;
|
|
917
|
+
const chromaMean = (Math.hypot(a1, b1) + Math.hypot(a2, b2)) / 2;
|
|
918
|
+
const g = .5 * (1 - Math.sqrt(chromaMean ** 7 / (chromaMean ** 7 + 25 ** 7)));
|
|
919
|
+
const a1p = a1 * (1 + g);
|
|
920
|
+
const a2p = a2 * (1 + g);
|
|
921
|
+
const c1p = Math.hypot(a1p, b1);
|
|
922
|
+
const c2p = Math.hypot(a2p, b2);
|
|
923
|
+
const hueOf = (a, b) => a === 0 && b === 0 ? 0 : (Math.atan2(b, a) / DEGREES + 360) % 360;
|
|
924
|
+
const h1p = hueOf(a1p, b1);
|
|
925
|
+
const h2p = hueOf(a2p, b2);
|
|
926
|
+
const chromatic = c1p * c2p !== 0;
|
|
927
|
+
let hueDelta = chromatic ? h2p - h1p : 0;
|
|
928
|
+
if (hueDelta > 180) hueDelta -= 360;
|
|
929
|
+
else if (hueDelta < -180) hueDelta += 360;
|
|
930
|
+
const lightnessDelta = l2 - l1;
|
|
931
|
+
const chromaDelta = c2p - c1p;
|
|
932
|
+
const hueDifference = 2 * Math.sqrt(c1p * c2p) * Math.sin(hueDelta * DEGREES / 2);
|
|
933
|
+
const lightnessMean = (l1 + l2) / 2;
|
|
934
|
+
const chromaMeanPrime = (c1p + c2p) / 2;
|
|
935
|
+
let hueMean = h1p + h2p;
|
|
936
|
+
if (chromatic) {
|
|
937
|
+
if (Math.abs(h1p - h2p) > 180) hueMean += hueMean < 360 ? 360 : -360;
|
|
938
|
+
hueMean /= 2;
|
|
939
|
+
}
|
|
940
|
+
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);
|
|
941
|
+
const lightnessScale = 1 + .015 * (lightnessMean - 50) ** 2 / Math.sqrt(20 + (lightnessMean - 50) ** 2);
|
|
942
|
+
const chromaScale = 1 + .045 * chromaMeanPrime;
|
|
943
|
+
const hueScale = 1 + .015 * chromaMeanPrime * t;
|
|
944
|
+
const rotation = -2 * Math.sqrt(chromaMeanPrime ** 7 / (chromaMeanPrime ** 7 + 25 ** 7)) * Math.sin(60 * Math.exp(-(((hueMean - 275) / 25) ** 2)) * DEGREES);
|
|
945
|
+
const lightnessTerm = lightnessDelta / lightnessScale;
|
|
946
|
+
const chromaTerm = chromaDelta / chromaScale;
|
|
947
|
+
const hueTerm = hueDifference / hueScale;
|
|
948
|
+
return Math.sqrt(lightnessTerm ** 2 + chromaTerm ** 2 + hueTerm ** 2 + rotation * chromaTerm * hueTerm);
|
|
949
|
+
};
|
|
859
950
|
/**
|
|
860
|
-
*
|
|
861
|
-
*
|
|
951
|
+
* A hex color in CIELAB as seen in normal vision, under deuteranopia and under protanopia.
|
|
952
|
+
*
|
|
953
|
+
* @param hex - Six-digit hex color.
|
|
954
|
+
* @return The three views.
|
|
955
|
+
* @throws {Error} if hex string is malformed
|
|
862
956
|
*/
|
|
863
|
-
const
|
|
957
|
+
const hexToViews = (hex) => {
|
|
958
|
+
const linear = hexToLinear(hex);
|
|
959
|
+
return [
|
|
960
|
+
linearToLab(linear),
|
|
961
|
+
linearToLab(simulate(DEUTERANOPIA, linear)),
|
|
962
|
+
linearToLab(simulate(PROTANOPIA, linear))
|
|
963
|
+
];
|
|
964
|
+
};
|
|
864
965
|
/**
|
|
865
|
-
*
|
|
866
|
-
*
|
|
966
|
+
* The smallest ΔE00 between two colors across the three views.
|
|
967
|
+
*
|
|
968
|
+
* @param first - First color's views.
|
|
969
|
+
* @param second - Second color's views.
|
|
970
|
+
* @return The distance in the view where the two are hardest to tell apart.
|
|
867
971
|
*/
|
|
868
|
-
const
|
|
972
|
+
const viewDistance = (first, second) => Math.min(deltaE2000(first[0], second[0]), deltaE2000(first[1], second[1]), deltaE2000(first[2], second[2]));
|
|
869
973
|
/**
|
|
870
|
-
*
|
|
871
|
-
*
|
|
974
|
+
* Convert an OKLCH color to sRGB hex.
|
|
975
|
+
*
|
|
976
|
+
* @param lightness - OKLab L, 0 to 1.
|
|
977
|
+
* @param chroma - OKLCH C.
|
|
978
|
+
* @param hue - Hue in degrees.
|
|
979
|
+
* @return Hex color, or null when the color is outside the sRGB gamut.
|
|
872
980
|
*/
|
|
873
|
-
const
|
|
981
|
+
const oklchToHex = (lightness, chroma, hue) => {
|
|
982
|
+
const a = chroma * Math.cos(hue * DEGREES);
|
|
983
|
+
const b = chroma * Math.sin(hue * DEGREES);
|
|
984
|
+
const l = (lightness + .3963377774 * a + .2158037573 * b) ** 3;
|
|
985
|
+
const m = (lightness - .1055613458 * a - .0638541728 * b) ** 3;
|
|
986
|
+
const s = (lightness - .0894841775 * a - 1.291485548 * b) ** 3;
|
|
987
|
+
const linear = [
|
|
988
|
+
4.0767416621 * l - 3.3077633306 * m + .2309645873 * s,
|
|
989
|
+
-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s,
|
|
990
|
+
-.0041960863 * l - .7034186147 * m + 1.707614701 * s
|
|
991
|
+
];
|
|
992
|
+
if (linear.some((channel) => channel < -1e-4 || channel > 1.0001)) return null;
|
|
993
|
+
return linearToHex(linear);
|
|
994
|
+
};
|
|
874
995
|
/**
|
|
875
|
-
*
|
|
996
|
+
* OKLCH chroma and hue of a hex color.
|
|
997
|
+
*
|
|
998
|
+
* @param hex - Six-digit hex color.
|
|
999
|
+
* @return Chroma, and hue in degrees from 0 to 360.
|
|
1000
|
+
* @throws {Error} if hex string is malformed
|
|
876
1001
|
*/
|
|
877
|
-
const
|
|
1002
|
+
const hexToOklch = (hex) => {
|
|
1003
|
+
const [r, g, b] = hexToLinear(hex);
|
|
1004
|
+
const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
|
|
1005
|
+
const m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
|
|
1006
|
+
const s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
|
|
1007
|
+
const a = 1.9779984951 * l - 2.428592205 * m + .4505937099 * s;
|
|
1008
|
+
const bAxis = .0259040371 * l + .7827717662 * m - .808675766 * s;
|
|
1009
|
+
return {
|
|
1010
|
+
chroma: Math.hypot(a, bAxis),
|
|
1011
|
+
hue: (Math.atan2(bAxis, a) / DEGREES + 360) % 360
|
|
1012
|
+
};
|
|
1013
|
+
};
|
|
878
1014
|
/**
|
|
879
|
-
*
|
|
1015
|
+
* WCAG contrast ratio between two relative luminances.
|
|
1016
|
+
*
|
|
1017
|
+
* @param first - First relative luminance.
|
|
1018
|
+
* @param second - Second relative luminance.
|
|
1019
|
+
* @return Ratio from 1 to 21.
|
|
880
1020
|
*/
|
|
881
|
-
const
|
|
1021
|
+
const luminanceContrastRatio = (first, second) => (Math.max(first, second) + .05) / (Math.min(first, second) + .05);
|
|
882
1022
|
/**
|
|
883
|
-
*
|
|
1023
|
+
* WCAG contrast ratio between two hex colors.
|
|
884
1024
|
*
|
|
885
|
-
* @param
|
|
886
|
-
* @param
|
|
887
|
-
* @return
|
|
1025
|
+
* @param first - First hex color.
|
|
1026
|
+
* @param second - Second hex color.
|
|
1027
|
+
* @return Ratio from 1 to 21.
|
|
1028
|
+
* @throws {Error} if either hex string is malformed
|
|
888
1029
|
*/
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1030
|
+
const contrastRatio = (first, second) => luminanceContrastRatio(relativeLuminance(first), relativeLuminance(second));
|
|
1031
|
+
/** WCAG 1.4.3 text contrast, which a label drawn on a fill needs from at least one label color. */
|
|
1032
|
+
const MIN_LABEL_CONTRAST = 4.5;
|
|
1033
|
+
/** Degrees of hue one unit of OKLCH chroma is worth when ranking candidates by hue. */
|
|
1034
|
+
const CHROMA_WEIGHT = 300;
|
|
1035
|
+
/** ΔE00 one unit of OKLCH chroma is worth when ranking candidates by distance. */
|
|
1036
|
+
const FAR_CHROMA_WEIGHT = 100;
|
|
1037
|
+
const toCandidate = (hex, oklch = hexToOklch(hex)) => ({
|
|
1038
|
+
hex,
|
|
1039
|
+
views: hexToViews(hex),
|
|
1040
|
+
luminance: relativeLuminance(hex),
|
|
1041
|
+
hue: oklch.hue,
|
|
1042
|
+
chroma: oklch.chroma
|
|
1043
|
+
});
|
|
1044
|
+
const rank = (candidate, distance, anchorHue) => anchorHue === null ? -(distance + candidate.chroma * FAR_CHROMA_WEIGHT) : (candidate.hue - anchorHue + 360) % 360 - candidate.chroma * CHROMA_WEIGHT;
|
|
1045
|
+
const LIGHTNESS_STEPS = Array.from({ length: 13 }, (_, step) => .45 + step * .025);
|
|
1046
|
+
const CHROMA_STEPS = [
|
|
1047
|
+
.12,
|
|
1048
|
+
.15,
|
|
1049
|
+
.18,
|
|
1050
|
+
.21
|
|
1051
|
+
];
|
|
1052
|
+
const HUE_STEPS = Array.from({ length: 72 }, (_, step) => step * 5);
|
|
1053
|
+
let candidateGrid = null;
|
|
1054
|
+
const getCandidateGrid = () => {
|
|
1055
|
+
if (!candidateGrid) {
|
|
1056
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1057
|
+
candidateGrid = [];
|
|
1058
|
+
for (const lightness of LIGHTNESS_STEPS) for (const chroma of CHROMA_STEPS) for (const hue of HUE_STEPS) {
|
|
1059
|
+
const hex = oklchToHex(lightness, chroma, hue);
|
|
1060
|
+
if (hex && !seen.has(hex)) {
|
|
1061
|
+
seen.add(hex);
|
|
1062
|
+
candidateGrid.push(toCandidate(hex, {
|
|
1063
|
+
hue,
|
|
1064
|
+
chroma
|
|
1065
|
+
}));
|
|
908
1066
|
}
|
|
909
|
-
const expandedRange = Math.max(hueRange * HUE_RANGE_EXPANSION_FACTOR, MIN_HUE_RANGE_DEGREES);
|
|
910
|
-
hue = (minHue + maxHue) / 2 - expandedRange / 2 + hue / FULL_HUE_ROTATION_DEGREES * expandedRange;
|
|
911
|
-
hue = (hue % FULL_HUE_ROTATION_DEGREES + FULL_HUE_ROTATION_DEGREES) % FULL_HUE_ROTATION_DEGREES;
|
|
912
1067
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1068
|
+
}
|
|
1069
|
+
return candidateGrid;
|
|
1070
|
+
};
|
|
1071
|
+
const legiblePools = /* @__PURE__ */ new Map();
|
|
1072
|
+
const legibleCandidatesFor = (rawBackground, labelColors) => {
|
|
1073
|
+
const background = rawBackground.toLowerCase();
|
|
1074
|
+
const key = [background, ...labelColors.map((hex) => hex.toLowerCase())].join("|");
|
|
1075
|
+
let pool = legiblePools.get(key);
|
|
1076
|
+
if (!pool) {
|
|
1077
|
+
const backgroundLuminance = relativeLuminance(background);
|
|
1078
|
+
const labelLuminances = labelColors.map(relativeLuminance);
|
|
1079
|
+
const onBackground = getCandidateGrid().filter((candidate) => luminanceContrastRatio(candidate.luminance, backgroundLuminance) >= 3);
|
|
1080
|
+
const underLabels = onBackground.filter((candidate) => labelLuminances.some((labelLuminance) => luminanceContrastRatio(candidate.luminance, labelLuminance) >= MIN_LABEL_CONTRAST));
|
|
1081
|
+
pool = underLabels.length > 0 ? underLabels : onBackground;
|
|
1082
|
+
legiblePools.set(key, pool);
|
|
1083
|
+
}
|
|
1084
|
+
return pool;
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
* Build a tracker for a candidate pool, seeding each unused candidate's nearest distance against
|
|
1088
|
+
* every color already in the palette.
|
|
1089
|
+
*
|
|
1090
|
+
* @param pool - Candidates to track, in selection-priority order.
|
|
1091
|
+
* @param palette - Colors already placed.
|
|
1092
|
+
* @param usedHexes - Hex values already placed; a used candidate's distance is never read.
|
|
1093
|
+
* @return The tracker.
|
|
1094
|
+
*/
|
|
1095
|
+
const buildTracker = (pool, palette, usedHexes) => {
|
|
1096
|
+
const nearest = new Float64Array(pool.length).fill(Infinity);
|
|
1097
|
+
for (let i = 0; i < pool.length; i++) {
|
|
1098
|
+
if (usedHexes.has(pool[i].hex)) continue;
|
|
1099
|
+
for (const color of palette) {
|
|
1100
|
+
const distance = viewDistance(pool[i].views, color.views);
|
|
1101
|
+
if (distance < nearest[i]) nearest[i] = distance;
|
|
924
1102
|
}
|
|
925
|
-
if (isSufficientlyDifferent) return hsl(Math.round(hue), saturation / 100, lightness / 100).formatHex();
|
|
926
1103
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1104
|
+
return {
|
|
1105
|
+
pool,
|
|
1106
|
+
nearest
|
|
1107
|
+
};
|
|
931
1108
|
};
|
|
932
1109
|
/**
|
|
933
|
-
*
|
|
1110
|
+
* Fold one newly placed color into a tracker's nearest distances.
|
|
934
1111
|
*
|
|
935
|
-
* @param
|
|
936
|
-
* @
|
|
1112
|
+
* @param tracker - Tracker to update.
|
|
1113
|
+
* @param addedColor - Color just added to the palette.
|
|
1114
|
+
* @param usedHexes - Hex values already placed; a used candidate's distance is never read.
|
|
937
1115
|
*/
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
const
|
|
1116
|
+
const updateTracker = (tracker, addedColor, usedHexes) => {
|
|
1117
|
+
for (let i = 0; i < tracker.pool.length; i++) {
|
|
1118
|
+
if (usedHexes.has(tracker.pool[i].hex)) continue;
|
|
1119
|
+
const distance = viewDistance(tracker.pool[i].views, addedColor.views);
|
|
1120
|
+
if (distance < tracker.nearest[i]) tracker.nearest[i] = distance;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
/**
|
|
1124
|
+
* Pick the next color: the best-ranked unused candidate that clears `PREFERRED_SEPARATION` from
|
|
1125
|
+
* every earlier color; otherwise the farthest candidate.
|
|
1126
|
+
*
|
|
1127
|
+
* @param tracker - Tracker to read.
|
|
1128
|
+
* @param usedHexes - Hex values already placed; a used candidate is never picked.
|
|
1129
|
+
* @param anchorHue - Hue to walk the wheel from, or null to rank by distance.
|
|
1130
|
+
* @return The picked candidate, or null if every candidate in the pool is used.
|
|
1131
|
+
*/
|
|
1132
|
+
const pickNext = (tracker, usedHexes, anchorHue) => {
|
|
1133
|
+
let farthestIndex = -1;
|
|
1134
|
+
let farthestDistance = -Infinity;
|
|
1135
|
+
let preferredIndex = -1;
|
|
1136
|
+
let preferredRank = Infinity;
|
|
1137
|
+
for (let i = 0; i < tracker.pool.length; i++) {
|
|
1138
|
+
if (usedHexes.has(tracker.pool[i].hex)) continue;
|
|
1139
|
+
const distance = tracker.nearest[i];
|
|
1140
|
+
if (distance > farthestDistance) {
|
|
1141
|
+
farthestDistance = distance;
|
|
1142
|
+
farthestIndex = i;
|
|
1143
|
+
}
|
|
1144
|
+
if (distance >= 12) {
|
|
1145
|
+
const candidateRank = rank(tracker.pool[i], distance, anchorHue);
|
|
1146
|
+
if (candidateRank < preferredRank || candidateRank === preferredRank && distance > tracker.nearest[preferredIndex]) {
|
|
1147
|
+
preferredRank = candidateRank;
|
|
1148
|
+
preferredIndex = i;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const index = preferredIndex === -1 ? farthestIndex : preferredIndex;
|
|
1153
|
+
return index === -1 ? null : tracker.pool[index];
|
|
1154
|
+
};
|
|
1155
|
+
const generators = /* @__PURE__ */ new Map();
|
|
1156
|
+
const MAX_CACHED_GENERATORS = 32;
|
|
1157
|
+
/**
|
|
1158
|
+
* Build the series palette: the seeds, then colors that stay apart from every earlier color in
|
|
1159
|
+
* normal vision and under deuteranopia and protanopia.
|
|
1160
|
+
*
|
|
1161
|
+
* @param seeds - Resolved hex palette slots, in slot order.
|
|
1162
|
+
* @param background - Resolved hex chart background.
|
|
1163
|
+
* @param labelColors - Resolved hex label colors that may be drawn on a fill; each generated color reaches `MIN_LABEL_CONTRAST` with one of them.
|
|
1164
|
+
* @return The color at a palette index.
|
|
1165
|
+
*/
|
|
1166
|
+
const createPaletteGenerator = (seeds, background, labelColors = []) => {
|
|
1167
|
+
const normalizedSeeds = seeds.map((hex) => hex.toLowerCase());
|
|
1168
|
+
const normalizedBackground = background.toLowerCase();
|
|
1169
|
+
const normalizedLabels = labelColors.map((hex) => hex.toLowerCase());
|
|
1170
|
+
const key = `${normalizedSeeds.join(",")}|${normalizedBackground}|${normalizedLabels.join(",")}`;
|
|
1171
|
+
let colorAt = generators.get(key);
|
|
1172
|
+
if (!colorAt) {
|
|
1173
|
+
if (generators.size >= MAX_CACHED_GENERATORS) generators.delete(generators.keys().next().value);
|
|
1174
|
+
colorAt = buildPaletteGenerator(normalizedSeeds, normalizedBackground, normalizedLabels);
|
|
1175
|
+
generators.set(key, colorAt);
|
|
1176
|
+
}
|
|
1177
|
+
return colorAt;
|
|
1178
|
+
};
|
|
1179
|
+
const buildPaletteGenerator = (normalizedSeeds, background, labelColors) => {
|
|
1180
|
+
const palette = normalizedSeeds.map((hex) => toCandidate(hex));
|
|
1181
|
+
const usedHexes = new Set(normalizedSeeds);
|
|
1182
|
+
const anchorHue = palette.length > 0 ? palette[0].hue : null;
|
|
1183
|
+
let legibleTracker = null;
|
|
1184
|
+
let legibleExhausted = false;
|
|
1185
|
+
let gridTracker = null;
|
|
1186
|
+
let gridExhausted = false;
|
|
1187
|
+
let placedBeforeExhaustion = 0;
|
|
1188
|
+
let repeatCount = 0;
|
|
1189
|
+
const nextColor = () => {
|
|
1190
|
+
const walkHue = (palette.length - normalizedSeeds.length) % 2 === 1 ? anchorHue : null;
|
|
1191
|
+
if (!legibleExhausted) {
|
|
1192
|
+
legibleTracker ??= buildTracker(legibleCandidatesFor(background, labelColors), palette, usedHexes);
|
|
1193
|
+
const picked = pickNext(legibleTracker, usedHexes, walkHue);
|
|
1194
|
+
if (picked) return picked;
|
|
1195
|
+
legibleExhausted = true;
|
|
1196
|
+
}
|
|
1197
|
+
if (!gridExhausted) {
|
|
1198
|
+
gridTracker ??= buildTracker(getCandidateGrid(), palette, usedHexes);
|
|
1199
|
+
const picked = pickNext(gridTracker, usedHexes, walkHue);
|
|
1200
|
+
if (picked) return picked;
|
|
1201
|
+
gridExhausted = true;
|
|
1202
|
+
placedBeforeExhaustion = palette.length;
|
|
1203
|
+
}
|
|
1204
|
+
const repeated = palette[repeatCount % placedBeforeExhaustion];
|
|
1205
|
+
repeatCount++;
|
|
1206
|
+
return repeated;
|
|
1207
|
+
};
|
|
1208
|
+
return (index) => {
|
|
1209
|
+
if (index < palette.length) return palette[index].hex;
|
|
1210
|
+
while (palette.length <= index) {
|
|
1211
|
+
const next = nextColor();
|
|
1212
|
+
usedHexes.add(next.hex);
|
|
1213
|
+
palette.push(next);
|
|
1214
|
+
if (legibleTracker && !legibleExhausted) updateTracker(legibleTracker, next, usedHexes);
|
|
1215
|
+
if (gridTracker && !gridExhausted) updateTracker(gridTracker, next, usedHexes);
|
|
1216
|
+
}
|
|
1217
|
+
return palette[index].hex;
|
|
1218
|
+
};
|
|
1219
|
+
};
|
|
942
1220
|
//#endregion
|
|
943
1221
|
//#region src/providers/chart-context/themes.ts
|
|
944
1222
|
/**
|
|
@@ -988,6 +1266,13 @@ const defaultTheme = {
|
|
|
988
1266
|
};
|
|
989
1267
|
//#endregion
|
|
990
1268
|
//#region src/providers/chart-context/global-charts-provider.tsx
|
|
1269
|
+
const resolveOpaqueHex = (pointer, element) => {
|
|
1270
|
+
const raw = resolveCssVariable(pointer, element);
|
|
1271
|
+
if (!raw || color(raw)?.opacity !== 1) return null;
|
|
1272
|
+
const hex = normalizeColorToHex(pointer, element, resolveCssVariable);
|
|
1273
|
+
return isValidHexColor(hex) ? hex : null;
|
|
1274
|
+
};
|
|
1275
|
+
const PLACEHOLDER_LABEL_COLORS = [LABEL_FALLBACK, LABEL_INVERSE_FALLBACK];
|
|
991
1276
|
const GlobalChartsContext = createContext(null);
|
|
992
1277
|
const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
993
1278
|
const [charts, setCharts] = useState(() => /* @__PURE__ */ new Map());
|
|
@@ -1002,50 +1287,32 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
|
1002
1287
|
const providerTheme = useMemo(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
|
|
1003
1288
|
const [colorCache, setColorCache] = useState(() => ({
|
|
1004
1289
|
colors: [],
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
maxHue: 0
|
|
1290
|
+
background: BACKGROUND_FALLBACK,
|
|
1291
|
+
labelColors: PLACEHOLDER_LABEL_COLORS,
|
|
1292
|
+
colorAt: createPaletteGenerator([SERIES_SLOT_1_FALLBACK], BACKGROUND_FALLBACK, PLACEHOLDER_LABEL_COLORS)
|
|
1009
1293
|
}));
|
|
1010
1294
|
const [isColorPaletteResolved, setIsColorPaletteResolved] = useState(false);
|
|
1011
1295
|
useLayoutEffect(() => {
|
|
1012
1296
|
setIsColorPaletteResolved(false);
|
|
1013
1297
|
const resolvedColors = [];
|
|
1014
|
-
const hues = [];
|
|
1015
|
-
const existingHslColors = [];
|
|
1016
|
-
let minHue = 360;
|
|
1017
|
-
let maxHue = 0;
|
|
1018
1298
|
for (const color of SERIES_PALETTE_POINTERS) {
|
|
1019
1299
|
const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
|
|
1020
|
-
if (normalizedColor.
|
|
1021
|
-
resolvedColors.push(normalizedColor);
|
|
1022
|
-
const hslColor = hsl(normalizedColor);
|
|
1023
|
-
if (!isNaN(hslColor.h)) {
|
|
1024
|
-
const hslTuple = [
|
|
1025
|
-
hslColor.h,
|
|
1026
|
-
hslColor.s * 100,
|
|
1027
|
-
hslColor.l * 100
|
|
1028
|
-
];
|
|
1029
|
-
hues.push(hslTuple[0]);
|
|
1030
|
-
existingHslColors.push(hslTuple);
|
|
1031
|
-
minHue = Math.min(minHue, hslTuple[0]);
|
|
1032
|
-
maxHue = Math.max(maxHue, hslTuple[0]);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1300
|
+
if (isValidHexColor(normalizedColor)) resolvedColors.push(normalizedColor);
|
|
1035
1301
|
}
|
|
1302
|
+
const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, wrapperRef.current) ?? "#ffffff";
|
|
1303
|
+
const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, wrapperRef.current)).filter((hex) => hex !== null);
|
|
1036
1304
|
setColorCache({
|
|
1037
1305
|
colors: resolvedColors,
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
maxHue
|
|
1306
|
+
background: backgroundHex,
|
|
1307
|
+
labelColors,
|
|
1308
|
+
colorAt: createPaletteGenerator(resolvedColors, backgroundHex, labelColors)
|
|
1042
1309
|
});
|
|
1043
1310
|
}, []);
|
|
1044
1311
|
useEffect(() => {
|
|
1045
1312
|
if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
|
|
1046
1313
|
}, [colorCache]);
|
|
1047
1314
|
const [groupToColorMap, setGroupToColorMap] = useState(() => /* @__PURE__ */ new Map());
|
|
1048
|
-
const paletteKey = colorCache.colors.join(",")
|
|
1315
|
+
const paletteKey = `${colorCache.colors.join(",")}|${colorCache.background}|${colorCache.labelColors.join(",")}`;
|
|
1049
1316
|
useEffect(() => {
|
|
1050
1317
|
setGroupToColorMap(/* @__PURE__ */ new Map());
|
|
1051
1318
|
}, [paletteKey]);
|
|
@@ -1068,11 +1335,11 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
|
1068
1335
|
const existing = groupToColorMap.get(group);
|
|
1069
1336
|
if (existing) return existing;
|
|
1070
1337
|
const assignedCount = groupToColorMap.size;
|
|
1071
|
-
const color =
|
|
1338
|
+
const color = colorCache.colorAt(assignedCount);
|
|
1072
1339
|
groupToColorMap.set(group, color);
|
|
1073
1340
|
return color;
|
|
1074
1341
|
}
|
|
1075
|
-
return
|
|
1342
|
+
return colorCache.colorAt(index);
|
|
1076
1343
|
}, [colorCache, groupToColorMap]);
|
|
1077
1344
|
const getElementStyles = useCallback(({ data, index, overrideColor, legendShape }) => {
|
|
1078
1345
|
const isSeriesData = data && typeof data === "object" && "data" in data;
|
|
@@ -1238,39 +1505,6 @@ const useDeepMemo = (value) => {
|
|
|
1238
1505
|
return ref.current;
|
|
1239
1506
|
};
|
|
1240
1507
|
//#endregion
|
|
1241
|
-
//#region src/providers/chart-context/private/catalog-pointers.ts
|
|
1242
|
-
const CATALOG_POINTERS = {
|
|
1243
|
-
background: "var(--a8c-charts-color-background, #fff)",
|
|
1244
|
-
labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
1245
|
-
grid: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
1246
|
-
axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
|
|
1247
|
-
tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
|
|
1248
|
-
axisY: "var(--a8c-charts-color-axis-y, none)",
|
|
1249
|
-
tickY: "var(--a8c-charts-color-tick-y, none)",
|
|
1250
|
-
annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
1251
|
-
surface: "var(--a8c-charts-color-surface, #fff)",
|
|
1252
|
-
surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
1253
|
-
trendUp: "var(--a8c-charts-color-trend-up, #008030)",
|
|
1254
|
-
trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
|
|
1255
|
-
series: SERIES_PALETTE_POINTERS
|
|
1256
|
-
};
|
|
1257
|
-
/**
|
|
1258
|
-
* The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
|
|
1259
|
-
* rides along as the base the theme and the per-datum styles merge onto.
|
|
1260
|
-
*/
|
|
1261
|
-
const ANNOTATION_POINTERS = {
|
|
1262
|
-
label: {
|
|
1263
|
-
anchorLineStroke: CATALOG_POINTERS.annotation,
|
|
1264
|
-
backgroundFill: CATALOG_POINTERS.surface
|
|
1265
|
-
},
|
|
1266
|
-
connector: { stroke: CATALOG_POINTERS.annotation },
|
|
1267
|
-
circleSubject: {
|
|
1268
|
-
stroke: "transparent",
|
|
1269
|
-
fill: CATALOG_POINTERS.annotation,
|
|
1270
|
-
radius: 5
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
//#endregion
|
|
1274
1508
|
//#region src/providers/chart-scope/use-chart-scope-element.ts
|
|
1275
1509
|
/**
|
|
1276
1510
|
* Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
|
|
@@ -2748,18 +2982,41 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2748
2982
|
getChartRoot,
|
|
2749
2983
|
focusWithoutScrollIfNeeded
|
|
2750
2984
|
]);
|
|
2985
|
+
const onChartFocus = useCallback((event) => {
|
|
2986
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
2987
|
+
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2988
|
+
}, [
|
|
2989
|
+
isNavigating,
|
|
2990
|
+
selectedIndex,
|
|
2991
|
+
setSelectedIndex
|
|
2992
|
+
]);
|
|
2993
|
+
const pointerIndex = useRef(void 0);
|
|
2994
|
+
const onChartPointerMove = useCallback((index) => {
|
|
2995
|
+
if (selectedIndex === void 0) {
|
|
2996
|
+
if (pointerIndex.current !== void 0) pointerIndex.current = index;
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
const root = getChartRoot();
|
|
3000
|
+
const activeElement = root?.ownerDocument.activeElement;
|
|
3001
|
+
if (activeElement && root.contains(activeElement)) {
|
|
3002
|
+
pointerIndex.current = index;
|
|
3003
|
+
focusWithoutScrollIfNeeded(root);
|
|
3004
|
+
}
|
|
3005
|
+
setSelectedIndex(void 0);
|
|
3006
|
+
setIsNavigating(false);
|
|
3007
|
+
}, [
|
|
3008
|
+
selectedIndex,
|
|
3009
|
+
setSelectedIndex,
|
|
3010
|
+
setIsNavigating,
|
|
3011
|
+
getChartRoot,
|
|
3012
|
+
focusWithoutScrollIfNeeded
|
|
3013
|
+
]);
|
|
2751
3014
|
return {
|
|
2752
3015
|
tooltipRef,
|
|
2753
|
-
onChartFocus
|
|
2754
|
-
|
|
2755
|
-
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2756
|
-
}, [
|
|
2757
|
-
isNavigating,
|
|
2758
|
-
selectedIndex,
|
|
2759
|
-
setSelectedIndex
|
|
2760
|
-
]),
|
|
2761
|
-
onChartBlur: useCallback(() => {
|
|
3016
|
+
onChartFocus,
|
|
3017
|
+
onChartBlur: useCallback((event) => {
|
|
2762
3018
|
setIsNavigating(false);
|
|
3019
|
+
if (!event.currentTarget.contains(event.relatedTarget)) pointerIndex.current = void 0;
|
|
2763
3020
|
}, [setIsNavigating]),
|
|
2764
3021
|
onChartKeyDown: useCallback((event) => {
|
|
2765
3022
|
if (event.key === "Tab" || event.key === "Escape") {
|
|
@@ -2767,10 +3024,12 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2767
3024
|
focusWithoutScrollIfNeeded(getChartRoot());
|
|
2768
3025
|
setSelectedIndex(void 0);
|
|
2769
3026
|
setIsNavigating(false);
|
|
3027
|
+
pointerIndex.current = void 0;
|
|
2770
3028
|
return;
|
|
2771
3029
|
}
|
|
2772
3030
|
if (totalPoints === 0) return;
|
|
2773
|
-
const currentSelectedIndex = selectedIndex
|
|
3031
|
+
const currentSelectedIndex = selectedIndex ?? pointerIndex.current ?? -1;
|
|
3032
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") pointerIndex.current = void 0;
|
|
2774
3033
|
if (event.key === "ArrowRight") {
|
|
2775
3034
|
event.preventDefault();
|
|
2776
3035
|
setIsNavigating(true);
|
|
@@ -2791,7 +3050,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2791
3050
|
getChartRoot,
|
|
2792
3051
|
focusWithoutScrollIfNeeded,
|
|
2793
3052
|
onActivate
|
|
2794
|
-
])
|
|
3053
|
+
]),
|
|
3054
|
+
onChartPointerMove
|
|
2795
3055
|
};
|
|
2796
3056
|
};
|
|
2797
3057
|
//#endregion
|
|
@@ -2930,7 +3190,7 @@ function useChartChildren(children, chartType) {
|
|
|
2930
3190
|
var chart_layout_module_default = { "chart-layout__content": "a8ccharts-fpNVAq-chart-layout__content" };
|
|
2931
3191
|
//#endregion
|
|
2932
3192
|
//#region src/charts/private/chart-layout/chart-layout.tsx
|
|
2933
|
-
const ChartLayout = ({ legendPosition, legendElement, legendChildren, children, trailingContent, onContentHeightChange, gap, className, style, "data-testid": dataTestId, "data-chart-id": dataChartId }) => {
|
|
3193
|
+
const ChartLayout = ({ legendPosition, legendElement, legendChildren, children, trailingContent, onContentHeightChange, gap, className, rootRef, style, "data-testid": dataTestId, "data-chart-id": dataChartId }) => {
|
|
2934
3194
|
const [contentRef, contentWidth, contentHeight] = useElementSize();
|
|
2935
3195
|
const isRenderProp = typeof children === "function";
|
|
2936
3196
|
const isMeasured = contentHeight > 0;
|
|
@@ -2949,6 +3209,7 @@ const ChartLayout = ({ legendPosition, legendElement, legendChildren, children,
|
|
|
2949
3209
|
isMeasured
|
|
2950
3210
|
}) : children;
|
|
2951
3211
|
return /* @__PURE__ */ jsxs(Stack, {
|
|
3212
|
+
ref: rootRef,
|
|
2952
3213
|
direction: "column",
|
|
2953
3214
|
gap,
|
|
2954
3215
|
className,
|
|
@@ -5659,9 +5920,8 @@ const createGroupScale = (keys, bandwidth, padding) => scaleBand({
|
|
|
5659
5920
|
//#endregion
|
|
5660
5921
|
//#region src/charts/bar-chart/private/comparison-bars-geometry.ts
|
|
5661
5922
|
/**
|
|
5662
|
-
*
|
|
5663
|
-
*
|
|
5664
|
-
* sit on the same baseline as primary bars.
|
|
5923
|
+
* Mirrors visx's getScaleBaseline so comparison shadows and primary bars
|
|
5924
|
+
* share the same baseline, including out-of-domain zero values.
|
|
5665
5925
|
*
|
|
5666
5926
|
* @param {ValueScale} scale - The continuous value scale.
|
|
5667
5927
|
* @return {number} The baseline output position in pixels.
|
|
@@ -5672,7 +5932,7 @@ function getValueScaleBaseline(scale) {
|
|
|
5672
5932
|
const maybeZero = scale(0);
|
|
5673
5933
|
const [minOutput, maxOutput] = isDescending ? [b, a] : [a, b];
|
|
5674
5934
|
if (isDescending) return Number.isFinite(maybeZero) ? Math.min(Math.max(minOutput, maybeZero), maxOutput) : maxOutput;
|
|
5675
|
-
return Number.isFinite(maybeZero) ? Math.
|
|
5935
|
+
return Number.isFinite(maybeZero) ? Math.max(maybeZero, minOutput) : minOutput;
|
|
5676
5936
|
}
|
|
5677
5937
|
/**
|
|
5678
5938
|
* Compute the rect for a comparison "shadow" bar, centered on the paired
|
|
@@ -5708,6 +5968,51 @@ function computeComparisonRect(params) {
|
|
|
5708
5968
|
height: valueLength
|
|
5709
5969
|
};
|
|
5710
5970
|
}
|
|
5971
|
+
//#endregion
|
|
5972
|
+
//#region src/charts/bar-chart/private/classified-bar-series.tsx
|
|
5973
|
+
const ClassifiedBarSeries = ({ barClassName, primaryKeys, groupPadding, ...props }) => {
|
|
5974
|
+
const { xScale, yScale, horizontal } = useContext(DataContext);
|
|
5975
|
+
const bandScale = horizontal ? yScale : xScale;
|
|
5976
|
+
const valueScale = horizontal ? xScale : yScale;
|
|
5977
|
+
const groupScale = useMemo(() => createGroupScale(primaryKeys, bandScale?.bandwidth?.() ?? 0, groupPadding), [
|
|
5978
|
+
primaryKeys,
|
|
5979
|
+
bandScale,
|
|
5980
|
+
groupPadding
|
|
5981
|
+
]);
|
|
5982
|
+
const renderGlyph = useCallback(({ datum, x, y, color, onBlur, onFocus, onPointerMove, onPointerOut, onPointerUp }) => {
|
|
5983
|
+
if (!bandScale?.bandwidth || !valueScale) return null;
|
|
5984
|
+
const baseline = getValueScaleBaseline(valueScale);
|
|
5985
|
+
const position = Number(bandScale((horizontal ? props.yAccessor : props.xAccessor)(datum))) + (groupScale(props.dataKey) ?? 0);
|
|
5986
|
+
const value = horizontal ? x : y;
|
|
5987
|
+
return /* @__PURE__ */ jsx("rect", {
|
|
5988
|
+
className: clsx("visx-bar", barClassName(datum)),
|
|
5989
|
+
tabIndex: onFocus || onBlur ? 0 : void 0,
|
|
5990
|
+
onBlur,
|
|
5991
|
+
onFocus,
|
|
5992
|
+
onPointerMove,
|
|
5993
|
+
onPointerOut,
|
|
5994
|
+
onPointerUp,
|
|
5995
|
+
x: horizontal ? baseline + Math.min(0, value - baseline) : position,
|
|
5996
|
+
y: horizontal ? position : baseline + Math.min(0, value - baseline),
|
|
5997
|
+
width: horizontal ? Math.abs(value - baseline) : groupScale.bandwidth(),
|
|
5998
|
+
height: horizontal ? groupScale.bandwidth() : Math.abs(value - baseline),
|
|
5999
|
+
fill: color
|
|
6000
|
+
});
|
|
6001
|
+
}, [
|
|
6002
|
+
bandScale,
|
|
6003
|
+
valueScale,
|
|
6004
|
+
horizontal,
|
|
6005
|
+
groupScale,
|
|
6006
|
+
props.dataKey,
|
|
6007
|
+
props.xAccessor,
|
|
6008
|
+
props.yAccessor,
|
|
6009
|
+
barClassName
|
|
6010
|
+
]);
|
|
6011
|
+
return /* @__PURE__ */ jsx(GlyphSeries, {
|
|
6012
|
+
...props,
|
|
6013
|
+
renderGlyph
|
|
6014
|
+
});
|
|
6015
|
+
};
|
|
5711
6016
|
/**
|
|
5712
6017
|
* Upper clamp on the computed group padding, so bars can never collapse to zero width
|
|
5713
6018
|
* even at very large `widthFactor` values.
|
|
@@ -5872,9 +6177,10 @@ function nearestBandIndex(data, accessor, scale, position) {
|
|
|
5872
6177
|
* @param root0.withTooltips - Whether pointer events update the tooltip.
|
|
5873
6178
|
* @param root0.onPointerDown - Receives the corrected pointer-down datum.
|
|
5874
6179
|
* @param root0.onPointerUp - Receives the corrected pointer-up datum.
|
|
6180
|
+
* @param root0.onPointerMove - Receives the corrected datum under a moving pointer.
|
|
5875
6181
|
* @return No visual content.
|
|
5876
6182
|
*/
|
|
5877
|
-
function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp }) {
|
|
6183
|
+
function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp, onPointerMove }) {
|
|
5878
6184
|
const { xScale, yScale, dataRegistry, horizontal } = useContext(DataContext);
|
|
5879
6185
|
const { showTooltip } = useContext(TooltipContext);
|
|
5880
6186
|
const scale = horizontal ? yScale : xScale;
|
|
@@ -5919,20 +6225,22 @@ function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointe
|
|
|
5919
6225
|
]);
|
|
5920
6226
|
const handlePointer = useCallback((params) => {
|
|
5921
6227
|
const selections = getSelections(params);
|
|
5922
|
-
if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
|
|
5923
|
-
const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
|
|
5924
|
-
if (params?.event.type === "pointermove" || !callback) return;
|
|
5925
6228
|
const nearest = selections.reduce((best, selection) => {
|
|
5926
6229
|
const distance = (value) => Math.hypot(value.distanceX, value.distanceY);
|
|
5927
6230
|
return !best || distance(selection) <= distance(best) ? selection : best;
|
|
5928
6231
|
}, void 0);
|
|
6232
|
+
if (params?.event.type === "pointermove" && nearest) onPointerMove?.(nearest);
|
|
6233
|
+
if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
|
|
6234
|
+
const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
|
|
6235
|
+
if (params?.event.type === "pointermove" || !callback) return;
|
|
5929
6236
|
if (nearest) callback(nearest);
|
|
5930
6237
|
}, [
|
|
5931
6238
|
getSelections,
|
|
5932
6239
|
withTooltips,
|
|
5933
6240
|
showTooltip,
|
|
5934
6241
|
onPointerDown,
|
|
5935
|
-
onPointerUp
|
|
6242
|
+
onPointerUp,
|
|
6243
|
+
onPointerMove
|
|
5936
6244
|
]);
|
|
5937
6245
|
useEventEmitter("pointermove", withTooltips ? handlePointer : void 0);
|
|
5938
6246
|
useEventEmitter("pointerdown", handlePointer);
|
|
@@ -5951,7 +6259,7 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ jsx("div", {
|
|
|
5951
6259
|
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
5952
6260
|
children: sprintf(__("%1$s: %2$s", "jetpack-charts"), label, value)
|
|
5953
6261
|
});
|
|
5954
|
-
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 }) => {
|
|
6262
|
+
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 }) => {
|
|
5955
6263
|
if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
|
|
5956
6264
|
const legendInteractive = legend.interactive ?? false;
|
|
5957
6265
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
@@ -6015,7 +6323,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6015
6323
|
});
|
|
6016
6324
|
}, [primaryEntries, onDatumActivate]);
|
|
6017
6325
|
const visibleSeriesKey = useMemo(() => JSON.stringify(primaryKeys), [primaryKeys]);
|
|
6018
|
-
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
|
|
6326
|
+
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown, onChartPointerMove } = useKeyboardNavigation({
|
|
6019
6327
|
selectedIndex,
|
|
6020
6328
|
setSelectedIndex,
|
|
6021
6329
|
isNavigating,
|
|
@@ -6025,6 +6333,10 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6025
6333
|
onActivate: activateSelectedBar,
|
|
6026
6334
|
visibleSeriesKey
|
|
6027
6335
|
});
|
|
6336
|
+
const handlePointerMove = useCallback(({ key, index }) => {
|
|
6337
|
+
const seriesIndex = primaryKeys.indexOf(key);
|
|
6338
|
+
if (seriesIndex >= 0) onChartPointerMove(index * primaryKeys.length + seriesIndex);
|
|
6339
|
+
}, [primaryKeys, onChartPointerMove]);
|
|
6028
6340
|
const comparisonEntries = useMemo(() => {
|
|
6029
6341
|
const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
|
|
6030
6342
|
label: series.label,
|
|
@@ -6321,7 +6633,19 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6321
6633
|
getElementStyles,
|
|
6322
6634
|
resolveFill: resolveComparisonFill
|
|
6323
6635
|
}),
|
|
6324
|
-
/* @__PURE__ */ jsx(
|
|
6636
|
+
barClassName ? /* @__PURE__ */ jsx("g", {
|
|
6637
|
+
className: "visx-bar-group",
|
|
6638
|
+
children: primaryEntries.map(({ series: seriesData, index }) => /* @__PURE__ */ jsx(ClassifiedBarSeries, {
|
|
6639
|
+
dataKey: seriesData.label,
|
|
6640
|
+
data: seriesData.data,
|
|
6641
|
+
xAccessor: chartOptions.accessors.xAccessor,
|
|
6642
|
+
yAccessor: chartOptions.accessors.yAccessor,
|
|
6643
|
+
colorAccessor: getBarBackground(index),
|
|
6644
|
+
barClassName,
|
|
6645
|
+
primaryKeys,
|
|
6646
|
+
groupPadding
|
|
6647
|
+
}, seriesData.label))
|
|
6648
|
+
}) : /* @__PURE__ */ jsx(BarGroup, {
|
|
6325
6649
|
padding: groupPadding,
|
|
6326
6650
|
children: primaryEntries.map(({ series: seriesData, index }) => /* @__PURE__ */ jsx(BarSeries, {
|
|
6327
6651
|
dataKey: seriesData?.label,
|
|
@@ -6336,7 +6660,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6336
6660
|
groupPadding,
|
|
6337
6661
|
withTooltips,
|
|
6338
6662
|
onPointerDown,
|
|
6339
|
-
onPointerUp
|
|
6663
|
+
onPointerUp,
|
|
6664
|
+
onPointerMove: handlePointerMove
|
|
6340
6665
|
}),
|
|
6341
6666
|
!allSeriesHidden && /* @__PURE__ */ jsx(WholeNumberTicks, {
|
|
6342
6667
|
...wholeNumberTicksProps,
|
|
@@ -6351,6 +6676,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
6351
6676
|
withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
|
|
6352
6677
|
tooltipPlacement,
|
|
6353
6678
|
tooltipAnchorTop,
|
|
6679
|
+
style: tooltipStyle,
|
|
6354
6680
|
detectBounds: true,
|
|
6355
6681
|
snapTooltipToDatumX: true,
|
|
6356
6682
|
snapTooltipToDatumY: true,
|
|
@@ -7017,12 +7343,13 @@ function normalizeGeoChartError(eventArgs) {
|
|
|
7017
7343
|
* @param props.height - Height of the chart in pixels
|
|
7018
7344
|
* @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
|
|
7019
7345
|
* @param props.resolution - Resolution level ('countries', 'provinces', or 'metros')
|
|
7346
|
+
* @param props.domain - Country code whose viewpoint sets the disputed borders
|
|
7020
7347
|
* @param props.onError - Optional callback for Google Charts errors
|
|
7021
7348
|
* @param props.className - Additional CSS class name for the chart container
|
|
7022
7349
|
* @param props.renderPlaceholder - Optional render function for the loading placeholder
|
|
7023
7350
|
* @return A React component displaying an interactive map with data visualization
|
|
7024
7351
|
*/
|
|
7025
|
-
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
|
|
7352
|
+
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", domain, onError, renderPlaceholder }) => {
|
|
7026
7353
|
const { getElementStyles } = useGlobalChartsContext();
|
|
7027
7354
|
const scopeElement = useChartScopeElement();
|
|
7028
7355
|
const containerRef = useRef(null);
|
|
@@ -7086,6 +7413,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
7086
7413
|
const options = useMemo(() => ({
|
|
7087
7414
|
...region !== "world" && { region },
|
|
7088
7415
|
...resolution !== "countries" && { resolution },
|
|
7416
|
+
...domain && { domain },
|
|
7089
7417
|
colorAxis: { colors: [lightColorHex, fullColorHex] },
|
|
7090
7418
|
backgroundColor: backgroundColorHex,
|
|
7091
7419
|
datalessRegionColor: defaultFillColorHex,
|
|
@@ -7099,6 +7427,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
7099
7427
|
}), [
|
|
7100
7428
|
region,
|
|
7101
7429
|
resolution,
|
|
7430
|
+
domain,
|
|
7102
7431
|
lightColorHex,
|
|
7103
7432
|
fullColorHex,
|
|
7104
7433
|
backgroundColorHex,
|
|
@@ -11027,6 +11356,7 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
|
|
|
11027
11356
|
var pie_chart_module_default = {
|
|
11028
11357
|
"pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
|
|
11029
11358
|
"pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
|
|
11359
|
+
"pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
|
|
11030
11360
|
"pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
|
|
11031
11361
|
"pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
|
|
11032
11362
|
};
|
|
@@ -11071,6 +11401,25 @@ const validateData$1 = (data) => {
|
|
|
11071
11401
|
};
|
|
11072
11402
|
};
|
|
11073
11403
|
/**
|
|
11404
|
+
* Whether a pie slice's label should use the dark `label` role instead of the default `label-inverse` role.
|
|
11405
|
+
*
|
|
11406
|
+
* A label plate always wins: the text sits on the plate, not the slice fill, so it never flips.
|
|
11407
|
+
* Otherwise the role that contrasts more with the resolved slice fill wins. `null` pointers (not
|
|
11408
|
+
* yet resolved, or labels off) keep the default inverse role.
|
|
11409
|
+
*
|
|
11410
|
+
* @param fill - The slice's resolved fill color.
|
|
11411
|
+
* @param pointers - The label pointers, resolved at the chart's own root element.
|
|
11412
|
+
* @return Whether the label needs dark text.
|
|
11413
|
+
*/
|
|
11414
|
+
const labelNeedsDarkText = (fill, pointers) => {
|
|
11415
|
+
if (!pointers || pointers.hasPlate) return false;
|
|
11416
|
+
const fillHex = normalizeColorToHex(fill);
|
|
11417
|
+
if (!isValidHexColor(fillHex) || !isValidHexColor(pointers.labelHex)) return false;
|
|
11418
|
+
if (pointers.isInverseSeeThrough) return true;
|
|
11419
|
+
if (!isValidHexColor(pointers.labelInverseHex)) return false;
|
|
11420
|
+
return contrastRatio(fillHex, pointers.labelHex) > contrastRatio(fillHex, pointers.labelInverseHex);
|
|
11421
|
+
};
|
|
11422
|
+
/**
|
|
11074
11423
|
* Renders a pie or donut chart using the provided data.
|
|
11075
11424
|
*
|
|
11076
11425
|
* @param {PieChartProps} props - Component props
|
|
@@ -11084,11 +11433,36 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11084
11433
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
|
|
11085
11434
|
const standaloneScopeClass = useStandaloneScopeClass();
|
|
11086
11435
|
const containerRef = useRef(null);
|
|
11436
|
+
const rootRef = useRef(null);
|
|
11437
|
+
const [labelPointers, setLabelPointers] = useState(null);
|
|
11087
11438
|
const onMouseLeave = useCallback(() => {
|
|
11088
11439
|
if (!withTooltips) return;
|
|
11089
11440
|
hideTooltip();
|
|
11090
11441
|
}, [withTooltips, hideTooltip]);
|
|
11091
|
-
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
11442
|
+
const { getElementStyles, isSeriesVisible, isColorPaletteResolved } = useGlobalChartsContext();
|
|
11443
|
+
const { isValid, message } = validateData$1(data);
|
|
11444
|
+
useLayoutEffect(() => {
|
|
11445
|
+
if (!showLabels || !rootRef.current) return;
|
|
11446
|
+
const resolve = createCssVariableResolver(rootRef.current);
|
|
11447
|
+
const rawLabelBackground = resolve(CATALOG_POINTERS.labelBackground);
|
|
11448
|
+
const rawLabel = resolve(CATALOG_POINTERS.label);
|
|
11449
|
+
const isLabelOpaque = rawLabel ? color(rawLabel)?.opacity === 1 : false;
|
|
11450
|
+
const rawLabelInverse = resolve(CATALOG_POINTERS.labelInverse);
|
|
11451
|
+
const labelInverseColor = rawLabelInverse ? color(rawLabelInverse) : null;
|
|
11452
|
+
const plateColor = rawLabelBackground ? color(rawLabelBackground) : null;
|
|
11453
|
+
const next = {
|
|
11454
|
+
hasPlate: rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false,
|
|
11455
|
+
labelHex: isLabelOpaque ? normalizeColorToHex(CATALOG_POINTERS.label, null, resolve) : "",
|
|
11456
|
+
labelInverseHex: normalizeColorToHex(CATALOG_POINTERS.labelInverse, null, resolve),
|
|
11457
|
+
isInverseSeeThrough: labelInverseColor ? labelInverseColor.opacity < 1 : false
|
|
11458
|
+
};
|
|
11459
|
+
setLabelPointers((previous) => previous && previous.hasPlate === next.hasPlate && previous.labelHex === next.labelHex && previous.labelInverseHex === next.labelInverseHex && previous.isInverseSeeThrough === next.isInverseSeeThrough ? previous : next);
|
|
11460
|
+
}, [
|
|
11461
|
+
showLabels,
|
|
11462
|
+
className,
|
|
11463
|
+
isColorPaletteResolved,
|
|
11464
|
+
isValid
|
|
11465
|
+
]);
|
|
11092
11466
|
const dataWithPercentages = useDataWithPercentages(data);
|
|
11093
11467
|
const { visibleData, allSegmentsHidden, legendData } = useLegendVisibilityData({
|
|
11094
11468
|
data: dataWithPercentages,
|
|
@@ -11099,7 +11473,6 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11099
11473
|
showValues: true,
|
|
11100
11474
|
legendValueDisplay
|
|
11101
11475
|
}), [legendValueDisplay]));
|
|
11102
|
-
const { isValid, message } = validateData$1(data);
|
|
11103
11476
|
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(children, "PieChart");
|
|
11104
11477
|
const chartMetadata = useMemo(() => ({
|
|
11105
11478
|
thickness,
|
|
@@ -11161,6 +11534,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11161
11534
|
legendElement,
|
|
11162
11535
|
legendChildren,
|
|
11163
11536
|
gap,
|
|
11537
|
+
rootRef,
|
|
11164
11538
|
className: clsx("pie-chart", pie_chart_module_default["pie-chart"], { [pie_chart_module_default["pie-chart--responsive"]]: !propWidth && !propHeight }, className),
|
|
11165
11539
|
style: {
|
|
11166
11540
|
width: propWidth || void 0,
|
|
@@ -11222,9 +11596,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11222
11596
|
tooltipTop: event.clientY - bounds.top + tooltipOffsetY
|
|
11223
11597
|
});
|
|
11224
11598
|
};
|
|
11599
|
+
const fill = accessors.fill(arc.data);
|
|
11225
11600
|
const pathProps = {
|
|
11226
11601
|
d: pie.path(arc) || "",
|
|
11227
|
-
fill
|
|
11602
|
+
fill,
|
|
11228
11603
|
"data-testid": "pie-segment"
|
|
11229
11604
|
};
|
|
11230
11605
|
const groupProps = {};
|
|
@@ -11252,7 +11627,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
11252
11627
|
ry: 4,
|
|
11253
11628
|
pointerEvents: "none"
|
|
11254
11629
|
}), /* @__PURE__ */ jsx("text", {
|
|
11255
|
-
className: pie_chart_module_default["pie-chart__label-text"],
|
|
11630
|
+
className: clsx(pie_chart_module_default["pie-chart__label-text"], { [pie_chart_module_default["pie-chart__label-text--on-light"]]: labelNeedsDarkText(fill, labelPointers) }),
|
|
11256
11631
|
x: centroidX,
|
|
11257
11632
|
y: centroidY,
|
|
11258
11633
|
dy: ".33em",
|