@automattic/charts 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/TOKENS.md +107 -48
- package/dist/index.cjs +584 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +97 -43
- package/dist/index.d.cts +46 -41
- package/dist/index.d.ts +46 -41
- package/dist/index.js +582 -210
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/charts/area-chart/area-chart.module.scss +8 -3
- package/src/charts/area-chart/area-chart.tsx +3 -5
- package/src/charts/bar-chart/bar-chart.module.scss +14 -6
- package/src/charts/bar-chart/bar-chart.tsx +24 -15
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +197 -30
- package/src/charts/bar-chart/test/bar-chart.test.tsx +685 -1
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +17 -12
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +107 -86
- package/src/charts/conversion-funnel-chart/test/chart-scope.test.tsx +36 -0
- package/src/charts/geo-chart/geo-chart.tsx +12 -4
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +13 -9
- package/src/charts/heatmap-chart/heatmap-chart.tsx +12 -5
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +13 -7
- package/src/charts/leaderboard-chart/types.ts +0 -2
- package/src/charts/line-chart/line-chart.module.scss +10 -5
- package/src/charts/line-chart/line-chart.tsx +3 -5
- package/src/charts/line-chart/private/line-chart-annotation.tsx +4 -4
- package/src/charts/pie-chart/pie-chart.tsx +4 -1
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +4 -1
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
- package/src/charts/private/test/time-axis.test.ts +247 -2
- package/src/charts/private/time-axis.ts +252 -63
- package/src/charts/private/with-responsive/test/with-responsive.test.tsx +16 -0
- package/src/charts/private/with-responsive/with-responsive.tsx +18 -11
- package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
- package/src/components/legend/private/base-legend.tsx +3 -1
- package/src/components/legend/test/legend.test.tsx +2 -1
- package/src/components/tooltip/accessible-tooltip.tsx +4 -2
- package/src/components/tooltip/base-tooltip.tsx +8 -1
- package/src/components/trend-indicator/test/trend-indicator.test.tsx +22 -1
- package/src/components/trend-indicator/trend-indicator.module.scss +3 -6
- package/src/components/trend-indicator/trend-indicator.tsx +3 -0
- package/src/hooks/test/use-chart-margin.test.tsx +60 -0
- package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +28 -0
- package/src/hooks/test/use-xychart-theme-style-reads.test.tsx +89 -0
- package/src/hooks/test/use-xychart-theme.test.tsx +25 -0
- package/src/hooks/use-chart-margin.tsx +3 -4
- package/src/hooks/use-xychart-theme.ts +19 -11
- package/src/index.ts +9 -2
- package/src/providers/chart-context/global-charts-provider.tsx +35 -12
- package/src/providers/chart-context/private/theme-override-vars.ts +87 -0
- package/src/providers/chart-context/private/with-catalog-pointers.ts +50 -0
- package/src/providers/chart-context/test/chart-context.test.tsx +0 -40
- package/src/providers/chart-context/test/chart-scope-class.test.tsx +19 -0
- package/src/providers/chart-context/test/sparse-theme.test.ts +35 -0
- package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +29 -0
- package/src/providers/chart-context/test/theme-override-vars.test.ts +118 -0
- package/src/providers/chart-context/test/with-catalog-pointers.test.ts +47 -0
- package/src/providers/chart-context/themes.ts +18 -27
- package/src/providers/chart-context/types.ts +0 -3
- package/src/providers/chart-scope/chart-scope-context.ts +8 -0
- package/src/providers/chart-scope/index.ts +3 -0
- package/src/providers/chart-scope/test/chart-scope.test.tsx +24 -0
- package/src/providers/chart-scope/test/use-standalone-scope-class.test.tsx +20 -0
- package/src/providers/chart-scope/use-chart-scope-element.ts +9 -0
- package/src/providers/chart-scope/use-standalone-scope-class.ts +13 -0
- package/src/providers/index.ts +1 -0
- package/src/styles/chart-scope-class.ts +6 -0
- package/src/styles/chart-scope.scss +83 -0
- package/src/styles/test/chart-scope.test.ts +165 -0
- package/src/types.ts +6 -40
- package/src/utils/index.ts +1 -1
- package/src/utils/resolve-css-var.ts +69 -28
- package/src/utils/test/create-css-variable-resolver.test.ts +83 -0
- package/src/charts/private/grid-control/grid-control.module.scss +0 -8
- package/src/charts/private/grid-control/grid-control.tsx +0 -26
- package/src/charts/private/grid-control/index.ts +0 -1
- package/src/charts/private/grid-control/test/grid-control.test.tsx +0 -56
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,14 @@ const useChartInstanceContext = () => {
|
|
|
64
64
|
};
|
|
65
65
|
const useSingleChartContext = useChartInstanceContext;
|
|
66
66
|
//#endregion
|
|
67
|
+
//#region src/styles/chart-scope-class.ts
|
|
68
|
+
/**
|
|
69
|
+
* Stable class that carries the `--a8c-charts-*` catalog. Applied to the `GlobalChartsProvider` wrapper, to portal-rendered surfaces (which sit outside the provider tree), and to standalone components when no provider is above them.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately unhashed: consumers are documented as being able to target it to override a token on the provider element.
|
|
72
|
+
*/
|
|
73
|
+
const CHART_SCOPE_CLASS = "a8c-charts-scope";
|
|
74
|
+
//#endregion
|
|
67
75
|
//#region src/utils/create-composition.ts
|
|
68
76
|
/**
|
|
69
77
|
* Utility function to create chart components with composition API.
|
|
@@ -510,15 +518,31 @@ const CSS_VAR_NAME_PATTERN = /^--[\w-]+$/;
|
|
|
510
518
|
* @param element - Optional DOM element to resolve the variable from (defaults to document.documentElement)
|
|
511
519
|
* @return The resolved value, fallback value, or null if unresolvable
|
|
512
520
|
*/
|
|
513
|
-
const resolveCssVariable = (value, element) =>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
521
|
+
const resolveCssVariable = (value, element) => createCssVariableResolver(element)(value);
|
|
522
|
+
/**
|
|
523
|
+
* Builds a resolver that reads every value from one `getComputedStyle` snapshot.
|
|
524
|
+
*
|
|
525
|
+
* `getComputedStyle` is the expensive half of resolving a token: each call can force the browser to flush pending style, and a chart theme resolves five roles at once. Taking the snapshot once per build turns that into a single query. The reads that share a snapshot happen synchronously inside one memo body, so no style change can land between them — the resolver is single-pass by design and is not meant to be held across renders.
|
|
526
|
+
*
|
|
527
|
+
* @param element - The element to resolve against, or null/undefined for the document root.
|
|
528
|
+
* @return A resolver that resolves each value exactly as `resolveCssVariable` does.
|
|
529
|
+
*/
|
|
530
|
+
function createCssVariableResolver(element) {
|
|
531
|
+
let styles = null;
|
|
532
|
+
const getStyles = () => {
|
|
533
|
+
if (!styles) styles = computedStyleFor(element);
|
|
534
|
+
return styles;
|
|
535
|
+
};
|
|
536
|
+
return (value) => {
|
|
537
|
+
if (!value) return null;
|
|
538
|
+
if (value.startsWith("var(") && value.endsWith(")")) {
|
|
539
|
+
const parsed = parseVarExpression(value);
|
|
540
|
+
if (parsed) return readCustomProperty(parsed.varName, getStyles()) || parsed.fallback;
|
|
541
|
+
}
|
|
542
|
+
if (value.startsWith("--")) return readCustomProperty(value, getStyles());
|
|
543
|
+
return value;
|
|
544
|
+
};
|
|
545
|
+
}
|
|
522
546
|
/**
|
|
523
547
|
* Parses a var() expression into its variable name and optional fallback.
|
|
524
548
|
* Uses string manipulation instead of complex regex to avoid ReDoS.
|
|
@@ -546,17 +570,30 @@ function parseVarExpression(expr) {
|
|
|
546
570
|
};
|
|
547
571
|
}
|
|
548
572
|
/**
|
|
549
|
-
*
|
|
573
|
+
* Takes one computed-style snapshot, or null where there is nothing to read (SSR, or a detached element).
|
|
574
|
+
*
|
|
575
|
+
* @param element - The element to read from, or null/undefined for the document root.
|
|
576
|
+
* @return The computed styles, or null.
|
|
577
|
+
*/
|
|
578
|
+
function computedStyleFor(element) {
|
|
579
|
+
if (typeof window === "undefined" || typeof document === "undefined") return null;
|
|
580
|
+
try {
|
|
581
|
+
return getComputedStyle(element || document.documentElement);
|
|
582
|
+
} catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Reads one custom property from a computed-style snapshot.
|
|
550
588
|
*
|
|
551
589
|
* @param varName - A CSS variable name like '--my-color'
|
|
552
|
-
* @param
|
|
590
|
+
* @param styles - The snapshot to read from, or null
|
|
553
591
|
* @return The computed value or null
|
|
554
592
|
*/
|
|
555
|
-
function
|
|
556
|
-
if (
|
|
593
|
+
function readCustomProperty(varName, styles) {
|
|
594
|
+
if (!styles) return null;
|
|
557
595
|
try {
|
|
558
|
-
|
|
559
|
-
return getComputedStyle(targetElement).getPropertyValue(varName).trim() || null;
|
|
596
|
+
return styles.getPropertyValue(varName).trim() || null;
|
|
560
597
|
} catch {
|
|
561
598
|
return null;
|
|
562
599
|
}
|
|
@@ -591,6 +628,14 @@ const resolveFontSize = (val) => {
|
|
|
591
628
|
}
|
|
592
629
|
};
|
|
593
630
|
//#endregion
|
|
631
|
+
//#region src/providers/chart-scope/chart-scope-context.ts
|
|
632
|
+
/**
|
|
633
|
+
* The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
|
|
634
|
+
*
|
|
635
|
+
* 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.
|
|
636
|
+
*/
|
|
637
|
+
const ChartScopeContext = (0, react.createContext)(null);
|
|
638
|
+
//#endregion
|
|
594
639
|
//#region src/providers/chart-context/private/get-chart-color.ts
|
|
595
640
|
/**
|
|
596
641
|
* Golden ratio for mathematically pleasing color distribution
|
|
@@ -720,12 +765,75 @@ const getChartColor = (index, colorCache) => {
|
|
|
720
765
|
return (0, _visx_vendor_d3_color.hsl)(Math.round(fallbackHue), fallbackSaturation / 100, fallbackLightness / 100).formatHex();
|
|
721
766
|
};
|
|
722
767
|
//#endregion
|
|
768
|
+
//#region src/providers/chart-context/private/theme-override-vars.ts
|
|
769
|
+
const ROLE_FOR_FIELD = [
|
|
770
|
+
["--a8c-charts-color-background", (theme) => theme.backgroundColor],
|
|
771
|
+
["--a8c-charts-color-grid", (theme) => theme.gridStyles?.stroke],
|
|
772
|
+
["--a8c-charts-color-axis", (theme) => theme.xAxisLineStyles?.stroke],
|
|
773
|
+
["--a8c-charts-color-tick", (theme) => theme.xTickLineStyles?.stroke],
|
|
774
|
+
["--a8c-charts-color-label-axis", (theme) => theme.svgLabelSmall?.fill]
|
|
775
|
+
];
|
|
776
|
+
/**
|
|
777
|
+
* The variable a `theme` prop override is published as, one layer outside the role itself.
|
|
778
|
+
*
|
|
779
|
+
* `chart-scope.scss` declares each of these roles as `var(<role>-theme, <catalog default>)`, so an override that is invalid at computed-value time — a fallback-less `var()` naming a token the host never set — only invalidates this variable, and the role still resolves its mapped `--wpds-*` token. Published as the role directly, such a value took the role and every bare `var(<role>)` read site down with it.
|
|
780
|
+
*
|
|
781
|
+
* @param role - The catalog role being overridden.
|
|
782
|
+
* @return The custom property carrying the consumer's value.
|
|
783
|
+
*/
|
|
784
|
+
const themeLayerVar = (role) => `${role}-theme`;
|
|
785
|
+
ROLE_FOR_FIELD.map(([role]) => role);
|
|
786
|
+
/**
|
|
787
|
+
* Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
|
|
788
|
+
*
|
|
789
|
+
* The dependency runs `<role>: var(<role>-theme, …)`, so a value naming `<role>` closes a cycle through the catalog entry. CSS marks every custom property in a cycle invalid at computed-value time — the role's own fallback is *not* used, so the token resolves to nothing and every chart loses that colour. Verified in Chrome: `--role-theme: var(--role, blue); --role: var(--role-theme, green)` leaves `--role` empty, not `green`.
|
|
790
|
+
*
|
|
791
|
+
* Three things this has to get right, none of which a plain substring test gets:
|
|
792
|
+
*
|
|
793
|
+
* `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
|
|
794
|
+
*
|
|
795
|
+
* Several roles are prefixes of others (`--a8c-charts-color-label` of `--a8c-charts-color-label-secondary`), and every role is a prefix of its own theme layer. A prefix match would read a legitimate cross-role pointer as a self-reference and silently drop the override, so the role must be followed by something that cannot continue an identifier — `-` included. A value naming `<role>-theme` is therefore not treated as a self-reference: that cycle is confined to the theme layer, which leaves the role free to fall back to the catalog default.
|
|
796
|
+
*
|
|
797
|
+
* `resolveCssVariable` also accepts a bare custom-property name with no `var()` wrapper (`--a8c-charts-color-grid`), so that is legal `theme` input too. It forms no cycle — custom properties accept arbitrary token streams — so it survives as a literal string and drops silently at the use site instead of falling back to the catalog default.
|
|
798
|
+
*
|
|
799
|
+
* @param value - The consumer's value for the field.
|
|
800
|
+
* @param role - The catalog role this value would override.
|
|
801
|
+
* @return True when the value reads `role`, so it must not be published.
|
|
802
|
+
*/
|
|
803
|
+
const readsOwnRole = (value, role) => value.trim() === role || new RegExp(`var\\(\\s*${role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(value);
|
|
804
|
+
/**
|
|
805
|
+
* Maps a sparse consumer theme onto the theme-layer variables its overridden catalog roles read, so CSS-painted and JS-resolved colours read one source.
|
|
806
|
+
*
|
|
807
|
+
* A value that reads the role it would override is left unpublished — that is the default theme's own pointer surviving `mergeThemes`, and publishing it would invalidate the role. The role is still reported in `roles`: `withCatalogPointers` restores its theme field to the catalog pointer either way, so CSS and visx agree on the catalog default rather than visx painting a literal CSS never sees.
|
|
808
|
+
*
|
|
809
|
+
* @param theme - The consumer's `theme` prop, before merging with the default theme.
|
|
810
|
+
* @return The wrapper's custom properties, and the roles the consumer overrode.
|
|
811
|
+
*/
|
|
812
|
+
const themeOverrideVars = (theme) => {
|
|
813
|
+
if (!theme) return {
|
|
814
|
+
vars: {},
|
|
815
|
+
roles: []
|
|
816
|
+
};
|
|
817
|
+
const vars = {};
|
|
818
|
+
const roles = [];
|
|
819
|
+
for (const [role, read] of ROLE_FOR_FIELD) {
|
|
820
|
+
const value = read(theme);
|
|
821
|
+
if (typeof value !== "string" || value === "") continue;
|
|
822
|
+
roles.push(role);
|
|
823
|
+
if (!readsOwnRole(value, role)) vars[themeLayerVar(role)] = value;
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
vars,
|
|
827
|
+
roles
|
|
828
|
+
};
|
|
829
|
+
};
|
|
830
|
+
//#endregion
|
|
723
831
|
//#region src/providers/chart-context/themes.ts
|
|
724
832
|
/**
|
|
725
833
|
* Default theme configuration
|
|
726
834
|
*/
|
|
727
835
|
const defaultTheme = {
|
|
728
|
-
backgroundColor: "var(--a8c-charts-color-background,
|
|
836
|
+
backgroundColor: "var(--a8c-charts-color-background, #fff)",
|
|
729
837
|
labelBackgroundColor: "transparent",
|
|
730
838
|
labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
|
|
731
839
|
colors: [
|
|
@@ -736,59 +844,59 @@ const defaultTheme = {
|
|
|
736
844
|
"#FF8C8F"
|
|
737
845
|
],
|
|
738
846
|
gridStyles: {
|
|
739
|
-
stroke: "var(--a8c-charts-color-grid,
|
|
847
|
+
stroke: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
740
848
|
strokeWidth: 1
|
|
741
849
|
},
|
|
742
850
|
tickLength: 4,
|
|
743
851
|
gridColor: "",
|
|
744
852
|
gridColorDark: "",
|
|
745
853
|
xTickLineStyles: {
|
|
746
|
-
stroke: "var(--a8c-charts-color-tick,
|
|
854
|
+
stroke: "var(--a8c-charts-color-tick, #dbdbdb)",
|
|
747
855
|
strokeWidth: 1
|
|
748
856
|
},
|
|
749
857
|
xAxisLineStyles: {
|
|
750
|
-
stroke: "var(--a8c-charts-color-axis,
|
|
858
|
+
stroke: "var(--a8c-charts-color-axis, #dbdbdb)",
|
|
751
859
|
strokeWidth: 1
|
|
752
860
|
},
|
|
753
861
|
legend: {
|
|
754
|
-
labelStyles: { color: "var(--a8c-charts-color-label,
|
|
862
|
+
labelStyles: { color: "var(--a8c-charts-color-label, #1e1e1e)" },
|
|
755
863
|
containerStyles: {},
|
|
756
864
|
shapeStyles: []
|
|
757
865
|
},
|
|
758
866
|
seriesLineStyles: [],
|
|
759
867
|
glyphs: [],
|
|
760
868
|
svgLabelSmall: {
|
|
761
|
-
fill: "var(--a8c-charts-color-label
|
|
869
|
+
fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
762
870
|
fontFamily: "inherit"
|
|
763
871
|
},
|
|
764
872
|
svgLabelBig: { fontFamily: "inherit" },
|
|
765
873
|
annotationStyles: {
|
|
766
874
|
label: {
|
|
767
|
-
anchorLineStroke: "var(--a8c-charts-color-annotation,
|
|
768
|
-
backgroundFill: "var(--a8c-charts-color-
|
|
875
|
+
anchorLineStroke: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
876
|
+
backgroundFill: "var(--a8c-charts-color-surface, #fff)"
|
|
769
877
|
},
|
|
770
|
-
connector: { stroke: "var(--a8c-charts-color-annotation,
|
|
878
|
+
connector: { stroke: "var(--a8c-charts-color-annotation, #1e1e1e)" },
|
|
771
879
|
circleSubject: {
|
|
772
880
|
stroke: "transparent",
|
|
773
|
-
fill: "var(--a8c-charts-color-annotation,
|
|
881
|
+
fill: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
774
882
|
radius: 5
|
|
775
883
|
}
|
|
776
884
|
},
|
|
777
|
-
geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary,
|
|
885
|
+
geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)" },
|
|
778
886
|
leaderboardChart: {
|
|
779
887
|
rowGap: 12,
|
|
780
888
|
columnGap: 4,
|
|
781
889
|
labelSpacing: "xs",
|
|
782
890
|
deltaColors: [
|
|
783
|
-
"var(--a8c-charts-color-trend-down,
|
|
784
|
-
"var(--a8c-charts-color-trend-neutral,
|
|
785
|
-
"var(--a8c-charts-color-trend-up,
|
|
891
|
+
"var(--a8c-charts-color-trend-down, #cc1818)",
|
|
892
|
+
"var(--a8c-charts-color-trend-neutral, #707070)",
|
|
893
|
+
"var(--a8c-charts-color-trend-up, #008030)"
|
|
786
894
|
]
|
|
787
895
|
},
|
|
788
896
|
conversionFunnelChart: {
|
|
789
|
-
backgroundColor: "var(--a8c-charts-color-surface-secondary,
|
|
790
|
-
positiveChangeColor: "var(--a8c-charts-color-trend-up,
|
|
791
|
-
negativeChangeColor: "var(--a8c-charts-color-trend-down,
|
|
897
|
+
backgroundColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
898
|
+
positiveChangeColor: "var(--a8c-charts-color-trend-up, #008030)",
|
|
899
|
+
negativeChangeColor: "var(--a8c-charts-color-trend-down, #cc1818)"
|
|
792
900
|
},
|
|
793
901
|
lineChart: { lineStyles: { comparison: {
|
|
794
902
|
strokeDasharray: "4 4",
|
|
@@ -813,15 +921,65 @@ const defaultTheme = {
|
|
|
813
921
|
}
|
|
814
922
|
};
|
|
815
923
|
//#endregion
|
|
924
|
+
//#region src/providers/chart-context/private/with-catalog-pointers.ts
|
|
925
|
+
const CATALOG_RESTORE_FOR_ROLE = {
|
|
926
|
+
"--a8c-charts-color-background": () => ({ backgroundColor: defaultTheme.backgroundColor }),
|
|
927
|
+
"--a8c-charts-color-grid": (theme) => ({ gridStyles: {
|
|
928
|
+
...theme.gridStyles,
|
|
929
|
+
stroke: defaultTheme.gridStyles.stroke
|
|
930
|
+
} }),
|
|
931
|
+
"--a8c-charts-color-axis": (theme) => ({ xAxisLineStyles: {
|
|
932
|
+
...theme.xAxisLineStyles,
|
|
933
|
+
stroke: defaultTheme.xAxisLineStyles.stroke
|
|
934
|
+
} }),
|
|
935
|
+
"--a8c-charts-color-tick": (theme) => ({ xTickLineStyles: {
|
|
936
|
+
...theme.xTickLineStyles,
|
|
937
|
+
stroke: defaultTheme.xTickLineStyles.stroke
|
|
938
|
+
} }),
|
|
939
|
+
"--a8c-charts-color-label-axis": (theme) => ({ svgLabelSmall: {
|
|
940
|
+
...theme.svgLabelSmall,
|
|
941
|
+
fill: defaultTheme.svgLabelSmall.fill
|
|
942
|
+
} })
|
|
943
|
+
};
|
|
944
|
+
/**
|
|
945
|
+
* Restores the mapped theme fields of `overriddenRoles` to their catalog pointer, so the theme-layer variable `themeOverrideVars` writes on the provider wrapper is the only carrier for an overridden role — CSS and the JS bridge (`useXYChartTheme`) then resolve it through the same cascade instead of visx reading a baked literal that can disagree with a closer CSS override.
|
|
946
|
+
*
|
|
947
|
+
* This covers roles `themeOverrideVars` deliberately left unpublished as well as the ones it published. A value that reads its own role is not publishable, but leaving visx the consumer's literal is what the whole mechanism exists to prevent: `theme={ { gridStyles: { stroke: 'var(--brand, var(--a8c-charts-color-grid, red))' } } }` would have CSS paint the catalog default while visx painted `--brand`.
|
|
948
|
+
*
|
|
949
|
+
* Every field outside the five mapped roles is left exactly as `merged` provided it: those aren't published as theme-layer vars, so rewriting them would erase a consumer's override with no replacement carrier.
|
|
950
|
+
*
|
|
951
|
+
* @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
|
|
952
|
+
* @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
|
|
953
|
+
* @return A new theme object; `merged` is not mutated.
|
|
954
|
+
*/
|
|
955
|
+
const withCatalogPointers = (merged, overriddenRoles) => {
|
|
956
|
+
let result = merged;
|
|
957
|
+
for (const role of overriddenRoles) {
|
|
958
|
+
const restore = CATALOG_RESTORE_FOR_ROLE[role];
|
|
959
|
+
if (restore) result = {
|
|
960
|
+
...result,
|
|
961
|
+
...restore(result)
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
return result;
|
|
965
|
+
};
|
|
966
|
+
//#endregion
|
|
816
967
|
//#region src/providers/chart-context/global-charts-provider.tsx
|
|
817
968
|
const GlobalChartsContext = (0, react.createContext)(null);
|
|
818
969
|
const GlobalChartsProvider = ({ children, theme }) => {
|
|
819
970
|
const [charts, setCharts] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
820
971
|
const [hiddenSeries, setHiddenSeries] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
821
972
|
const wrapperRef = (0, react.useRef)(null);
|
|
973
|
+
const [scopeNode, setScopeNode] = (0, react.useState)(null);
|
|
974
|
+
const setWrapperNode = (0, react.useCallback)((node) => {
|
|
975
|
+
wrapperRef.current = node;
|
|
976
|
+
setScopeNode(node);
|
|
977
|
+
}, []);
|
|
978
|
+
const { vars: overrideVars, roles: overriddenRoles } = (0, react.useMemo)(() => themeOverrideVars(theme), [theme]);
|
|
822
979
|
const providerTheme = (0, react.useMemo)(() => {
|
|
823
|
-
|
|
824
|
-
|
|
980
|
+
if (!theme) return defaultTheme;
|
|
981
|
+
return withCatalogPointers(mergeThemes(defaultTheme, theme), overriddenRoles);
|
|
982
|
+
}, [theme, overriddenRoles]);
|
|
825
983
|
const [colorCache, setColorCache] = (0, react.useState)(() => ({
|
|
826
984
|
colors: [],
|
|
827
985
|
hues: [],
|
|
@@ -898,7 +1056,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
898
1056
|
}
|
|
899
1057
|
return getChartColor(index, colorCache);
|
|
900
1058
|
}, [colorCache, groupToColorMap]);
|
|
901
|
-
const resolveThemeColor = (0, react.useCallback)((value) => value ? normalizeColorToHex(value, wrapperRef.current, resolveCssVariable) : "", []);
|
|
902
1059
|
const getElementStyles = (0, react.useCallback)(({ data, index, overrideColor, legendShape }) => {
|
|
903
1060
|
const isSeriesData = data && typeof data === "object" && "data" in data && "options" in data;
|
|
904
1061
|
const isPointPercentageData = data && typeof data === "object" && "value" in data && typeof data.value === "number" && !("data" in data);
|
|
@@ -941,7 +1098,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
941
1098
|
getChartData,
|
|
942
1099
|
theme: providerTheme,
|
|
943
1100
|
getElementStyles,
|
|
944
|
-
resolveThemeColor,
|
|
945
1101
|
toggleSeriesVisibility,
|
|
946
1102
|
isSeriesVisible,
|
|
947
1103
|
getHiddenSeries,
|
|
@@ -953,7 +1109,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
953
1109
|
getChartData,
|
|
954
1110
|
providerTheme,
|
|
955
1111
|
getElementStyles,
|
|
956
|
-
resolveThemeColor,
|
|
957
1112
|
toggleSeriesVisibility,
|
|
958
1113
|
isSeriesVisible,
|
|
959
1114
|
getHiddenSeries,
|
|
@@ -962,9 +1117,16 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
962
1117
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalChartsContext.Provider, {
|
|
963
1118
|
value,
|
|
964
1119
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
965
|
-
ref:
|
|
966
|
-
|
|
967
|
-
|
|
1120
|
+
ref: setWrapperNode,
|
|
1121
|
+
className: CHART_SCOPE_CLASS,
|
|
1122
|
+
style: {
|
|
1123
|
+
display: "contents",
|
|
1124
|
+
...overrideVars
|
|
1125
|
+
},
|
|
1126
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartScopeContext.Provider, {
|
|
1127
|
+
value: scopeNode,
|
|
1128
|
+
children
|
|
1129
|
+
})
|
|
968
1130
|
})
|
|
969
1131
|
});
|
|
970
1132
|
};
|
|
@@ -1024,12 +1186,33 @@ const useDeepMemo = (value) => {
|
|
|
1024
1186
|
return ref.current;
|
|
1025
1187
|
};
|
|
1026
1188
|
//#endregion
|
|
1189
|
+
//#region src/providers/chart-scope/use-chart-scope-element.ts
|
|
1190
|
+
/**
|
|
1191
|
+
* Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
|
|
1192
|
+
*
|
|
1193
|
+
* @return {HTMLElement | null} The nearest chart scope element, or `null`.
|
|
1194
|
+
*/
|
|
1195
|
+
const useChartScopeElement = () => (0, react.useContext)(ChartScopeContext);
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/providers/chart-scope/use-standalone-scope-class.ts
|
|
1198
|
+
/**
|
|
1199
|
+
* Returns the catalog class when there is no `GlobalChartsProvider` above this component, and `undefined` when there is.
|
|
1200
|
+
*
|
|
1201
|
+
* Standalone components need the catalog to resolve at all, but inside a provider the tokens already inherit — re-declaring them there would shadow a consumer override set anywhere in the tree.
|
|
1202
|
+
*
|
|
1203
|
+
* @return The scope class name, or undefined when a provider is present.
|
|
1204
|
+
*/
|
|
1205
|
+
const useStandaloneScopeClass = () => (0, react.useContext)(GlobalChartsContext) ? void 0 : CHART_SCOPE_CLASS;
|
|
1206
|
+
//#endregion
|
|
1027
1207
|
//#region src/hooks/use-xychart-theme.ts
|
|
1028
|
-
const resolveColor = (value) => value ? resolveCssVariable(value) ?? value : value;
|
|
1029
1208
|
const useXYChartTheme = (data) => {
|
|
1030
1209
|
const theme = useGlobalChartsTheme();
|
|
1210
|
+
const scopeElement = useChartScopeElement();
|
|
1211
|
+
const seriesColorKey = JSON.stringify((data ?? []).map((series) => series.options?.stroke).filter((color) => Boolean(color)));
|
|
1031
1212
|
return (0, react.useMemo)(() => {
|
|
1032
|
-
const
|
|
1213
|
+
const resolve = createCssVariableResolver(scopeElement);
|
|
1214
|
+
const resolveColor = (value) => value ? resolve(value) ?? value : value;
|
|
1215
|
+
const seriesColors = JSON.parse(seriesColorKey);
|
|
1033
1216
|
return (0, _visx_xychart.buildChartTheme)({
|
|
1034
1217
|
...theme,
|
|
1035
1218
|
colors: [...seriesColors, ...theme.colors ?? []],
|
|
@@ -1051,7 +1234,11 @@ const useXYChartTheme = (data) => {
|
|
|
1051
1234
|
fill: resolveColor(theme.svgLabelSmall.fill)
|
|
1052
1235
|
}
|
|
1053
1236
|
});
|
|
1054
|
-
}, [
|
|
1237
|
+
}, [
|
|
1238
|
+
theme,
|
|
1239
|
+
seriesColorKey,
|
|
1240
|
+
scopeElement
|
|
1241
|
+
]);
|
|
1055
1242
|
};
|
|
1056
1243
|
//#endregion
|
|
1057
1244
|
//#region src/hooks/use-chart-data-transform.ts
|
|
@@ -1138,7 +1325,7 @@ const getXAxisLabelMetrics = (theme, orientation) => {
|
|
|
1138
1325
|
const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
1139
1326
|
const yTicks = (0, react.useMemo)(() => {
|
|
1140
1327
|
const allDataPoints = data.flatMap((series) => series.data);
|
|
1141
|
-
if (horizontal) return allDataPoints.map((d) => d.label ||
|
|
1328
|
+
if (horizontal) return allDataPoints.map((d) => d.label || d.date?.getTime());
|
|
1142
1329
|
if (options.axis?.y?.tickValues?.length) return options.axis.y.tickValues;
|
|
1143
1330
|
const minY = Math.min(...allDataPoints.map((d) => d.value));
|
|
1144
1331
|
const maxY = Math.max(...allDataPoints.map((d) => d.value));
|
|
@@ -1527,6 +1714,7 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
|
|
|
1527
1714
|
const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
|
|
1528
1715
|
const theme = useGlobalChartsTheme();
|
|
1529
1716
|
const context = (0, react.useContext)(GlobalChartsContext);
|
|
1717
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
1530
1718
|
const legendScale = (0, _visx_scale.scaleOrdinal)({
|
|
1531
1719
|
domain: items.map((item) => item.label),
|
|
1532
1720
|
range: items.map((item) => item.color)
|
|
@@ -1579,7 +1767,7 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
|
|
|
1579
1767
|
justify: orientation === "horizontal" ? flexAlignment : void 0,
|
|
1580
1768
|
wrap: orientation === "horizontal" ? "wrap" : void 0,
|
|
1581
1769
|
role: "list",
|
|
1582
|
-
className: (0, clsx.default)(base_legend_module_default.legend, className),
|
|
1770
|
+
className: (0, clsx.default)(standaloneScopeClass, base_legend_module_default.legend, className),
|
|
1583
1771
|
style: theme.legend?.containerStyles,
|
|
1584
1772
|
children: labels.map((label, i) => {
|
|
1585
1773
|
const matchedItem = items[i];
|
|
@@ -1866,9 +2054,10 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
|
|
|
1866
2054
|
data,
|
|
1867
2055
|
className
|
|
1868
2056
|
});
|
|
2057
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
1869
2058
|
if (!renderContainer) return content;
|
|
1870
2059
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1871
|
-
className: base_tooltip_module_default.tooltip,
|
|
2060
|
+
className: (0, clsx.default)(standaloneScopeClass, base_tooltip_module_default.tooltip),
|
|
1872
2061
|
style: {
|
|
1873
2062
|
top,
|
|
1874
2063
|
left,
|
|
@@ -1937,10 +2126,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
1937
2126
|
tabIndex: -1,
|
|
1938
2127
|
role: "tooltip",
|
|
1939
2128
|
"aria-atomic": "true",
|
|
1940
|
-
className: keyboardFocusedClassName,
|
|
2129
|
+
className: (0, clsx.default)(CHART_SCOPE_CLASS, keyboardFocusedClassName),
|
|
1941
2130
|
children: tooltipContent
|
|
1942
2131
|
}, `chart-tooltip-${selectedIndex}`);
|
|
1943
2132
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2133
|
+
className: CHART_SCOPE_CLASS,
|
|
1944
2134
|
role: "tooltip",
|
|
1945
2135
|
"aria-live": "polite",
|
|
1946
2136
|
children: tooltipContent
|
|
@@ -2233,28 +2423,38 @@ const getCurveType = (type, smoothing) => {
|
|
|
2233
2423
|
default: return _visx_curve.curveLinear;
|
|
2234
2424
|
}
|
|
2235
2425
|
};
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
});
|
|
2244
|
-
};
|
|
2245
|
-
const formatHourTick = (timestamp) => {
|
|
2246
|
-
return new Date(timestamp).toLocaleTimeString(void 0, {
|
|
2247
|
-
hour: "numeric",
|
|
2248
|
-
hour12: true
|
|
2249
|
-
});
|
|
2250
|
-
};
|
|
2251
|
-
const formatDateOrHourTick = (timestamp) => {
|
|
2252
|
-
const date = new Date(timestamp);
|
|
2253
|
-
return date.getHours() === 0 && date.getMinutes() === 0 ? formatDateTick$1(timestamp) : formatHourTick(timestamp);
|
|
2426
|
+
const dateTimeFormat = (options) => {
|
|
2427
|
+
let formatter;
|
|
2428
|
+
return (timestamp) => {
|
|
2429
|
+
if (!Number.isFinite(Number(timestamp))) return new Date(timestamp).toLocaleDateString();
|
|
2430
|
+
formatter = formatter ?? new Intl.DateTimeFormat(void 0, options);
|
|
2431
|
+
return formatter.format(timestamp);
|
|
2432
|
+
};
|
|
2254
2433
|
};
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2434
|
+
const formatYearTick = dateTimeFormat({ year: "numeric" });
|
|
2435
|
+
const formatDateTick = dateTimeFormat({
|
|
2436
|
+
month: "short",
|
|
2437
|
+
day: "numeric"
|
|
2438
|
+
});
|
|
2439
|
+
const formatHourTick = dateTimeFormat({
|
|
2440
|
+
hour: "numeric",
|
|
2441
|
+
hour12: true
|
|
2442
|
+
});
|
|
2443
|
+
const formatMonthTick = dateTimeFormat({ month: "short" });
|
|
2444
|
+
const boundaryFormat = (isAnchor, atBoundary, between) => {
|
|
2445
|
+
const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
|
|
2446
|
+
format.isAnchor = isAnchor;
|
|
2447
|
+
return format;
|
|
2448
|
+
};
|
|
2449
|
+
const formatDateOrHourTick = boundaryFormat((date) => date.getHours() === 0 && date.getMinutes() === 0, formatDateTick, formatHourTick);
|
|
2450
|
+
const formatMonthOrYearTick = boundaryFormat((date) => date.getMonth() === 0, formatYearTick, formatMonthTick);
|
|
2451
|
+
const getSpan = (sortedData) => {
|
|
2452
|
+
const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => first !== void 0 && last !== void 0);
|
|
2453
|
+
if (!bounds.length) return null;
|
|
2454
|
+
return {
|
|
2455
|
+
minX: Math.min(...bounds.map(([first]) => Number(first))),
|
|
2456
|
+
maxX: Math.max(...bounds.map(([, last]) => Number(last)))
|
|
2457
|
+
};
|
|
2258
2458
|
};
|
|
2259
2459
|
const getPointSpacingInHours = (sortedData) => {
|
|
2260
2460
|
return sortedData.reduce((spacing, datom) => datom.data.reduce((seriesSpacing, point, index) => {
|
|
@@ -2263,26 +2463,90 @@ const getPointSpacingInHours = (sortedData) => {
|
|
|
2263
2463
|
return Math.min(seriesSpacing, Math.abs((0, date_fns.differenceInHours)(point.date, previous.date)));
|
|
2264
2464
|
}, spacing), Number.POSITIVE_INFINITY);
|
|
2265
2465
|
};
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
week:
|
|
2270
|
-
|
|
2466
|
+
const SUB_DAILY_SPACING_HOURS = 23;
|
|
2467
|
+
const MONTHLY_SPACING_HOURS = 672;
|
|
2468
|
+
const getBucketResolution = (sortedData, tickResolution) => {
|
|
2469
|
+
if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
|
|
2470
|
+
const spacingInHours = getPointSpacingInHours(sortedData);
|
|
2471
|
+
if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
|
|
2472
|
+
if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
|
|
2473
|
+
return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
|
|
2271
2474
|
};
|
|
2272
2475
|
const getFormatter = (sortedData, tickResolution) => {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
const diffInHours = Math.abs((0, date_fns.differenceInHours)(maxX, minX));
|
|
2279
|
-
if (
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2476
|
+
const resolution = getBucketResolution(sortedData, tickResolution);
|
|
2477
|
+
if (resolution === "year") return formatYearTick;
|
|
2478
|
+
if (resolution === "month") return formatMonthOrYearTick;
|
|
2479
|
+
const span = getSpan(sortedData);
|
|
2480
|
+
if (!span) return formatDateTick;
|
|
2481
|
+
const diffInHours = Math.abs((0, date_fns.differenceInHours)(span.maxX, span.minX));
|
|
2482
|
+
if (resolution === "hour") {
|
|
2483
|
+
if (diffInHours <= 24) return formatHourTick;
|
|
2484
|
+
if (diffInHours <= 168) return formatDateOrHourTick;
|
|
2485
|
+
}
|
|
2486
|
+
return Math.abs((0, date_fns.differenceInYears)(span.maxX, span.minX)) <= 1 ? formatDateTick : formatYearTick;
|
|
2487
|
+
};
|
|
2488
|
+
const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
|
|
2489
|
+
if (isAnchor(date)) indices.push(index);
|
|
2490
|
+
return indices;
|
|
2491
|
+
}, []);
|
|
2492
|
+
const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
|
|
2493
|
+
const steps = /* @__PURE__ */ new Set();
|
|
2494
|
+
for (let count = maxTicks; count > 1; count--) {
|
|
2495
|
+
steps.add(Math.max(1, Math.floor((length - 1) / (count - 1))));
|
|
2496
|
+
steps.add(Math.max(1, Math.ceil((length - 1) / (count - 1))));
|
|
2497
|
+
}
|
|
2498
|
+
if (anchorSpacing) {
|
|
2499
|
+
for (let divisor = 1; divisor <= anchorSpacing; divisor++) if (anchorSpacing % divisor === 0) steps.add(anchorSpacing / divisor);
|
|
2500
|
+
const minStep = Math.floor((length - 1) / Math.max(1, maxTicks)) + 1;
|
|
2501
|
+
steps.add(Math.ceil(minStep / anchorSpacing) * anchorSpacing);
|
|
2502
|
+
}
|
|
2503
|
+
return steps;
|
|
2504
|
+
};
|
|
2505
|
+
/**
|
|
2506
|
+
* Tick values for a band time axis.
|
|
2507
|
+
*
|
|
2508
|
+
* visx samples a band domain by index from offset zero, blind to which labels
|
|
2509
|
+
* carry a boundary and without collapsing repeats — so the tick that prints the
|
|
2510
|
+
* year or the date often isn't sampled at all, and a long series can show the
|
|
2511
|
+
* same label twice. Choose the values instead: sweep the evenly spaced
|
|
2512
|
+
* candidates, including those that step from anchor to anchor, and keep the one
|
|
2513
|
+
* that reaches the most anchors without thinning the axis or putting two
|
|
2514
|
+
* identical labels side by side.
|
|
2515
|
+
*
|
|
2516
|
+
* @param domain - Band domain, in axis order.
|
|
2517
|
+
* @param tickFormatter - Formatter the axis will render these values with.
|
|
2518
|
+
* @param maxTicks - Most ticks the axis should carry.
|
|
2519
|
+
* @return Values to hand the axis as `tickValues`.
|
|
2520
|
+
*/
|
|
2521
|
+
const getBandTickValues = (domain, tickFormatter, maxTicks) => {
|
|
2522
|
+
if (!domain.length) return [];
|
|
2523
|
+
const { isAnchor } = tickFormatter;
|
|
2524
|
+
const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
|
|
2525
|
+
const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
|
|
2526
|
+
const candidates = [];
|
|
2527
|
+
const consider = (indices) => {
|
|
2528
|
+
if (!indices.length || indices.length > maxTicks) return;
|
|
2529
|
+
if (!indices.some((index, position) => position > 0 && domainLabels[index] === domainLabels[indices[position - 1]])) candidates.push(indices);
|
|
2530
|
+
};
|
|
2531
|
+
const anchorSpacing = anchorIndices.length > 1 ? anchorIndices[1] - anchorIndices[0] : null;
|
|
2532
|
+
for (const step of getCandidateSteps(domain.length, maxTicks, anchorSpacing)) for (let offset = 0; offset < step; offset++) {
|
|
2533
|
+
const indices = [];
|
|
2534
|
+
for (let index = offset; index < domain.length; index += step) indices.push(index);
|
|
2535
|
+
consider(indices);
|
|
2536
|
+
}
|
|
2537
|
+
for (let stride = 1; stride <= anchorIndices.length; stride++) consider(anchorIndices.filter((_, position) => position % stride === 0));
|
|
2538
|
+
if (!candidates.length) return [domain[0]];
|
|
2539
|
+
const densest = candidates.reduce((most, indices) => Math.max(most, indices.length), 0);
|
|
2540
|
+
const anchorsIn = (indices) => isAnchor ? indices.filter((index) => isAnchor(domain[index])).length : 0;
|
|
2541
|
+
return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
|
|
2542
|
+
const gain = anchorsIn(indices) - anchorsIn(chosen);
|
|
2543
|
+
return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
|
|
2544
|
+
}).map((index) => domain[index]);
|
|
2283
2545
|
};
|
|
2284
2546
|
const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
|
|
2285
|
-
const
|
|
2547
|
+
const span = getSpan(data);
|
|
2548
|
+
if (!span) return 1;
|
|
2549
|
+
const xScale = (0, _visx_scale.scaleTime)({ domain: [span.minX, span.maxX] });
|
|
2286
2550
|
const upperBound = Math.min(data[0]?.data.length || 3, Math.ceil(chartWidth / X_TICK_WIDTH));
|
|
2287
2551
|
let secondBestGuess = 1;
|
|
2288
2552
|
for (let numTicks = upperBound; numTicks > 1; --numTicks) {
|
|
@@ -2319,9 +2583,11 @@ function withResponsive(WrappedComponent) {
|
|
|
2319
2583
|
enableDebounceLeadingCall: true
|
|
2320
2584
|
});
|
|
2321
2585
|
const hasAspectRatio = aspectRatio !== void 0 && aspectRatio > 0;
|
|
2586
|
+
const [scopeNode, setScopeNode] = (0, react.useState)(null);
|
|
2322
2587
|
const wrapperRef = (0, react.useRef)(null);
|
|
2323
2588
|
const setWrapperRef = (0, react.useCallback)((node) => {
|
|
2324
2589
|
wrapperRef.current = node;
|
|
2590
|
+
setScopeNode(node);
|
|
2325
2591
|
if (typeof parentRef === "function") parentRef(node);
|
|
2326
2592
|
else if (parentRef) parentRef.current = node;
|
|
2327
2593
|
}, [parentRef]);
|
|
@@ -2367,14 +2633,17 @@ function withResponsive(WrappedComponent) {
|
|
|
2367
2633
|
...width !== void 0 ? { width } : null,
|
|
2368
2634
|
...height !== void 0 ? { height } : null
|
|
2369
2635
|
},
|
|
2370
|
-
children:
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2636
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartScopeContext.Provider, {
|
|
2637
|
+
value: scopeNode,
|
|
2638
|
+
children: hasAspectRatio ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2639
|
+
className: with_responsive_module_default.content,
|
|
2640
|
+
style: {
|
|
2641
|
+
width: boxWidth,
|
|
2642
|
+
height: boxHeight
|
|
2643
|
+
},
|
|
2644
|
+
children: wrappedComponent
|
|
2645
|
+
}) : wrappedComponent
|
|
2646
|
+
})
|
|
2378
2647
|
});
|
|
2379
2648
|
};
|
|
2380
2649
|
}
|
|
@@ -2809,11 +3078,12 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
|
|
|
2809
3078
|
};
|
|
2810
3079
|
const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
|
|
2811
3080
|
const providerTheme = useGlobalChartsTheme();
|
|
3081
|
+
const scopeElement = useChartScopeElement();
|
|
2812
3082
|
const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext) || {};
|
|
2813
3083
|
const labelRef = (0, react.useRef)(null);
|
|
2814
3084
|
const [height, setHeight] = (0, react.useState)(null);
|
|
2815
3085
|
const styles = (0, deepmerge.default)(providerTheme.annotationStyles ?? {}, datumStyles ?? {});
|
|
2816
|
-
const resolveColor = (value) => value ? resolveCssVariable(value) ?? value : value;
|
|
3086
|
+
const resolveColor = (value) => value ? resolveCssVariable(value, scopeElement) ?? value : value;
|
|
2817
3087
|
(0, react.useEffect)(() => {
|
|
2818
3088
|
if (labelRef.current?.getBBox) {
|
|
2819
3089
|
const bbox = labelRef.current.getBBox();
|
|
@@ -3060,8 +3330,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
3060
3330
|
const legendShape = legend.shape ?? "line";
|
|
3061
3331
|
const legendPosition = legend.position ?? "bottom";
|
|
3062
3332
|
const providerTheme = useGlobalChartsTheme();
|
|
3063
|
-
const resolvedBackgroundColor = resolveCssVariable(providerTheme.backgroundColor) ?? providerTheme.backgroundColor;
|
|
3064
3333
|
const theme = useXYChartTheme(data);
|
|
3334
|
+
const resolvedBackgroundColor = theme.backgroundColor ?? providerTheme.backgroundColor;
|
|
3065
3335
|
const chartId = useChartId(providedChartId);
|
|
3066
3336
|
const chartRef = (0, react.useRef)(null);
|
|
3067
3337
|
const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
|
|
@@ -3837,7 +4107,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
3837
4107
|
stacked,
|
|
3838
4108
|
stackOffset,
|
|
3839
4109
|
getElementStyles,
|
|
3840
|
-
strokeColor:
|
|
4110
|
+
strokeColor: theme.backgroundColor ?? providerTheme.backgroundColor
|
|
3841
4111
|
})] }),
|
|
3842
4112
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AreaChartScalesRef, {
|
|
3843
4113
|
chartRef: internalChartRef,
|
|
@@ -3997,11 +4267,73 @@ const TruncatedYTickComponent = createTruncatedTickComponent("y");
|
|
|
3997
4267
|
const BASE_BAND_PADDING = .2;
|
|
3998
4268
|
/** Inner padding of the category band scale (the base gap between ticks). */
|
|
3999
4269
|
const BASE_BAND_PADDING_INNER = .1;
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4270
|
+
/** Ticks each axis carries unless the caller asks for a different count. */
|
|
4271
|
+
const DEFAULT_NUM_TICKS = 4;
|
|
4272
|
+
const ALL_RENDERED = () => true;
|
|
4273
|
+
const TOOLTIP_FORMAT_BY_RESOLUTION = {
|
|
4274
|
+
hour: {
|
|
4275
|
+
year: "numeric",
|
|
4276
|
+
month: "long",
|
|
4277
|
+
day: "numeric",
|
|
4278
|
+
hour: "numeric",
|
|
4279
|
+
hour12: true
|
|
4280
|
+
},
|
|
4281
|
+
day: {
|
|
4282
|
+
year: "numeric",
|
|
4283
|
+
month: "long",
|
|
4003
4284
|
day: "numeric"
|
|
4004
|
-
}
|
|
4285
|
+
},
|
|
4286
|
+
month: {
|
|
4287
|
+
year: "numeric",
|
|
4288
|
+
month: "long"
|
|
4289
|
+
},
|
|
4290
|
+
year: { year: "numeric" }
|
|
4291
|
+
};
|
|
4292
|
+
/**
|
|
4293
|
+
* Labels one bar's bucket, at the bucket's own granularity — finer than the
|
|
4294
|
+
* ticks whenever the overall time span coarsens the axis.
|
|
4295
|
+
*
|
|
4296
|
+
* @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
|
|
4297
|
+
* @param tickResolution - Caller-declared bucket resolution, when known.
|
|
4298
|
+
* @return Tooltip label formatter.
|
|
4299
|
+
*/
|
|
4300
|
+
const getTooltipFormatter = (data, tickResolution) => {
|
|
4301
|
+
if (tickResolution === "week") return (timestamp) => (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("Week of %s", "jetpack-charts"), new Date(timestamp).toLocaleDateString(void 0, {
|
|
4302
|
+
year: "numeric",
|
|
4303
|
+
month: "long",
|
|
4304
|
+
day: "numeric"
|
|
4305
|
+
}));
|
|
4306
|
+
const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
|
|
4307
|
+
return (timestamp) => new Date(timestamp).toLocaleString(void 0, format);
|
|
4308
|
+
};
|
|
4309
|
+
const identity = (label) => label;
|
|
4310
|
+
const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
|
|
4311
|
+
const bucketAccessor = (d) => d?.label || d?.date;
|
|
4312
|
+
/**
|
|
4313
|
+
* The buckets the band axis can put a tick on.
|
|
4314
|
+
*
|
|
4315
|
+
* Built from the accessor visx is given, over the same series in the same order,
|
|
4316
|
+
* so that a tick value is a key the scale actually holds; `scaleBand` returns
|
|
4317
|
+
* `undefined` for one it does not, which parks the tick at the origin. Null
|
|
4318
|
+
* rather than a partial list when any bucket is labelled instead of dated.
|
|
4319
|
+
*
|
|
4320
|
+
* @param data - Every series handed to the chart.
|
|
4321
|
+
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
4322
|
+
* @return Bucket dates in axis order, or null if the axis cannot be ticked by date.
|
|
4323
|
+
*/
|
|
4324
|
+
const getBandDomain = (data, isSeriesRendered) => {
|
|
4325
|
+
const registry = {};
|
|
4326
|
+
for (const series of data) {
|
|
4327
|
+
if (series.options?.type === "comparison" || !isSeriesRendered(series)) continue;
|
|
4328
|
+
registry[series.label] = series;
|
|
4329
|
+
}
|
|
4330
|
+
const byTimestamp = /* @__PURE__ */ new Map();
|
|
4331
|
+
for (const key of Object.keys(registry)) for (const point of registry[key].data) {
|
|
4332
|
+
const bucket = bucketAccessor(point);
|
|
4333
|
+
if (!(bucket instanceof Date)) return null;
|
|
4334
|
+
if (!byTimestamp.has(bucket.getTime())) byTimestamp.set(bucket.getTime(), bucket);
|
|
4335
|
+
}
|
|
4336
|
+
return byTimestamp.size ? [...byTimestamp.values()] : null;
|
|
4005
4337
|
};
|
|
4006
4338
|
/**
|
|
4007
4339
|
* Get the group padding of a scale.
|
|
@@ -4015,12 +4347,26 @@ const getGroupPadding = (scale) => {
|
|
|
4015
4347
|
/**
|
|
4016
4348
|
* Returns the merged options for the bar chart, including axis and scale configuration based on the orientation.
|
|
4017
4349
|
*
|
|
4018
|
-
* @param data
|
|
4019
|
-
* @param horizontal
|
|
4020
|
-
* @param options
|
|
4350
|
+
* @param data - The data to be displayed in the chart.
|
|
4351
|
+
* @param horizontal - Whether the chart is horizontal or vertical.
|
|
4352
|
+
* @param options - The options for the chart.
|
|
4353
|
+
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
4021
4354
|
* @return The merged options for the chart.
|
|
4022
4355
|
*/
|
|
4023
|
-
function useBarChartOptions(data, horizontal, options = {}) {
|
|
4356
|
+
function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
|
|
4357
|
+
const stableOptions = useDeepMemo(options);
|
|
4358
|
+
const axisConfig = (0, react.useMemo)(() => {
|
|
4359
|
+
const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, ...xAxisOptions } = stableOptions.axis?.x || {};
|
|
4360
|
+
const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, ...yAxisOptions } = stableOptions.axis?.y || {};
|
|
4361
|
+
return {
|
|
4362
|
+
xLabelOverflow,
|
|
4363
|
+
yLabelOverflow,
|
|
4364
|
+
xAxisOptions,
|
|
4365
|
+
yAxisOptions,
|
|
4366
|
+
tickResolution: horizontal ? yTickResolution : xTickResolution
|
|
4367
|
+
};
|
|
4368
|
+
}, [stableOptions, horizontal]);
|
|
4369
|
+
const { tickResolution } = axisConfig;
|
|
4024
4370
|
const defaultOptions = (0, react.useMemo)(() => {
|
|
4025
4371
|
const bandScale = {
|
|
4026
4372
|
type: "band",
|
|
@@ -4032,19 +4378,26 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4032
4378
|
nice: true,
|
|
4033
4379
|
zero: false
|
|
4034
4380
|
};
|
|
4035
|
-
const
|
|
4381
|
+
const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
|
|
4382
|
+
const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
|
|
4383
|
+
const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
|
|
4384
|
+
const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
|
|
4036
4385
|
const valueFormatter = _automattic_number_formatters.formatNumberCompact;
|
|
4037
|
-
const
|
|
4386
|
+
const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
|
|
4038
4387
|
const valueAccessor = (d) => {
|
|
4039
4388
|
const enhancedPoint = d;
|
|
4040
4389
|
return enhancedPoint?.visualValue !== void 0 ? enhancedPoint.visualValue : d?.value;
|
|
4041
4390
|
};
|
|
4042
4391
|
return {
|
|
4392
|
+
timeAxis: bandDomain && timeTickFormatter && {
|
|
4393
|
+
domain: bandDomain,
|
|
4394
|
+
tickFormatter: timeTickFormatter
|
|
4395
|
+
},
|
|
4043
4396
|
vertical: {
|
|
4044
4397
|
xTickFormat: labelFormatter,
|
|
4045
4398
|
yTickFormat: valueFormatter,
|
|
4046
|
-
tooltipLabelFormatter:
|
|
4047
|
-
xAccessor:
|
|
4399
|
+
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
4400
|
+
xAccessor: bucketAccessor,
|
|
4048
4401
|
yAccessor: valueAccessor,
|
|
4049
4402
|
gridVisibility: "x",
|
|
4050
4403
|
xScale: bandScale,
|
|
@@ -4053,20 +4406,24 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4053
4406
|
horizontal: {
|
|
4054
4407
|
xTickFormat: valueFormatter,
|
|
4055
4408
|
yTickFormat: labelFormatter,
|
|
4056
|
-
tooltipLabelFormatter:
|
|
4409
|
+
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
4057
4410
|
xAccessor: valueAccessor,
|
|
4058
|
-
yAccessor:
|
|
4411
|
+
yAccessor: bucketAccessor,
|
|
4059
4412
|
gridVisibility: "y",
|
|
4060
4413
|
xScale: linearScale,
|
|
4061
4414
|
yScale: bandScale
|
|
4062
4415
|
}
|
|
4063
4416
|
};
|
|
4064
|
-
}, [
|
|
4417
|
+
}, [
|
|
4418
|
+
data,
|
|
4419
|
+
tickResolution,
|
|
4420
|
+
isSeriesRendered
|
|
4421
|
+
]);
|
|
4065
4422
|
return (0, react.useMemo)(() => {
|
|
4066
4423
|
const { xTickFormat, yTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
|
|
4067
4424
|
let valueScaleDomainOverride = {};
|
|
4068
4425
|
if (data.some((s) => s.options?.type === "comparison")) {
|
|
4069
|
-
if (!(!horizontal ?
|
|
4426
|
+
if (!(!horizontal ? stableOptions.yScale?.domain : stableOptions.xScale?.domain)) {
|
|
4070
4427
|
const allValues = [];
|
|
4071
4428
|
data.forEach((series) => {
|
|
4072
4429
|
series.data.forEach((d) => {
|
|
@@ -4080,17 +4437,20 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4080
4437
|
}
|
|
4081
4438
|
const xScale = {
|
|
4082
4439
|
...baseXScale,
|
|
4083
|
-
...
|
|
4440
|
+
...stableOptions.xScale || {},
|
|
4084
4441
|
...horizontal ? valueScaleDomainOverride : {}
|
|
4085
4442
|
};
|
|
4086
4443
|
const yScale = {
|
|
4087
4444
|
...baseYScale,
|
|
4088
|
-
...
|
|
4445
|
+
...stableOptions.yScale || {},
|
|
4089
4446
|
...!horizontal ? valueScaleDomainOverride : {}
|
|
4090
4447
|
};
|
|
4091
|
-
const
|
|
4092
|
-
const
|
|
4093
|
-
const {
|
|
4448
|
+
const { xLabelOverflow, yLabelOverflow, xAxisOptions, yAxisOptions } = axisConfig;
|
|
4449
|
+
const providedToolTipLabelFormatter = horizontal ? yAxisOptions.tickFormat : xAxisOptions.tickFormat;
|
|
4450
|
+
const { timeAxis } = defaultOptions;
|
|
4451
|
+
const dateAxisOptions = horizontal ? yAxisOptions : xAxisOptions;
|
|
4452
|
+
const bandTickValues = timeAxis && !dateAxisOptions.tickFormat ? getBandTickValues(timeAxis.domain, timeAxis.tickFormatter, dateAxisOptions.numTicks ?? DEFAULT_NUM_TICKS) : null;
|
|
4453
|
+
const dateAxisTickValues = bandTickValues ? { tickValues: bandTickValues } : {};
|
|
4094
4454
|
return {
|
|
4095
4455
|
gridVisibility,
|
|
4096
4456
|
xScale,
|
|
@@ -4102,15 +4462,17 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4102
4462
|
axis: {
|
|
4103
4463
|
x: {
|
|
4104
4464
|
orientation: "bottom",
|
|
4105
|
-
numTicks:
|
|
4465
|
+
numTicks: DEFAULT_NUM_TICKS,
|
|
4106
4466
|
tickFormat: xTickFormat,
|
|
4467
|
+
...horizontal ? {} : dateAxisTickValues,
|
|
4107
4468
|
...xLabelOverflow === "ellipsis" ? { tickComponent: TruncatedXTickComponent } : {},
|
|
4108
4469
|
...xAxisOptions
|
|
4109
4470
|
},
|
|
4110
4471
|
y: {
|
|
4111
4472
|
orientation: "left",
|
|
4112
|
-
numTicks:
|
|
4473
|
+
numTicks: DEFAULT_NUM_TICKS,
|
|
4113
4474
|
tickFormat: yTickFormat,
|
|
4475
|
+
...horizontal ? dateAxisTickValues : {},
|
|
4114
4476
|
...yLabelOverflow === "ellipsis" ? { tickComponent: TruncatedYTickComponent } : {},
|
|
4115
4477
|
...yAxisOptions
|
|
4116
4478
|
}
|
|
@@ -4120,7 +4482,8 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4120
4482
|
};
|
|
4121
4483
|
}, [
|
|
4122
4484
|
defaultOptions,
|
|
4123
|
-
|
|
4485
|
+
axisConfig,
|
|
4486
|
+
stableOptions,
|
|
4124
4487
|
horizontal,
|
|
4125
4488
|
data
|
|
4126
4489
|
]);
|
|
@@ -4282,7 +4645,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4282
4645
|
valueAxisLength: horizontal ? width : height
|
|
4283
4646
|
});
|
|
4284
4647
|
const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({ collapseGroups: legendCollapseGroups }), [legendCollapseGroups]));
|
|
4285
|
-
const
|
|
4648
|
+
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
4649
|
+
const isSeriesRendered = (0, react.useCallback)((series) => !chartId || !legendInteractive || isSeriesVisible(chartId, series.label), [
|
|
4650
|
+
chartId,
|
|
4651
|
+
legendInteractive,
|
|
4652
|
+
isSeriesVisible
|
|
4653
|
+
]);
|
|
4654
|
+
const chartOptions = useBarChartOptions(dataWithVisibleZeros, horizontal, options, isSeriesRendered);
|
|
4286
4655
|
const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme, horizontal);
|
|
4287
4656
|
const chartRef = (0, react.useRef)(null);
|
|
4288
4657
|
const { legendChildren, nonLegendChildren } = useChartChildren(children, "BarChart");
|
|
@@ -4301,24 +4670,11 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4301
4670
|
chartRef,
|
|
4302
4671
|
totalPoints: Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length
|
|
4303
4672
|
});
|
|
4304
|
-
const
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
isVisible: true
|
|
4310
|
-
}));
|
|
4311
|
-
return dataWithVisibleZeros.map((series, index) => ({
|
|
4312
|
-
series,
|
|
4313
|
-
index,
|
|
4314
|
-
isVisible: isSeriesVisible(chartId, series.label)
|
|
4315
|
-
}));
|
|
4316
|
-
}, [
|
|
4317
|
-
dataWithVisibleZeros,
|
|
4318
|
-
chartId,
|
|
4319
|
-
isSeriesVisible,
|
|
4320
|
-
legendInteractive
|
|
4321
|
-
]);
|
|
4673
|
+
const seriesWithVisibility = (0, react.useMemo)(() => dataWithVisibleZeros.map((series, index) => ({
|
|
4674
|
+
series,
|
|
4675
|
+
index,
|
|
4676
|
+
isVisible: isSeriesRendered(series)
|
|
4677
|
+
})), [dataWithVisibleZeros, isSeriesRendered]);
|
|
4322
4678
|
const allSeriesHidden = (0, react.useMemo)(() => {
|
|
4323
4679
|
return seriesWithVisibility.every(({ isVisible }) => !isVisible);
|
|
4324
4680
|
}, [seriesWithVisibility]);
|
|
@@ -4878,12 +5234,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
4878
5234
|
const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
|
|
4879
5235
|
const chartRef = (0, react.useRef)(null);
|
|
4880
5236
|
const selectedBarRef = (0, react.useRef)(null);
|
|
5237
|
+
const [scopeNode, setScopeNode] = (0, react.useState)(null);
|
|
4881
5238
|
const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
|
|
4882
5239
|
const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
|
|
4883
5240
|
const { containerRef: portalContainerRef, TooltipInPortal, containerBounds } = (0, _visx_tooltip.useTooltipInPortal)({
|
|
4884
5241
|
detectBounds: true,
|
|
4885
5242
|
scroll: true
|
|
4886
5243
|
});
|
|
5244
|
+
const portalContainerRefRef = (0, react.useRef)(portalContainerRef);
|
|
5245
|
+
(0, react.useEffect)(() => {
|
|
5246
|
+
portalContainerRefRef.current = portalContainerRef;
|
|
5247
|
+
}, [portalContainerRef]);
|
|
5248
|
+
const setChartRef = (0, react.useCallback)((node) => {
|
|
5249
|
+
portalContainerRefRef.current(node);
|
|
5250
|
+
chartRef.current = node;
|
|
5251
|
+
setScopeNode(node);
|
|
5252
|
+
}, []);
|
|
4887
5253
|
const clearSelectionAndRef = (0, react.useCallback)(() => {
|
|
4888
5254
|
clearSelection();
|
|
4889
5255
|
selectedBarRef.current = null;
|
|
@@ -5029,6 +5395,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5029
5395
|
direction: "column",
|
|
5030
5396
|
align: "center",
|
|
5031
5397
|
justify: "center",
|
|
5398
|
+
ref: setScopeNode,
|
|
5032
5399
|
className: (0, clsx.default)(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5033
5400
|
style: {
|
|
5034
5401
|
...style,
|
|
@@ -5040,80 +5407,80 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5040
5407
|
})
|
|
5041
5408
|
});
|
|
5042
5409
|
const maxRate = Math.max(...steps.map((step) => step.rate));
|
|
5043
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5410
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
5044
5411
|
direction: "column",
|
|
5045
5412
|
gap: "xl",
|
|
5046
|
-
ref:
|
|
5047
|
-
portalContainerRef(node);
|
|
5048
|
-
chartRef.current = node;
|
|
5049
|
-
},
|
|
5413
|
+
ref: setChartRef,
|
|
5050
5414
|
className: (0, clsx.default)(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5051
5415
|
style: {
|
|
5052
5416
|
...style,
|
|
5053
5417
|
height: resolvedHeight
|
|
5054
5418
|
},
|
|
5055
|
-
children:
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
gap: "xl",
|
|
5078
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
|
|
5419
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartScopeContext.Provider, {
|
|
5420
|
+
value: scopeNode,
|
|
5421
|
+
children: [renderMainMetric ? renderMainMetric({
|
|
5422
|
+
mainRate,
|
|
5423
|
+
changeIndicator,
|
|
5424
|
+
className: conversion_funnel_chart_module_default["main-metric"],
|
|
5425
|
+
changeColor
|
|
5426
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
5427
|
+
direction: "row",
|
|
5428
|
+
align: "baseline",
|
|
5429
|
+
gap: "sm",
|
|
5430
|
+
className: conversion_funnel_chart_module_default["main-metric"],
|
|
5431
|
+
children: renderDefaultMainMetric()
|
|
5432
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
5433
|
+
direction: "row",
|
|
5434
|
+
align: "flex-end",
|
|
5435
|
+
gap: "lg",
|
|
5436
|
+
className: conversion_funnel_chart_module_default["funnel-container"],
|
|
5437
|
+
children: steps.map((step, index) => {
|
|
5438
|
+
const barHeight = step.rate / maxRate * 100;
|
|
5439
|
+
const { isBlurred } = getStepState(step.id);
|
|
5440
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
|
|
5079
5441
|
direction: "column",
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5442
|
+
className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-step"], isColorPaletteResolved && conversion_funnel_chart_module_default["funnel-step--animated"], isBlurred && conversion_funnel_chart_module_default["funnel-step--blurred"]),
|
|
5443
|
+
gap: "xl",
|
|
5444
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
|
|
5445
|
+
direction: "column",
|
|
5446
|
+
gap: "xs",
|
|
5447
|
+
children: [renderStepLabel ? renderStepLabel({
|
|
5448
|
+
step,
|
|
5449
|
+
index,
|
|
5450
|
+
className: conversion_funnel_chart_module_default["step-label"]
|
|
5451
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5452
|
+
className: conversion_funnel_chart_module_default["step-label"],
|
|
5453
|
+
children: step.label
|
|
5454
|
+
}), renderStepRate ? renderStepRate({
|
|
5455
|
+
step,
|
|
5456
|
+
index,
|
|
5457
|
+
className: conversion_funnel_chart_module_default["step-rate"]
|
|
5458
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5459
|
+
className: conversion_funnel_chart_module_default["step-rate"],
|
|
5460
|
+
children: formatPercentage(step.rate)
|
|
5461
|
+
})]
|
|
5462
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
5463
|
+
direction: "column",
|
|
5464
|
+
justify: "flex-end",
|
|
5465
|
+
className: conversion_funnel_chart_module_default["bar-container"],
|
|
5466
|
+
onClick: stepHandlers.get(step.id)?.onClick,
|
|
5467
|
+
onKeyDown: stepHandlers.get(step.id)?.onKeyDown,
|
|
5468
|
+
role: "button",
|
|
5469
|
+
tabIndex: isBlurred ? -1 : 0,
|
|
5470
|
+
"aria-label": step.label,
|
|
5471
|
+
style: { backgroundColor: barBackgroundColor },
|
|
5472
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5473
|
+
className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
|
|
5474
|
+
style: {
|
|
5475
|
+
height: `${barHeight}%`,
|
|
5476
|
+
backgroundColor: barColor
|
|
5477
|
+
}
|
|
5478
|
+
})
|
|
5095
5479
|
})]
|
|
5096
|
-
}
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
onClick: stepHandlers.get(step.id)?.onClick,
|
|
5101
|
-
onKeyDown: stepHandlers.get(step.id)?.onKeyDown,
|
|
5102
|
-
role: "button",
|
|
5103
|
-
tabIndex: isBlurred ? -1 : 0,
|
|
5104
|
-
"aria-label": step.label,
|
|
5105
|
-
style: { backgroundColor: barBackgroundColor },
|
|
5106
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5107
|
-
className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
|
|
5108
|
-
style: {
|
|
5109
|
-
height: `${barHeight}%`,
|
|
5110
|
-
backgroundColor: barColor
|
|
5111
|
-
}
|
|
5112
|
-
})
|
|
5113
|
-
})]
|
|
5114
|
-
}, step.id);
|
|
5115
|
-
})
|
|
5116
|
-
})]
|
|
5480
|
+
}, step.id);
|
|
5481
|
+
})
|
|
5482
|
+
})]
|
|
5483
|
+
})
|
|
5117
5484
|
}), tooltipOpen && tooltipData && (() => {
|
|
5118
5485
|
const tooltipContent = renderTooltip ? renderTooltip({
|
|
5119
5486
|
step: tooltipData,
|
|
@@ -5126,7 +5493,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5126
5493
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
|
|
5127
5494
|
top: tooltipTop,
|
|
5128
5495
|
left: tooltipLeft,
|
|
5129
|
-
className: conversion_funnel_chart_module_default["tooltip-wrapper"],
|
|
5496
|
+
className: (0, clsx.default)("a8c-charts-scope", conversion_funnel_chart_module_default["tooltip-wrapper"]),
|
|
5130
5497
|
children: tooltipContent
|
|
5131
5498
|
}, Math.random());
|
|
5132
5499
|
})()] });
|
|
@@ -5276,6 +5643,7 @@ function normalizeGeoChartError(eventArgs) {
|
|
|
5276
5643
|
*/
|
|
5277
5644
|
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
|
|
5278
5645
|
const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
|
|
5646
|
+
const scopeElement = useChartScopeElement();
|
|
5279
5647
|
const containerRef = (0, react.useRef)(null);
|
|
5280
5648
|
const reportedErrorIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
5281
5649
|
(0, react.useEffect)(() => {
|
|
@@ -5308,8 +5676,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
5308
5676
|
});
|
|
5309
5677
|
const fullColorHex = getElementStyles({ index: 0 }).color;
|
|
5310
5678
|
const lightColorHex = lightenHexColor(fullColorHex, .8);
|
|
5311
|
-
const backgroundColorHex = normalizeColorToHex(backgroundColor,
|
|
5312
|
-
const defaultFillColorHex = normalizeColorToHex(featureFillColor,
|
|
5679
|
+
const backgroundColorHex = normalizeColorToHex(backgroundColor, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
|
|
5680
|
+
const defaultFillColorHex = normalizeColorToHex(featureFillColor, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
|
|
5313
5681
|
const sanitizedData = (0, react.useMemo)(() => {
|
|
5314
5682
|
if (data.length === 0) return {
|
|
5315
5683
|
data,
|
|
@@ -5569,7 +5937,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
5569
5937
|
const CELL_MIX_FLOOR = .15;
|
|
5570
5938
|
const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = [], primaryColor, gap = "md", withTooltips = false, renderTooltip, children }) => {
|
|
5571
5939
|
const chartId = useChartId(providedChartId);
|
|
5572
|
-
const { getElementStyles,
|
|
5940
|
+
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
5941
|
+
const scopeElement = useChartScopeElement();
|
|
5573
5942
|
const { heatmapChart: heatmapChartSettings } = theme;
|
|
5574
5943
|
const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
|
|
5575
5944
|
const [selectedIndex, setSelectedIndex] = (0, react.useState)();
|
|
@@ -5584,7 +5953,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
5584
5953
|
index: 0,
|
|
5585
5954
|
overrideColor: primaryColor || heatmapChartSettings.primaryColor
|
|
5586
5955
|
});
|
|
5587
|
-
const chartBackgroundHex =
|
|
5956
|
+
const chartBackgroundHex = normalizeColorToHex(theme.backgroundColor, scopeElement, resolveCssVariable);
|
|
5588
5957
|
const primaryHex = normalizeColorToHex(primaryColorHex);
|
|
5589
5958
|
const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + (1 - CELL_MIX_FLOOR) * intensity)));
|
|
5590
5959
|
const extent = (0, react.useMemo)(() => getValueExtent(data), [data]);
|
|
@@ -5811,6 +6180,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
5811
6180
|
top: tooltipTop,
|
|
5812
6181
|
left: tooltipLeft,
|
|
5813
6182
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6183
|
+
className: "a8c-charts-scope",
|
|
5814
6184
|
role: "tooltip",
|
|
5815
6185
|
tabIndex: -1,
|
|
5816
6186
|
children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
|
|
@@ -9146,6 +9516,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9146
9516
|
top: tooltipTop || 0,
|
|
9147
9517
|
left: tooltipLeft || 0,
|
|
9148
9518
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9519
|
+
className: "a8c-charts-scope",
|
|
9149
9520
|
role: "tooltip",
|
|
9150
9521
|
children: renderTooltip({ tooltipData })
|
|
9151
9522
|
})
|
|
@@ -9444,6 +9815,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9444
9815
|
top: tooltipTop || 0,
|
|
9445
9816
|
left: tooltipLeft || 0,
|
|
9446
9817
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9818
|
+
className: "a8c-charts-scope",
|
|
9447
9819
|
role: "tooltip",
|
|
9448
9820
|
children: renderTooltip({ tooltipData })
|
|
9449
9821
|
})
|
|
@@ -9731,7 +10103,7 @@ const Icon = ({ direction }) => {
|
|
|
9731
10103
|
function TrendIndicator({ direction, value, className, style, showIcon = true }) {
|
|
9732
10104
|
const ariaLabel = `${DIRECTION_LABELS[direction]}: ${value}`;
|
|
9733
10105
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
9734
|
-
className: (0, clsx.default)(trend_indicator_module_default["trend-indicator"], trend_indicator_module_default[`trend-indicator--${direction}`], className),
|
|
10106
|
+
className: (0, clsx.default)(useStandaloneScopeClass(), trend_indicator_module_default["trend-indicator"], trend_indicator_module_default[`trend-indicator--${direction}`], className),
|
|
9735
10107
|
style,
|
|
9736
10108
|
"aria-label": ariaLabel,
|
|
9737
10109
|
children: [showIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { direction }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -9785,7 +10157,10 @@ exports.parseHslString = parseHslString;
|
|
|
9785
10157
|
exports.parseRgbString = parseRgbString;
|
|
9786
10158
|
exports.prefersLightText = prefersLightText;
|
|
9787
10159
|
exports.relativeLuminance = relativeLuminance;
|
|
10160
|
+
exports.resolveCssVariable = resolveCssVariable;
|
|
9788
10161
|
exports.useChartLegendItems = useChartLegendItems;
|
|
10162
|
+
exports.useChartRegistration = useChartRegistration;
|
|
10163
|
+
exports.useChartScopeElement = useChartScopeElement;
|
|
9789
10164
|
exports.useGlobalChartsContext = useGlobalChartsContext;
|
|
9790
10165
|
exports.useGlobalChartsTheme = useGlobalChartsTheme;
|
|
9791
10166
|
exports.useLeaderboardLegendItems = useLeaderboardLegendItems;
|