@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.js
CHANGED
|
@@ -62,6 +62,14 @@ const useChartInstanceContext = () => {
|
|
|
62
62
|
};
|
|
63
63
|
const useSingleChartContext = useChartInstanceContext;
|
|
64
64
|
//#endregion
|
|
65
|
+
//#region src/styles/chart-scope-class.ts
|
|
66
|
+
/**
|
|
67
|
+
* 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.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately unhashed: consumers are documented as being able to target it to override a token on the provider element.
|
|
70
|
+
*/
|
|
71
|
+
const CHART_SCOPE_CLASS = "a8c-charts-scope";
|
|
72
|
+
//#endregion
|
|
65
73
|
//#region src/utils/create-composition.ts
|
|
66
74
|
/**
|
|
67
75
|
* Utility function to create chart components with composition API.
|
|
@@ -508,15 +516,31 @@ const CSS_VAR_NAME_PATTERN = /^--[\w-]+$/;
|
|
|
508
516
|
* @param element - Optional DOM element to resolve the variable from (defaults to document.documentElement)
|
|
509
517
|
* @return The resolved value, fallback value, or null if unresolvable
|
|
510
518
|
*/
|
|
511
|
-
const resolveCssVariable = (value, element) =>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
519
|
+
const resolveCssVariable = (value, element) => createCssVariableResolver(element)(value);
|
|
520
|
+
/**
|
|
521
|
+
* Builds a resolver that reads every value from one `getComputedStyle` snapshot.
|
|
522
|
+
*
|
|
523
|
+
* `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.
|
|
524
|
+
*
|
|
525
|
+
* @param element - The element to resolve against, or null/undefined for the document root.
|
|
526
|
+
* @return A resolver that resolves each value exactly as `resolveCssVariable` does.
|
|
527
|
+
*/
|
|
528
|
+
function createCssVariableResolver(element) {
|
|
529
|
+
let styles = null;
|
|
530
|
+
const getStyles = () => {
|
|
531
|
+
if (!styles) styles = computedStyleFor(element);
|
|
532
|
+
return styles;
|
|
533
|
+
};
|
|
534
|
+
return (value) => {
|
|
535
|
+
if (!value) return null;
|
|
536
|
+
if (value.startsWith("var(") && value.endsWith(")")) {
|
|
537
|
+
const parsed = parseVarExpression(value);
|
|
538
|
+
if (parsed) return readCustomProperty(parsed.varName, getStyles()) || parsed.fallback;
|
|
539
|
+
}
|
|
540
|
+
if (value.startsWith("--")) return readCustomProperty(value, getStyles());
|
|
541
|
+
return value;
|
|
542
|
+
};
|
|
543
|
+
}
|
|
520
544
|
/**
|
|
521
545
|
* Parses a var() expression into its variable name and optional fallback.
|
|
522
546
|
* Uses string manipulation instead of complex regex to avoid ReDoS.
|
|
@@ -544,17 +568,30 @@ function parseVarExpression(expr) {
|
|
|
544
568
|
};
|
|
545
569
|
}
|
|
546
570
|
/**
|
|
547
|
-
*
|
|
571
|
+
* Takes one computed-style snapshot, or null where there is nothing to read (SSR, or a detached element).
|
|
572
|
+
*
|
|
573
|
+
* @param element - The element to read from, or null/undefined for the document root.
|
|
574
|
+
* @return The computed styles, or null.
|
|
575
|
+
*/
|
|
576
|
+
function computedStyleFor(element) {
|
|
577
|
+
if (typeof window === "undefined" || typeof document === "undefined") return null;
|
|
578
|
+
try {
|
|
579
|
+
return getComputedStyle(element || document.documentElement);
|
|
580
|
+
} catch {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Reads one custom property from a computed-style snapshot.
|
|
548
586
|
*
|
|
549
587
|
* @param varName - A CSS variable name like '--my-color'
|
|
550
|
-
* @param
|
|
588
|
+
* @param styles - The snapshot to read from, or null
|
|
551
589
|
* @return The computed value or null
|
|
552
590
|
*/
|
|
553
|
-
function
|
|
554
|
-
if (
|
|
591
|
+
function readCustomProperty(varName, styles) {
|
|
592
|
+
if (!styles) return null;
|
|
555
593
|
try {
|
|
556
|
-
|
|
557
|
-
return getComputedStyle(targetElement).getPropertyValue(varName).trim() || null;
|
|
594
|
+
return styles.getPropertyValue(varName).trim() || null;
|
|
558
595
|
} catch {
|
|
559
596
|
return null;
|
|
560
597
|
}
|
|
@@ -589,6 +626,14 @@ const resolveFontSize = (val) => {
|
|
|
589
626
|
}
|
|
590
627
|
};
|
|
591
628
|
//#endregion
|
|
629
|
+
//#region src/providers/chart-scope/chart-scope-context.ts
|
|
630
|
+
/**
|
|
631
|
+
* The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
|
|
632
|
+
*
|
|
633
|
+
* 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.
|
|
634
|
+
*/
|
|
635
|
+
const ChartScopeContext = createContext(null);
|
|
636
|
+
//#endregion
|
|
592
637
|
//#region src/providers/chart-context/private/get-chart-color.ts
|
|
593
638
|
/**
|
|
594
639
|
* Golden ratio for mathematically pleasing color distribution
|
|
@@ -718,12 +763,75 @@ const getChartColor = (index, colorCache) => {
|
|
|
718
763
|
return hsl(Math.round(fallbackHue), fallbackSaturation / 100, fallbackLightness / 100).formatHex();
|
|
719
764
|
};
|
|
720
765
|
//#endregion
|
|
766
|
+
//#region src/providers/chart-context/private/theme-override-vars.ts
|
|
767
|
+
const ROLE_FOR_FIELD = [
|
|
768
|
+
["--a8c-charts-color-background", (theme) => theme.backgroundColor],
|
|
769
|
+
["--a8c-charts-color-grid", (theme) => theme.gridStyles?.stroke],
|
|
770
|
+
["--a8c-charts-color-axis", (theme) => theme.xAxisLineStyles?.stroke],
|
|
771
|
+
["--a8c-charts-color-tick", (theme) => theme.xTickLineStyles?.stroke],
|
|
772
|
+
["--a8c-charts-color-label-axis", (theme) => theme.svgLabelSmall?.fill]
|
|
773
|
+
];
|
|
774
|
+
/**
|
|
775
|
+
* The variable a `theme` prop override is published as, one layer outside the role itself.
|
|
776
|
+
*
|
|
777
|
+
* `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.
|
|
778
|
+
*
|
|
779
|
+
* @param role - The catalog role being overridden.
|
|
780
|
+
* @return The custom property carrying the consumer's value.
|
|
781
|
+
*/
|
|
782
|
+
const themeLayerVar = (role) => `${role}-theme`;
|
|
783
|
+
ROLE_FOR_FIELD.map(([role]) => role);
|
|
784
|
+
/**
|
|
785
|
+
* Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
|
|
786
|
+
*
|
|
787
|
+
* 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`.
|
|
788
|
+
*
|
|
789
|
+
* Three things this has to get right, none of which a plain substring test gets:
|
|
790
|
+
*
|
|
791
|
+
* `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
|
|
792
|
+
*
|
|
793
|
+
* 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.
|
|
794
|
+
*
|
|
795
|
+
* `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.
|
|
796
|
+
*
|
|
797
|
+
* @param value - The consumer's value for the field.
|
|
798
|
+
* @param role - The catalog role this value would override.
|
|
799
|
+
* @return True when the value reads `role`, so it must not be published.
|
|
800
|
+
*/
|
|
801
|
+
const readsOwnRole = (value, role) => value.trim() === role || new RegExp(`var\\(\\s*${role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(value);
|
|
802
|
+
/**
|
|
803
|
+
* 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.
|
|
804
|
+
*
|
|
805
|
+
* 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.
|
|
806
|
+
*
|
|
807
|
+
* @param theme - The consumer's `theme` prop, before merging with the default theme.
|
|
808
|
+
* @return The wrapper's custom properties, and the roles the consumer overrode.
|
|
809
|
+
*/
|
|
810
|
+
const themeOverrideVars = (theme) => {
|
|
811
|
+
if (!theme) return {
|
|
812
|
+
vars: {},
|
|
813
|
+
roles: []
|
|
814
|
+
};
|
|
815
|
+
const vars = {};
|
|
816
|
+
const roles = [];
|
|
817
|
+
for (const [role, read] of ROLE_FOR_FIELD) {
|
|
818
|
+
const value = read(theme);
|
|
819
|
+
if (typeof value !== "string" || value === "") continue;
|
|
820
|
+
roles.push(role);
|
|
821
|
+
if (!readsOwnRole(value, role)) vars[themeLayerVar(role)] = value;
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
vars,
|
|
825
|
+
roles
|
|
826
|
+
};
|
|
827
|
+
};
|
|
828
|
+
//#endregion
|
|
721
829
|
//#region src/providers/chart-context/themes.ts
|
|
722
830
|
/**
|
|
723
831
|
* Default theme configuration
|
|
724
832
|
*/
|
|
725
833
|
const defaultTheme = {
|
|
726
|
-
backgroundColor: "var(--a8c-charts-color-background,
|
|
834
|
+
backgroundColor: "var(--a8c-charts-color-background, #fff)",
|
|
727
835
|
labelBackgroundColor: "transparent",
|
|
728
836
|
labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
|
|
729
837
|
colors: [
|
|
@@ -734,59 +842,59 @@ const defaultTheme = {
|
|
|
734
842
|
"#FF8C8F"
|
|
735
843
|
],
|
|
736
844
|
gridStyles: {
|
|
737
|
-
stroke: "var(--a8c-charts-color-grid,
|
|
845
|
+
stroke: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
738
846
|
strokeWidth: 1
|
|
739
847
|
},
|
|
740
848
|
tickLength: 4,
|
|
741
849
|
gridColor: "",
|
|
742
850
|
gridColorDark: "",
|
|
743
851
|
xTickLineStyles: {
|
|
744
|
-
stroke: "var(--a8c-charts-color-tick,
|
|
852
|
+
stroke: "var(--a8c-charts-color-tick, #dbdbdb)",
|
|
745
853
|
strokeWidth: 1
|
|
746
854
|
},
|
|
747
855
|
xAxisLineStyles: {
|
|
748
|
-
stroke: "var(--a8c-charts-color-axis,
|
|
856
|
+
stroke: "var(--a8c-charts-color-axis, #dbdbdb)",
|
|
749
857
|
strokeWidth: 1
|
|
750
858
|
},
|
|
751
859
|
legend: {
|
|
752
|
-
labelStyles: { color: "var(--a8c-charts-color-label,
|
|
860
|
+
labelStyles: { color: "var(--a8c-charts-color-label, #1e1e1e)" },
|
|
753
861
|
containerStyles: {},
|
|
754
862
|
shapeStyles: []
|
|
755
863
|
},
|
|
756
864
|
seriesLineStyles: [],
|
|
757
865
|
glyphs: [],
|
|
758
866
|
svgLabelSmall: {
|
|
759
|
-
fill: "var(--a8c-charts-color-label
|
|
867
|
+
fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
760
868
|
fontFamily: "inherit"
|
|
761
869
|
},
|
|
762
870
|
svgLabelBig: { fontFamily: "inherit" },
|
|
763
871
|
annotationStyles: {
|
|
764
872
|
label: {
|
|
765
|
-
anchorLineStroke: "var(--a8c-charts-color-annotation,
|
|
766
|
-
backgroundFill: "var(--a8c-charts-color-
|
|
873
|
+
anchorLineStroke: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
874
|
+
backgroundFill: "var(--a8c-charts-color-surface, #fff)"
|
|
767
875
|
},
|
|
768
|
-
connector: { stroke: "var(--a8c-charts-color-annotation,
|
|
876
|
+
connector: { stroke: "var(--a8c-charts-color-annotation, #1e1e1e)" },
|
|
769
877
|
circleSubject: {
|
|
770
878
|
stroke: "transparent",
|
|
771
|
-
fill: "var(--a8c-charts-color-annotation,
|
|
879
|
+
fill: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
772
880
|
radius: 5
|
|
773
881
|
}
|
|
774
882
|
},
|
|
775
|
-
geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary,
|
|
883
|
+
geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)" },
|
|
776
884
|
leaderboardChart: {
|
|
777
885
|
rowGap: 12,
|
|
778
886
|
columnGap: 4,
|
|
779
887
|
labelSpacing: "xs",
|
|
780
888
|
deltaColors: [
|
|
781
|
-
"var(--a8c-charts-color-trend-down,
|
|
782
|
-
"var(--a8c-charts-color-trend-neutral,
|
|
783
|
-
"var(--a8c-charts-color-trend-up,
|
|
889
|
+
"var(--a8c-charts-color-trend-down, #cc1818)",
|
|
890
|
+
"var(--a8c-charts-color-trend-neutral, #707070)",
|
|
891
|
+
"var(--a8c-charts-color-trend-up, #008030)"
|
|
784
892
|
]
|
|
785
893
|
},
|
|
786
894
|
conversionFunnelChart: {
|
|
787
|
-
backgroundColor: "var(--a8c-charts-color-surface-secondary,
|
|
788
|
-
positiveChangeColor: "var(--a8c-charts-color-trend-up,
|
|
789
|
-
negativeChangeColor: "var(--a8c-charts-color-trend-down,
|
|
895
|
+
backgroundColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
896
|
+
positiveChangeColor: "var(--a8c-charts-color-trend-up, #008030)",
|
|
897
|
+
negativeChangeColor: "var(--a8c-charts-color-trend-down, #cc1818)"
|
|
790
898
|
},
|
|
791
899
|
lineChart: { lineStyles: { comparison: {
|
|
792
900
|
strokeDasharray: "4 4",
|
|
@@ -811,15 +919,65 @@ const defaultTheme = {
|
|
|
811
919
|
}
|
|
812
920
|
};
|
|
813
921
|
//#endregion
|
|
922
|
+
//#region src/providers/chart-context/private/with-catalog-pointers.ts
|
|
923
|
+
const CATALOG_RESTORE_FOR_ROLE = {
|
|
924
|
+
"--a8c-charts-color-background": () => ({ backgroundColor: defaultTheme.backgroundColor }),
|
|
925
|
+
"--a8c-charts-color-grid": (theme) => ({ gridStyles: {
|
|
926
|
+
...theme.gridStyles,
|
|
927
|
+
stroke: defaultTheme.gridStyles.stroke
|
|
928
|
+
} }),
|
|
929
|
+
"--a8c-charts-color-axis": (theme) => ({ xAxisLineStyles: {
|
|
930
|
+
...theme.xAxisLineStyles,
|
|
931
|
+
stroke: defaultTheme.xAxisLineStyles.stroke
|
|
932
|
+
} }),
|
|
933
|
+
"--a8c-charts-color-tick": (theme) => ({ xTickLineStyles: {
|
|
934
|
+
...theme.xTickLineStyles,
|
|
935
|
+
stroke: defaultTheme.xTickLineStyles.stroke
|
|
936
|
+
} }),
|
|
937
|
+
"--a8c-charts-color-label-axis": (theme) => ({ svgLabelSmall: {
|
|
938
|
+
...theme.svgLabelSmall,
|
|
939
|
+
fill: defaultTheme.svgLabelSmall.fill
|
|
940
|
+
} })
|
|
941
|
+
};
|
|
942
|
+
/**
|
|
943
|
+
* 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.
|
|
944
|
+
*
|
|
945
|
+
* 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`.
|
|
946
|
+
*
|
|
947
|
+
* 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.
|
|
948
|
+
*
|
|
949
|
+
* @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
|
|
950
|
+
* @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
|
|
951
|
+
* @return A new theme object; `merged` is not mutated.
|
|
952
|
+
*/
|
|
953
|
+
const withCatalogPointers = (merged, overriddenRoles) => {
|
|
954
|
+
let result = merged;
|
|
955
|
+
for (const role of overriddenRoles) {
|
|
956
|
+
const restore = CATALOG_RESTORE_FOR_ROLE[role];
|
|
957
|
+
if (restore) result = {
|
|
958
|
+
...result,
|
|
959
|
+
...restore(result)
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return result;
|
|
963
|
+
};
|
|
964
|
+
//#endregion
|
|
814
965
|
//#region src/providers/chart-context/global-charts-provider.tsx
|
|
815
966
|
const GlobalChartsContext = createContext(null);
|
|
816
967
|
const GlobalChartsProvider = ({ children, theme }) => {
|
|
817
968
|
const [charts, setCharts] = useState(() => /* @__PURE__ */ new Map());
|
|
818
969
|
const [hiddenSeries, setHiddenSeries] = useState(() => /* @__PURE__ */ new Map());
|
|
819
970
|
const wrapperRef = useRef(null);
|
|
971
|
+
const [scopeNode, setScopeNode] = useState(null);
|
|
972
|
+
const setWrapperNode = useCallback((node) => {
|
|
973
|
+
wrapperRef.current = node;
|
|
974
|
+
setScopeNode(node);
|
|
975
|
+
}, []);
|
|
976
|
+
const { vars: overrideVars, roles: overriddenRoles } = useMemo(() => themeOverrideVars(theme), [theme]);
|
|
820
977
|
const providerTheme = useMemo(() => {
|
|
821
|
-
|
|
822
|
-
|
|
978
|
+
if (!theme) return defaultTheme;
|
|
979
|
+
return withCatalogPointers(mergeThemes(defaultTheme, theme), overriddenRoles);
|
|
980
|
+
}, [theme, overriddenRoles]);
|
|
823
981
|
const [colorCache, setColorCache] = useState(() => ({
|
|
824
982
|
colors: [],
|
|
825
983
|
hues: [],
|
|
@@ -896,7 +1054,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
896
1054
|
}
|
|
897
1055
|
return getChartColor(index, colorCache);
|
|
898
1056
|
}, [colorCache, groupToColorMap]);
|
|
899
|
-
const resolveThemeColor = useCallback((value) => value ? normalizeColorToHex(value, wrapperRef.current, resolveCssVariable) : "", []);
|
|
900
1057
|
const getElementStyles = useCallback(({ data, index, overrideColor, legendShape }) => {
|
|
901
1058
|
const isSeriesData = data && typeof data === "object" && "data" in data && "options" in data;
|
|
902
1059
|
const isPointPercentageData = data && typeof data === "object" && "value" in data && typeof data.value === "number" && !("data" in data);
|
|
@@ -939,7 +1096,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
939
1096
|
getChartData,
|
|
940
1097
|
theme: providerTheme,
|
|
941
1098
|
getElementStyles,
|
|
942
|
-
resolveThemeColor,
|
|
943
1099
|
toggleSeriesVisibility,
|
|
944
1100
|
isSeriesVisible,
|
|
945
1101
|
getHiddenSeries,
|
|
@@ -951,7 +1107,6 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
951
1107
|
getChartData,
|
|
952
1108
|
providerTheme,
|
|
953
1109
|
getElementStyles,
|
|
954
|
-
resolveThemeColor,
|
|
955
1110
|
toggleSeriesVisibility,
|
|
956
1111
|
isSeriesVisible,
|
|
957
1112
|
getHiddenSeries,
|
|
@@ -960,9 +1115,16 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
960
1115
|
return /* @__PURE__ */ jsx(GlobalChartsContext.Provider, {
|
|
961
1116
|
value,
|
|
962
1117
|
children: /* @__PURE__ */ jsx("div", {
|
|
963
|
-
ref:
|
|
964
|
-
|
|
965
|
-
|
|
1118
|
+
ref: setWrapperNode,
|
|
1119
|
+
className: CHART_SCOPE_CLASS,
|
|
1120
|
+
style: {
|
|
1121
|
+
display: "contents",
|
|
1122
|
+
...overrideVars
|
|
1123
|
+
},
|
|
1124
|
+
children: /* @__PURE__ */ jsx(ChartScopeContext.Provider, {
|
|
1125
|
+
value: scopeNode,
|
|
1126
|
+
children
|
|
1127
|
+
})
|
|
966
1128
|
})
|
|
967
1129
|
});
|
|
968
1130
|
};
|
|
@@ -1022,12 +1184,33 @@ const useDeepMemo = (value) => {
|
|
|
1022
1184
|
return ref.current;
|
|
1023
1185
|
};
|
|
1024
1186
|
//#endregion
|
|
1187
|
+
//#region src/providers/chart-scope/use-chart-scope-element.ts
|
|
1188
|
+
/**
|
|
1189
|
+
* Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
|
|
1190
|
+
*
|
|
1191
|
+
* @return {HTMLElement | null} The nearest chart scope element, or `null`.
|
|
1192
|
+
*/
|
|
1193
|
+
const useChartScopeElement = () => useContext(ChartScopeContext);
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/providers/chart-scope/use-standalone-scope-class.ts
|
|
1196
|
+
/**
|
|
1197
|
+
* Returns the catalog class when there is no `GlobalChartsProvider` above this component, and `undefined` when there is.
|
|
1198
|
+
*
|
|
1199
|
+
* 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.
|
|
1200
|
+
*
|
|
1201
|
+
* @return The scope class name, or undefined when a provider is present.
|
|
1202
|
+
*/
|
|
1203
|
+
const useStandaloneScopeClass = () => useContext(GlobalChartsContext) ? void 0 : CHART_SCOPE_CLASS;
|
|
1204
|
+
//#endregion
|
|
1025
1205
|
//#region src/hooks/use-xychart-theme.ts
|
|
1026
|
-
const resolveColor = (value) => value ? resolveCssVariable(value) ?? value : value;
|
|
1027
1206
|
const useXYChartTheme = (data) => {
|
|
1028
1207
|
const theme = useGlobalChartsTheme();
|
|
1208
|
+
const scopeElement = useChartScopeElement();
|
|
1209
|
+
const seriesColorKey = JSON.stringify((data ?? []).map((series) => series.options?.stroke).filter((color) => Boolean(color)));
|
|
1029
1210
|
return useMemo(() => {
|
|
1030
|
-
const
|
|
1211
|
+
const resolve = createCssVariableResolver(scopeElement);
|
|
1212
|
+
const resolveColor = (value) => value ? resolve(value) ?? value : value;
|
|
1213
|
+
const seriesColors = JSON.parse(seriesColorKey);
|
|
1031
1214
|
return buildChartTheme({
|
|
1032
1215
|
...theme,
|
|
1033
1216
|
colors: [...seriesColors, ...theme.colors ?? []],
|
|
@@ -1049,7 +1232,11 @@ const useXYChartTheme = (data) => {
|
|
|
1049
1232
|
fill: resolveColor(theme.svgLabelSmall.fill)
|
|
1050
1233
|
}
|
|
1051
1234
|
});
|
|
1052
|
-
}, [
|
|
1235
|
+
}, [
|
|
1236
|
+
theme,
|
|
1237
|
+
seriesColorKey,
|
|
1238
|
+
scopeElement
|
|
1239
|
+
]);
|
|
1053
1240
|
};
|
|
1054
1241
|
//#endregion
|
|
1055
1242
|
//#region src/hooks/use-chart-data-transform.ts
|
|
@@ -1136,7 +1323,7 @@ const getXAxisLabelMetrics = (theme, orientation) => {
|
|
|
1136
1323
|
const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
1137
1324
|
const yTicks = useMemo(() => {
|
|
1138
1325
|
const allDataPoints = data.flatMap((series) => series.data);
|
|
1139
|
-
if (horizontal) return allDataPoints.map((d) => d.label ||
|
|
1326
|
+
if (horizontal) return allDataPoints.map((d) => d.label || d.date?.getTime());
|
|
1140
1327
|
if (options.axis?.y?.tickValues?.length) return options.axis.y.tickValues;
|
|
1141
1328
|
const minY = Math.min(...allDataPoints.map((d) => d.value));
|
|
1142
1329
|
const maxY = Math.max(...allDataPoints.map((d) => d.value));
|
|
@@ -1525,6 +1712,7 @@ const BaseLegend = forwardRef(({ items, className, orientation = "horizontal", a
|
|
|
1525
1712
|
const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
|
|
1526
1713
|
const theme = useGlobalChartsTheme();
|
|
1527
1714
|
const context = useContext(GlobalChartsContext);
|
|
1715
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
1528
1716
|
const legendScale = scaleOrdinal({
|
|
1529
1717
|
domain: items.map((item) => item.label),
|
|
1530
1718
|
range: items.map((item) => item.color)
|
|
@@ -1577,7 +1765,7 @@ const BaseLegend = forwardRef(({ items, className, orientation = "horizontal", a
|
|
|
1577
1765
|
justify: orientation === "horizontal" ? flexAlignment : void 0,
|
|
1578
1766
|
wrap: orientation === "horizontal" ? "wrap" : void 0,
|
|
1579
1767
|
role: "list",
|
|
1580
|
-
className: clsx(base_legend_module_default.legend, className),
|
|
1768
|
+
className: clsx(standaloneScopeClass, base_legend_module_default.legend, className),
|
|
1581
1769
|
style: theme.legend?.containerStyles,
|
|
1582
1770
|
children: labels.map((label, i) => {
|
|
1583
1771
|
const matchedItem = items[i];
|
|
@@ -1864,9 +2052,10 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
|
|
|
1864
2052
|
data,
|
|
1865
2053
|
className
|
|
1866
2054
|
});
|
|
2055
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
1867
2056
|
if (!renderContainer) return content;
|
|
1868
2057
|
return /* @__PURE__ */ jsx("div", {
|
|
1869
|
-
className: base_tooltip_module_default.tooltip,
|
|
2058
|
+
className: clsx(standaloneScopeClass, base_tooltip_module_default.tooltip),
|
|
1870
2059
|
style: {
|
|
1871
2060
|
top,
|
|
1872
2061
|
left,
|
|
@@ -1935,10 +2124,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
1935
2124
|
tabIndex: -1,
|
|
1936
2125
|
role: "tooltip",
|
|
1937
2126
|
"aria-atomic": "true",
|
|
1938
|
-
className: keyboardFocusedClassName,
|
|
2127
|
+
className: clsx(CHART_SCOPE_CLASS, keyboardFocusedClassName),
|
|
1939
2128
|
children: tooltipContent
|
|
1940
2129
|
}, `chart-tooltip-${selectedIndex}`);
|
|
1941
2130
|
return /* @__PURE__ */ jsx("div", {
|
|
2131
|
+
className: CHART_SCOPE_CLASS,
|
|
1942
2132
|
role: "tooltip",
|
|
1943
2133
|
"aria-live": "polite",
|
|
1944
2134
|
children: tooltipContent
|
|
@@ -2231,28 +2421,38 @@ const getCurveType = (type, smoothing) => {
|
|
|
2231
2421
|
default: return curveLinear;
|
|
2232
2422
|
}
|
|
2233
2423
|
};
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
});
|
|
2242
|
-
};
|
|
2243
|
-
const formatHourTick = (timestamp) => {
|
|
2244
|
-
return new Date(timestamp).toLocaleTimeString(void 0, {
|
|
2245
|
-
hour: "numeric",
|
|
2246
|
-
hour12: true
|
|
2247
|
-
});
|
|
2248
|
-
};
|
|
2249
|
-
const formatDateOrHourTick = (timestamp) => {
|
|
2250
|
-
const date = new Date(timestamp);
|
|
2251
|
-
return date.getHours() === 0 && date.getMinutes() === 0 ? formatDateTick$1(timestamp) : formatHourTick(timestamp);
|
|
2424
|
+
const dateTimeFormat = (options) => {
|
|
2425
|
+
let formatter;
|
|
2426
|
+
return (timestamp) => {
|
|
2427
|
+
if (!Number.isFinite(Number(timestamp))) return new Date(timestamp).toLocaleDateString();
|
|
2428
|
+
formatter = formatter ?? new Intl.DateTimeFormat(void 0, options);
|
|
2429
|
+
return formatter.format(timestamp);
|
|
2430
|
+
};
|
|
2252
2431
|
};
|
|
2253
|
-
const
|
|
2254
|
-
|
|
2255
|
-
|
|
2432
|
+
const formatYearTick = dateTimeFormat({ year: "numeric" });
|
|
2433
|
+
const formatDateTick = dateTimeFormat({
|
|
2434
|
+
month: "short",
|
|
2435
|
+
day: "numeric"
|
|
2436
|
+
});
|
|
2437
|
+
const formatHourTick = dateTimeFormat({
|
|
2438
|
+
hour: "numeric",
|
|
2439
|
+
hour12: true
|
|
2440
|
+
});
|
|
2441
|
+
const formatMonthTick = dateTimeFormat({ month: "short" });
|
|
2442
|
+
const boundaryFormat = (isAnchor, atBoundary, between) => {
|
|
2443
|
+
const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
|
|
2444
|
+
format.isAnchor = isAnchor;
|
|
2445
|
+
return format;
|
|
2446
|
+
};
|
|
2447
|
+
const formatDateOrHourTick = boundaryFormat((date) => date.getHours() === 0 && date.getMinutes() === 0, formatDateTick, formatHourTick);
|
|
2448
|
+
const formatMonthOrYearTick = boundaryFormat((date) => date.getMonth() === 0, formatYearTick, formatMonthTick);
|
|
2449
|
+
const getSpan = (sortedData) => {
|
|
2450
|
+
const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => first !== void 0 && last !== void 0);
|
|
2451
|
+
if (!bounds.length) return null;
|
|
2452
|
+
return {
|
|
2453
|
+
minX: Math.min(...bounds.map(([first]) => Number(first))),
|
|
2454
|
+
maxX: Math.max(...bounds.map(([, last]) => Number(last)))
|
|
2455
|
+
};
|
|
2256
2456
|
};
|
|
2257
2457
|
const getPointSpacingInHours = (sortedData) => {
|
|
2258
2458
|
return sortedData.reduce((spacing, datom) => datom.data.reduce((seriesSpacing, point, index) => {
|
|
@@ -2261,26 +2461,90 @@ const getPointSpacingInHours = (sortedData) => {
|
|
|
2261
2461
|
return Math.min(seriesSpacing, Math.abs(differenceInHours(point.date, previous.date)));
|
|
2262
2462
|
}, spacing), Number.POSITIVE_INFINITY);
|
|
2263
2463
|
};
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
week:
|
|
2268
|
-
|
|
2464
|
+
const SUB_DAILY_SPACING_HOURS = 23;
|
|
2465
|
+
const MONTHLY_SPACING_HOURS = 672;
|
|
2466
|
+
const getBucketResolution = (sortedData, tickResolution) => {
|
|
2467
|
+
if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
|
|
2468
|
+
const spacingInHours = getPointSpacingInHours(sortedData);
|
|
2469
|
+
if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
|
|
2470
|
+
if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
|
|
2471
|
+
return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
|
|
2269
2472
|
};
|
|
2270
2473
|
const getFormatter = (sortedData, tickResolution) => {
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2276
|
-
const diffInHours = Math.abs(differenceInHours(maxX, minX));
|
|
2277
|
-
if (
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2474
|
+
const resolution = getBucketResolution(sortedData, tickResolution);
|
|
2475
|
+
if (resolution === "year") return formatYearTick;
|
|
2476
|
+
if (resolution === "month") return formatMonthOrYearTick;
|
|
2477
|
+
const span = getSpan(sortedData);
|
|
2478
|
+
if (!span) return formatDateTick;
|
|
2479
|
+
const diffInHours = Math.abs(differenceInHours(span.maxX, span.minX));
|
|
2480
|
+
if (resolution === "hour") {
|
|
2481
|
+
if (diffInHours <= 24) return formatHourTick;
|
|
2482
|
+
if (diffInHours <= 168) return formatDateOrHourTick;
|
|
2483
|
+
}
|
|
2484
|
+
return Math.abs(differenceInYears(span.maxX, span.minX)) <= 1 ? formatDateTick : formatYearTick;
|
|
2485
|
+
};
|
|
2486
|
+
const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
|
|
2487
|
+
if (isAnchor(date)) indices.push(index);
|
|
2488
|
+
return indices;
|
|
2489
|
+
}, []);
|
|
2490
|
+
const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
|
|
2491
|
+
const steps = /* @__PURE__ */ new Set();
|
|
2492
|
+
for (let count = maxTicks; count > 1; count--) {
|
|
2493
|
+
steps.add(Math.max(1, Math.floor((length - 1) / (count - 1))));
|
|
2494
|
+
steps.add(Math.max(1, Math.ceil((length - 1) / (count - 1))));
|
|
2495
|
+
}
|
|
2496
|
+
if (anchorSpacing) {
|
|
2497
|
+
for (let divisor = 1; divisor <= anchorSpacing; divisor++) if (anchorSpacing % divisor === 0) steps.add(anchorSpacing / divisor);
|
|
2498
|
+
const minStep = Math.floor((length - 1) / Math.max(1, maxTicks)) + 1;
|
|
2499
|
+
steps.add(Math.ceil(minStep / anchorSpacing) * anchorSpacing);
|
|
2500
|
+
}
|
|
2501
|
+
return steps;
|
|
2502
|
+
};
|
|
2503
|
+
/**
|
|
2504
|
+
* Tick values for a band time axis.
|
|
2505
|
+
*
|
|
2506
|
+
* visx samples a band domain by index from offset zero, blind to which labels
|
|
2507
|
+
* carry a boundary and without collapsing repeats — so the tick that prints the
|
|
2508
|
+
* year or the date often isn't sampled at all, and a long series can show the
|
|
2509
|
+
* same label twice. Choose the values instead: sweep the evenly spaced
|
|
2510
|
+
* candidates, including those that step from anchor to anchor, and keep the one
|
|
2511
|
+
* that reaches the most anchors without thinning the axis or putting two
|
|
2512
|
+
* identical labels side by side.
|
|
2513
|
+
*
|
|
2514
|
+
* @param domain - Band domain, in axis order.
|
|
2515
|
+
* @param tickFormatter - Formatter the axis will render these values with.
|
|
2516
|
+
* @param maxTicks - Most ticks the axis should carry.
|
|
2517
|
+
* @return Values to hand the axis as `tickValues`.
|
|
2518
|
+
*/
|
|
2519
|
+
const getBandTickValues = (domain, tickFormatter, maxTicks) => {
|
|
2520
|
+
if (!domain.length) return [];
|
|
2521
|
+
const { isAnchor } = tickFormatter;
|
|
2522
|
+
const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
|
|
2523
|
+
const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
|
|
2524
|
+
const candidates = [];
|
|
2525
|
+
const consider = (indices) => {
|
|
2526
|
+
if (!indices.length || indices.length > maxTicks) return;
|
|
2527
|
+
if (!indices.some((index, position) => position > 0 && domainLabels[index] === domainLabels[indices[position - 1]])) candidates.push(indices);
|
|
2528
|
+
};
|
|
2529
|
+
const anchorSpacing = anchorIndices.length > 1 ? anchorIndices[1] - anchorIndices[0] : null;
|
|
2530
|
+
for (const step of getCandidateSteps(domain.length, maxTicks, anchorSpacing)) for (let offset = 0; offset < step; offset++) {
|
|
2531
|
+
const indices = [];
|
|
2532
|
+
for (let index = offset; index < domain.length; index += step) indices.push(index);
|
|
2533
|
+
consider(indices);
|
|
2534
|
+
}
|
|
2535
|
+
for (let stride = 1; stride <= anchorIndices.length; stride++) consider(anchorIndices.filter((_, position) => position % stride === 0));
|
|
2536
|
+
if (!candidates.length) return [domain[0]];
|
|
2537
|
+
const densest = candidates.reduce((most, indices) => Math.max(most, indices.length), 0);
|
|
2538
|
+
const anchorsIn = (indices) => isAnchor ? indices.filter((index) => isAnchor(domain[index])).length : 0;
|
|
2539
|
+
return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
|
|
2540
|
+
const gain = anchorsIn(indices) - anchorsIn(chosen);
|
|
2541
|
+
return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
|
|
2542
|
+
}).map((index) => domain[index]);
|
|
2281
2543
|
};
|
|
2282
2544
|
const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
|
|
2283
|
-
const
|
|
2545
|
+
const span = getSpan(data);
|
|
2546
|
+
if (!span) return 1;
|
|
2547
|
+
const xScale = scaleTime({ domain: [span.minX, span.maxX] });
|
|
2284
2548
|
const upperBound = Math.min(data[0]?.data.length || 3, Math.ceil(chartWidth / X_TICK_WIDTH));
|
|
2285
2549
|
let secondBestGuess = 1;
|
|
2286
2550
|
for (let numTicks = upperBound; numTicks > 1; --numTicks) {
|
|
@@ -2317,9 +2581,11 @@ function withResponsive(WrappedComponent) {
|
|
|
2317
2581
|
enableDebounceLeadingCall: true
|
|
2318
2582
|
});
|
|
2319
2583
|
const hasAspectRatio = aspectRatio !== void 0 && aspectRatio > 0;
|
|
2584
|
+
const [scopeNode, setScopeNode] = useState(null);
|
|
2320
2585
|
const wrapperRef = useRef(null);
|
|
2321
2586
|
const setWrapperRef = useCallback((node) => {
|
|
2322
2587
|
wrapperRef.current = node;
|
|
2588
|
+
setScopeNode(node);
|
|
2323
2589
|
if (typeof parentRef === "function") parentRef(node);
|
|
2324
2590
|
else if (parentRef) parentRef.current = node;
|
|
2325
2591
|
}, [parentRef]);
|
|
@@ -2365,14 +2631,17 @@ function withResponsive(WrappedComponent) {
|
|
|
2365
2631
|
...width !== void 0 ? { width } : null,
|
|
2366
2632
|
...height !== void 0 ? { height } : null
|
|
2367
2633
|
},
|
|
2368
|
-
children:
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2634
|
+
children: /* @__PURE__ */ jsx(ChartScopeContext.Provider, {
|
|
2635
|
+
value: scopeNode,
|
|
2636
|
+
children: hasAspectRatio ? /* @__PURE__ */ jsx("div", {
|
|
2637
|
+
className: with_responsive_module_default.content,
|
|
2638
|
+
style: {
|
|
2639
|
+
width: boxWidth,
|
|
2640
|
+
height: boxHeight
|
|
2641
|
+
},
|
|
2642
|
+
children: wrappedComponent
|
|
2643
|
+
}) : wrappedComponent
|
|
2644
|
+
})
|
|
2376
2645
|
});
|
|
2377
2646
|
};
|
|
2378
2647
|
}
|
|
@@ -2807,11 +3076,12 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
|
|
|
2807
3076
|
};
|
|
2808
3077
|
const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
|
|
2809
3078
|
const providerTheme = useGlobalChartsTheme();
|
|
3079
|
+
const scopeElement = useChartScopeElement();
|
|
2810
3080
|
const { xScale, yScale } = useContext(DataContext) || {};
|
|
2811
3081
|
const labelRef = useRef(null);
|
|
2812
3082
|
const [height, setHeight] = useState(null);
|
|
2813
3083
|
const styles = deepmerge(providerTheme.annotationStyles ?? {}, datumStyles ?? {});
|
|
2814
|
-
const resolveColor = (value) => value ? resolveCssVariable(value) ?? value : value;
|
|
3084
|
+
const resolveColor = (value) => value ? resolveCssVariable(value, scopeElement) ?? value : value;
|
|
2815
3085
|
useEffect(() => {
|
|
2816
3086
|
if (labelRef.current?.getBBox) {
|
|
2817
3087
|
const bbox = labelRef.current.getBBox();
|
|
@@ -3058,8 +3328,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3058
3328
|
const legendShape = legend.shape ?? "line";
|
|
3059
3329
|
const legendPosition = legend.position ?? "bottom";
|
|
3060
3330
|
const providerTheme = useGlobalChartsTheme();
|
|
3061
|
-
const resolvedBackgroundColor = resolveCssVariable(providerTheme.backgroundColor) ?? providerTheme.backgroundColor;
|
|
3062
3331
|
const theme = useXYChartTheme(data);
|
|
3332
|
+
const resolvedBackgroundColor = theme.backgroundColor ?? providerTheme.backgroundColor;
|
|
3063
3333
|
const chartId = useChartId(providedChartId);
|
|
3064
3334
|
const chartRef = useRef(null);
|
|
3065
3335
|
const [selectedIndex, setSelectedIndex] = useState(void 0);
|
|
@@ -3835,7 +4105,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3835
4105
|
stacked,
|
|
3836
4106
|
stackOffset,
|
|
3837
4107
|
getElementStyles,
|
|
3838
|
-
strokeColor:
|
|
4108
|
+
strokeColor: theme.backgroundColor ?? providerTheme.backgroundColor
|
|
3839
4109
|
})] }),
|
|
3840
4110
|
/* @__PURE__ */ jsx(AreaChartScalesRef, {
|
|
3841
4111
|
chartRef: internalChartRef,
|
|
@@ -3995,11 +4265,73 @@ const TruncatedYTickComponent = createTruncatedTickComponent("y");
|
|
|
3995
4265
|
const BASE_BAND_PADDING = .2;
|
|
3996
4266
|
/** Inner padding of the category band scale (the base gap between ticks). */
|
|
3997
4267
|
const BASE_BAND_PADDING_INNER = .1;
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4268
|
+
/** Ticks each axis carries unless the caller asks for a different count. */
|
|
4269
|
+
const DEFAULT_NUM_TICKS = 4;
|
|
4270
|
+
const ALL_RENDERED = () => true;
|
|
4271
|
+
const TOOLTIP_FORMAT_BY_RESOLUTION = {
|
|
4272
|
+
hour: {
|
|
4273
|
+
year: "numeric",
|
|
4274
|
+
month: "long",
|
|
4275
|
+
day: "numeric",
|
|
4276
|
+
hour: "numeric",
|
|
4277
|
+
hour12: true
|
|
4278
|
+
},
|
|
4279
|
+
day: {
|
|
4280
|
+
year: "numeric",
|
|
4281
|
+
month: "long",
|
|
4001
4282
|
day: "numeric"
|
|
4002
|
-
}
|
|
4283
|
+
},
|
|
4284
|
+
month: {
|
|
4285
|
+
year: "numeric",
|
|
4286
|
+
month: "long"
|
|
4287
|
+
},
|
|
4288
|
+
year: { year: "numeric" }
|
|
4289
|
+
};
|
|
4290
|
+
/**
|
|
4291
|
+
* Labels one bar's bucket, at the bucket's own granularity — finer than the
|
|
4292
|
+
* ticks whenever the overall time span coarsens the axis.
|
|
4293
|
+
*
|
|
4294
|
+
* @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
|
|
4295
|
+
* @param tickResolution - Caller-declared bucket resolution, when known.
|
|
4296
|
+
* @return Tooltip label formatter.
|
|
4297
|
+
*/
|
|
4298
|
+
const getTooltipFormatter = (data, tickResolution) => {
|
|
4299
|
+
if (tickResolution === "week") return (timestamp) => sprintf(__("Week of %s", "jetpack-charts"), new Date(timestamp).toLocaleDateString(void 0, {
|
|
4300
|
+
year: "numeric",
|
|
4301
|
+
month: "long",
|
|
4302
|
+
day: "numeric"
|
|
4303
|
+
}));
|
|
4304
|
+
const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
|
|
4305
|
+
return (timestamp) => new Date(timestamp).toLocaleString(void 0, format);
|
|
4306
|
+
};
|
|
4307
|
+
const identity = (label) => label;
|
|
4308
|
+
const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
|
|
4309
|
+
const bucketAccessor = (d) => d?.label || d?.date;
|
|
4310
|
+
/**
|
|
4311
|
+
* The buckets the band axis can put a tick on.
|
|
4312
|
+
*
|
|
4313
|
+
* Built from the accessor visx is given, over the same series in the same order,
|
|
4314
|
+
* so that a tick value is a key the scale actually holds; `scaleBand` returns
|
|
4315
|
+
* `undefined` for one it does not, which parks the tick at the origin. Null
|
|
4316
|
+
* rather than a partial list when any bucket is labelled instead of dated.
|
|
4317
|
+
*
|
|
4318
|
+
* @param data - Every series handed to the chart.
|
|
4319
|
+
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
4320
|
+
* @return Bucket dates in axis order, or null if the axis cannot be ticked by date.
|
|
4321
|
+
*/
|
|
4322
|
+
const getBandDomain = (data, isSeriesRendered) => {
|
|
4323
|
+
const registry = {};
|
|
4324
|
+
for (const series of data) {
|
|
4325
|
+
if (series.options?.type === "comparison" || !isSeriesRendered(series)) continue;
|
|
4326
|
+
registry[series.label] = series;
|
|
4327
|
+
}
|
|
4328
|
+
const byTimestamp = /* @__PURE__ */ new Map();
|
|
4329
|
+
for (const key of Object.keys(registry)) for (const point of registry[key].data) {
|
|
4330
|
+
const bucket = bucketAccessor(point);
|
|
4331
|
+
if (!(bucket instanceof Date)) return null;
|
|
4332
|
+
if (!byTimestamp.has(bucket.getTime())) byTimestamp.set(bucket.getTime(), bucket);
|
|
4333
|
+
}
|
|
4334
|
+
return byTimestamp.size ? [...byTimestamp.values()] : null;
|
|
4003
4335
|
};
|
|
4004
4336
|
/**
|
|
4005
4337
|
* Get the group padding of a scale.
|
|
@@ -4013,12 +4345,26 @@ const getGroupPadding = (scale) => {
|
|
|
4013
4345
|
/**
|
|
4014
4346
|
* Returns the merged options for the bar chart, including axis and scale configuration based on the orientation.
|
|
4015
4347
|
*
|
|
4016
|
-
* @param data
|
|
4017
|
-
* @param horizontal
|
|
4018
|
-
* @param options
|
|
4348
|
+
* @param data - The data to be displayed in the chart.
|
|
4349
|
+
* @param horizontal - Whether the chart is horizontal or vertical.
|
|
4350
|
+
* @param options - The options for the chart.
|
|
4351
|
+
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
4019
4352
|
* @return The merged options for the chart.
|
|
4020
4353
|
*/
|
|
4021
|
-
function useBarChartOptions(data, horizontal, options = {}) {
|
|
4354
|
+
function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
|
|
4355
|
+
const stableOptions = useDeepMemo(options);
|
|
4356
|
+
const axisConfig = useMemo(() => {
|
|
4357
|
+
const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, ...xAxisOptions } = stableOptions.axis?.x || {};
|
|
4358
|
+
const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, ...yAxisOptions } = stableOptions.axis?.y || {};
|
|
4359
|
+
return {
|
|
4360
|
+
xLabelOverflow,
|
|
4361
|
+
yLabelOverflow,
|
|
4362
|
+
xAxisOptions,
|
|
4363
|
+
yAxisOptions,
|
|
4364
|
+
tickResolution: horizontal ? yTickResolution : xTickResolution
|
|
4365
|
+
};
|
|
4366
|
+
}, [stableOptions, horizontal]);
|
|
4367
|
+
const { tickResolution } = axisConfig;
|
|
4022
4368
|
const defaultOptions = useMemo(() => {
|
|
4023
4369
|
const bandScale = {
|
|
4024
4370
|
type: "band",
|
|
@@ -4030,19 +4376,26 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4030
4376
|
nice: true,
|
|
4031
4377
|
zero: false
|
|
4032
4378
|
};
|
|
4033
|
-
const
|
|
4379
|
+
const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
|
|
4380
|
+
const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
|
|
4381
|
+
const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
|
|
4382
|
+
const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
|
|
4034
4383
|
const valueFormatter = formatNumberCompact;
|
|
4035
|
-
const
|
|
4384
|
+
const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
|
|
4036
4385
|
const valueAccessor = (d) => {
|
|
4037
4386
|
const enhancedPoint = d;
|
|
4038
4387
|
return enhancedPoint?.visualValue !== void 0 ? enhancedPoint.visualValue : d?.value;
|
|
4039
4388
|
};
|
|
4040
4389
|
return {
|
|
4390
|
+
timeAxis: bandDomain && timeTickFormatter && {
|
|
4391
|
+
domain: bandDomain,
|
|
4392
|
+
tickFormatter: timeTickFormatter
|
|
4393
|
+
},
|
|
4041
4394
|
vertical: {
|
|
4042
4395
|
xTickFormat: labelFormatter,
|
|
4043
4396
|
yTickFormat: valueFormatter,
|
|
4044
|
-
tooltipLabelFormatter:
|
|
4045
|
-
xAccessor:
|
|
4397
|
+
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
4398
|
+
xAccessor: bucketAccessor,
|
|
4046
4399
|
yAccessor: valueAccessor,
|
|
4047
4400
|
gridVisibility: "x",
|
|
4048
4401
|
xScale: bandScale,
|
|
@@ -4051,20 +4404,24 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4051
4404
|
horizontal: {
|
|
4052
4405
|
xTickFormat: valueFormatter,
|
|
4053
4406
|
yTickFormat: labelFormatter,
|
|
4054
|
-
tooltipLabelFormatter:
|
|
4407
|
+
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
4055
4408
|
xAccessor: valueAccessor,
|
|
4056
|
-
yAccessor:
|
|
4409
|
+
yAccessor: bucketAccessor,
|
|
4057
4410
|
gridVisibility: "y",
|
|
4058
4411
|
xScale: linearScale,
|
|
4059
4412
|
yScale: bandScale
|
|
4060
4413
|
}
|
|
4061
4414
|
};
|
|
4062
|
-
}, [
|
|
4415
|
+
}, [
|
|
4416
|
+
data,
|
|
4417
|
+
tickResolution,
|
|
4418
|
+
isSeriesRendered
|
|
4419
|
+
]);
|
|
4063
4420
|
return useMemo(() => {
|
|
4064
4421
|
const { xTickFormat, yTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
|
|
4065
4422
|
let valueScaleDomainOverride = {};
|
|
4066
4423
|
if (data.some((s) => s.options?.type === "comparison")) {
|
|
4067
|
-
if (!(!horizontal ?
|
|
4424
|
+
if (!(!horizontal ? stableOptions.yScale?.domain : stableOptions.xScale?.domain)) {
|
|
4068
4425
|
const allValues = [];
|
|
4069
4426
|
data.forEach((series) => {
|
|
4070
4427
|
series.data.forEach((d) => {
|
|
@@ -4078,17 +4435,20 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4078
4435
|
}
|
|
4079
4436
|
const xScale = {
|
|
4080
4437
|
...baseXScale,
|
|
4081
|
-
...
|
|
4438
|
+
...stableOptions.xScale || {},
|
|
4082
4439
|
...horizontal ? valueScaleDomainOverride : {}
|
|
4083
4440
|
};
|
|
4084
4441
|
const yScale = {
|
|
4085
4442
|
...baseYScale,
|
|
4086
|
-
...
|
|
4443
|
+
...stableOptions.yScale || {},
|
|
4087
4444
|
...!horizontal ? valueScaleDomainOverride : {}
|
|
4088
4445
|
};
|
|
4089
|
-
const
|
|
4090
|
-
const
|
|
4091
|
-
const {
|
|
4446
|
+
const { xLabelOverflow, yLabelOverflow, xAxisOptions, yAxisOptions } = axisConfig;
|
|
4447
|
+
const providedToolTipLabelFormatter = horizontal ? yAxisOptions.tickFormat : xAxisOptions.tickFormat;
|
|
4448
|
+
const { timeAxis } = defaultOptions;
|
|
4449
|
+
const dateAxisOptions = horizontal ? yAxisOptions : xAxisOptions;
|
|
4450
|
+
const bandTickValues = timeAxis && !dateAxisOptions.tickFormat ? getBandTickValues(timeAxis.domain, timeAxis.tickFormatter, dateAxisOptions.numTicks ?? DEFAULT_NUM_TICKS) : null;
|
|
4451
|
+
const dateAxisTickValues = bandTickValues ? { tickValues: bandTickValues } : {};
|
|
4092
4452
|
return {
|
|
4093
4453
|
gridVisibility,
|
|
4094
4454
|
xScale,
|
|
@@ -4100,15 +4460,17 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4100
4460
|
axis: {
|
|
4101
4461
|
x: {
|
|
4102
4462
|
orientation: "bottom",
|
|
4103
|
-
numTicks:
|
|
4463
|
+
numTicks: DEFAULT_NUM_TICKS,
|
|
4104
4464
|
tickFormat: xTickFormat,
|
|
4465
|
+
...horizontal ? {} : dateAxisTickValues,
|
|
4105
4466
|
...xLabelOverflow === "ellipsis" ? { tickComponent: TruncatedXTickComponent } : {},
|
|
4106
4467
|
...xAxisOptions
|
|
4107
4468
|
},
|
|
4108
4469
|
y: {
|
|
4109
4470
|
orientation: "left",
|
|
4110
|
-
numTicks:
|
|
4471
|
+
numTicks: DEFAULT_NUM_TICKS,
|
|
4111
4472
|
tickFormat: yTickFormat,
|
|
4473
|
+
...horizontal ? dateAxisTickValues : {},
|
|
4112
4474
|
...yLabelOverflow === "ellipsis" ? { tickComponent: TruncatedYTickComponent } : {},
|
|
4113
4475
|
...yAxisOptions
|
|
4114
4476
|
}
|
|
@@ -4118,7 +4480,8 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4118
4480
|
};
|
|
4119
4481
|
}, [
|
|
4120
4482
|
defaultOptions,
|
|
4121
|
-
|
|
4483
|
+
axisConfig,
|
|
4484
|
+
stableOptions,
|
|
4122
4485
|
horizontal,
|
|
4123
4486
|
data
|
|
4124
4487
|
]);
|
|
@@ -4280,7 +4643,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4280
4643
|
valueAxisLength: horizontal ? width : height
|
|
4281
4644
|
});
|
|
4282
4645
|
const legendItems = useChartLegendItems(dataSorted, useMemo(() => ({ collapseGroups: legendCollapseGroups }), [legendCollapseGroups]));
|
|
4283
|
-
const
|
|
4646
|
+
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
4647
|
+
const isSeriesRendered = useCallback((series) => !chartId || !legendInteractive || isSeriesVisible(chartId, series.label), [
|
|
4648
|
+
chartId,
|
|
4649
|
+
legendInteractive,
|
|
4650
|
+
isSeriesVisible
|
|
4651
|
+
]);
|
|
4652
|
+
const chartOptions = useBarChartOptions(dataWithVisibleZeros, horizontal, options, isSeriesRendered);
|
|
4284
4653
|
const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme, horizontal);
|
|
4285
4654
|
const chartRef = useRef(null);
|
|
4286
4655
|
const { legendChildren, nonLegendChildren } = useChartChildren(children, "BarChart");
|
|
@@ -4299,24 +4668,11 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4299
4668
|
chartRef,
|
|
4300
4669
|
totalPoints: Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length
|
|
4301
4670
|
});
|
|
4302
|
-
const
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
isVisible: true
|
|
4308
|
-
}));
|
|
4309
|
-
return dataWithVisibleZeros.map((series, index) => ({
|
|
4310
|
-
series,
|
|
4311
|
-
index,
|
|
4312
|
-
isVisible: isSeriesVisible(chartId, series.label)
|
|
4313
|
-
}));
|
|
4314
|
-
}, [
|
|
4315
|
-
dataWithVisibleZeros,
|
|
4316
|
-
chartId,
|
|
4317
|
-
isSeriesVisible,
|
|
4318
|
-
legendInteractive
|
|
4319
|
-
]);
|
|
4671
|
+
const seriesWithVisibility = useMemo(() => dataWithVisibleZeros.map((series, index) => ({
|
|
4672
|
+
series,
|
|
4673
|
+
index,
|
|
4674
|
+
isVisible: isSeriesRendered(series)
|
|
4675
|
+
})), [dataWithVisibleZeros, isSeriesRendered]);
|
|
4320
4676
|
const allSeriesHidden = useMemo(() => {
|
|
4321
4677
|
return seriesWithVisibility.every(({ isVisible }) => !isVisible);
|
|
4322
4678
|
}, [seriesWithVisibility]);
|
|
@@ -4876,12 +5232,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
4876
5232
|
const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
|
|
4877
5233
|
const chartRef = useRef(null);
|
|
4878
5234
|
const selectedBarRef = useRef(null);
|
|
5235
|
+
const [scopeNode, setScopeNode] = useState(null);
|
|
4879
5236
|
const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = useTooltip();
|
|
4880
5237
|
const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
|
|
4881
5238
|
const { containerRef: portalContainerRef, TooltipInPortal, containerBounds } = useTooltipInPortal({
|
|
4882
5239
|
detectBounds: true,
|
|
4883
5240
|
scroll: true
|
|
4884
5241
|
});
|
|
5242
|
+
const portalContainerRefRef = useRef(portalContainerRef);
|
|
5243
|
+
useEffect(() => {
|
|
5244
|
+
portalContainerRefRef.current = portalContainerRef;
|
|
5245
|
+
}, [portalContainerRef]);
|
|
5246
|
+
const setChartRef = useCallback((node) => {
|
|
5247
|
+
portalContainerRefRef.current(node);
|
|
5248
|
+
chartRef.current = node;
|
|
5249
|
+
setScopeNode(node);
|
|
5250
|
+
}, []);
|
|
4885
5251
|
const clearSelectionAndRef = useCallback(() => {
|
|
4886
5252
|
clearSelection();
|
|
4887
5253
|
selectedBarRef.current = null;
|
|
@@ -5027,6 +5393,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5027
5393
|
direction: "column",
|
|
5028
5394
|
align: "center",
|
|
5029
5395
|
justify: "center",
|
|
5396
|
+
ref: setScopeNode,
|
|
5030
5397
|
className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5031
5398
|
style: {
|
|
5032
5399
|
...style,
|
|
@@ -5038,80 +5405,80 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5038
5405
|
})
|
|
5039
5406
|
});
|
|
5040
5407
|
const maxRate = Math.max(...steps.map((step) => step.rate));
|
|
5041
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */
|
|
5408
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Stack, {
|
|
5042
5409
|
direction: "column",
|
|
5043
5410
|
gap: "xl",
|
|
5044
|
-
ref:
|
|
5045
|
-
portalContainerRef(node);
|
|
5046
|
-
chartRef.current = node;
|
|
5047
|
-
},
|
|
5411
|
+
ref: setChartRef,
|
|
5048
5412
|
className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5049
5413
|
style: {
|
|
5050
5414
|
...style,
|
|
5051
5415
|
height: resolvedHeight
|
|
5052
5416
|
},
|
|
5053
|
-
children:
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
gap: "xl",
|
|
5076
|
-
children: [/* @__PURE__ */ jsxs(Stack, {
|
|
5417
|
+
children: /* @__PURE__ */ jsxs(ChartScopeContext.Provider, {
|
|
5418
|
+
value: scopeNode,
|
|
5419
|
+
children: [renderMainMetric ? renderMainMetric({
|
|
5420
|
+
mainRate,
|
|
5421
|
+
changeIndicator,
|
|
5422
|
+
className: conversion_funnel_chart_module_default["main-metric"],
|
|
5423
|
+
changeColor
|
|
5424
|
+
}) : /* @__PURE__ */ jsx(Stack, {
|
|
5425
|
+
direction: "row",
|
|
5426
|
+
align: "baseline",
|
|
5427
|
+
gap: "sm",
|
|
5428
|
+
className: conversion_funnel_chart_module_default["main-metric"],
|
|
5429
|
+
children: renderDefaultMainMetric()
|
|
5430
|
+
}), /* @__PURE__ */ jsx(Stack, {
|
|
5431
|
+
direction: "row",
|
|
5432
|
+
align: "flex-end",
|
|
5433
|
+
gap: "lg",
|
|
5434
|
+
className: conversion_funnel_chart_module_default["funnel-container"],
|
|
5435
|
+
children: steps.map((step, index) => {
|
|
5436
|
+
const barHeight = step.rate / maxRate * 100;
|
|
5437
|
+
const { isBlurred } = getStepState(step.id);
|
|
5438
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
5077
5439
|
direction: "column",
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5440
|
+
className: clsx(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"]),
|
|
5441
|
+
gap: "xl",
|
|
5442
|
+
children: [/* @__PURE__ */ jsxs(Stack, {
|
|
5443
|
+
direction: "column",
|
|
5444
|
+
gap: "xs",
|
|
5445
|
+
children: [renderStepLabel ? renderStepLabel({
|
|
5446
|
+
step,
|
|
5447
|
+
index,
|
|
5448
|
+
className: conversion_funnel_chart_module_default["step-label"]
|
|
5449
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
5450
|
+
className: conversion_funnel_chart_module_default["step-label"],
|
|
5451
|
+
children: step.label
|
|
5452
|
+
}), renderStepRate ? renderStepRate({
|
|
5453
|
+
step,
|
|
5454
|
+
index,
|
|
5455
|
+
className: conversion_funnel_chart_module_default["step-rate"]
|
|
5456
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
5457
|
+
className: conversion_funnel_chart_module_default["step-rate"],
|
|
5458
|
+
children: formatPercentage(step.rate)
|
|
5459
|
+
})]
|
|
5460
|
+
}), /* @__PURE__ */ jsx(Stack, {
|
|
5461
|
+
direction: "column",
|
|
5462
|
+
justify: "flex-end",
|
|
5463
|
+
className: conversion_funnel_chart_module_default["bar-container"],
|
|
5464
|
+
onClick: stepHandlers.get(step.id)?.onClick,
|
|
5465
|
+
onKeyDown: stepHandlers.get(step.id)?.onKeyDown,
|
|
5466
|
+
role: "button",
|
|
5467
|
+
tabIndex: isBlurred ? -1 : 0,
|
|
5468
|
+
"aria-label": step.label,
|
|
5469
|
+
style: { backgroundColor: barBackgroundColor },
|
|
5470
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
5471
|
+
className: clsx(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
|
|
5472
|
+
style: {
|
|
5473
|
+
height: `${barHeight}%`,
|
|
5474
|
+
backgroundColor: barColor
|
|
5475
|
+
}
|
|
5476
|
+
})
|
|
5093
5477
|
})]
|
|
5094
|
-
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
onClick: stepHandlers.get(step.id)?.onClick,
|
|
5099
|
-
onKeyDown: stepHandlers.get(step.id)?.onKeyDown,
|
|
5100
|
-
role: "button",
|
|
5101
|
-
tabIndex: isBlurred ? -1 : 0,
|
|
5102
|
-
"aria-label": step.label,
|
|
5103
|
-
style: { backgroundColor: barBackgroundColor },
|
|
5104
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
5105
|
-
className: clsx(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
|
|
5106
|
-
style: {
|
|
5107
|
-
height: `${barHeight}%`,
|
|
5108
|
-
backgroundColor: barColor
|
|
5109
|
-
}
|
|
5110
|
-
})
|
|
5111
|
-
})]
|
|
5112
|
-
}, step.id);
|
|
5113
|
-
})
|
|
5114
|
-
})]
|
|
5478
|
+
}, step.id);
|
|
5479
|
+
})
|
|
5480
|
+
})]
|
|
5481
|
+
})
|
|
5115
5482
|
}), tooltipOpen && tooltipData && (() => {
|
|
5116
5483
|
const tooltipContent = renderTooltip ? renderTooltip({
|
|
5117
5484
|
step: tooltipData,
|
|
@@ -5124,7 +5491,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5124
5491
|
return /* @__PURE__ */ jsx(TooltipInPortal, {
|
|
5125
5492
|
top: tooltipTop,
|
|
5126
5493
|
left: tooltipLeft,
|
|
5127
|
-
className: conversion_funnel_chart_module_default["tooltip-wrapper"],
|
|
5494
|
+
className: clsx("a8c-charts-scope", conversion_funnel_chart_module_default["tooltip-wrapper"]),
|
|
5128
5495
|
children: tooltipContent
|
|
5129
5496
|
}, Math.random());
|
|
5130
5497
|
})()] });
|
|
@@ -5274,6 +5641,7 @@ function normalizeGeoChartError(eventArgs) {
|
|
|
5274
5641
|
*/
|
|
5275
5642
|
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
|
|
5276
5643
|
const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
|
|
5644
|
+
const scopeElement = useChartScopeElement();
|
|
5277
5645
|
const containerRef = useRef(null);
|
|
5278
5646
|
const reportedErrorIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
5279
5647
|
useEffect(() => {
|
|
@@ -5306,8 +5674,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
5306
5674
|
});
|
|
5307
5675
|
const fullColorHex = getElementStyles({ index: 0 }).color;
|
|
5308
5676
|
const lightColorHex = lightenHexColor(fullColorHex, .8);
|
|
5309
|
-
const backgroundColorHex = normalizeColorToHex(backgroundColor,
|
|
5310
|
-
const defaultFillColorHex = normalizeColorToHex(featureFillColor,
|
|
5677
|
+
const backgroundColorHex = normalizeColorToHex(backgroundColor, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
|
|
5678
|
+
const defaultFillColorHex = normalizeColorToHex(featureFillColor, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
|
|
5311
5679
|
const sanitizedData = useMemo(() => {
|
|
5312
5680
|
if (data.length === 0) return {
|
|
5313
5681
|
data,
|
|
@@ -5567,7 +5935,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
5567
5935
|
const CELL_MIX_FLOOR = .15;
|
|
5568
5936
|
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 }) => {
|
|
5569
5937
|
const chartId = useChartId(providedChartId);
|
|
5570
|
-
const { getElementStyles,
|
|
5938
|
+
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
5939
|
+
const scopeElement = useChartScopeElement();
|
|
5571
5940
|
const { heatmapChart: heatmapChartSettings } = theme;
|
|
5572
5941
|
const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
|
|
5573
5942
|
const [selectedIndex, setSelectedIndex] = useState();
|
|
@@ -5582,7 +5951,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
5582
5951
|
index: 0,
|
|
5583
5952
|
overrideColor: primaryColor || heatmapChartSettings.primaryColor
|
|
5584
5953
|
});
|
|
5585
|
-
const chartBackgroundHex =
|
|
5954
|
+
const chartBackgroundHex = normalizeColorToHex(theme.backgroundColor, scopeElement, resolveCssVariable);
|
|
5586
5955
|
const primaryHex = normalizeColorToHex(primaryColorHex);
|
|
5587
5956
|
const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + (1 - CELL_MIX_FLOOR) * intensity)));
|
|
5588
5957
|
const extent = useMemo(() => getValueExtent(data), [data]);
|
|
@@ -5809,6 +6178,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
5809
6178
|
top: tooltipTop,
|
|
5810
6179
|
left: tooltipLeft,
|
|
5811
6180
|
children: /* @__PURE__ */ jsx("div", {
|
|
6181
|
+
className: "a8c-charts-scope",
|
|
5812
6182
|
role: "tooltip",
|
|
5813
6183
|
tabIndex: -1,
|
|
5814
6184
|
children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
|
|
@@ -8779,6 +9149,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
8779
9149
|
top: tooltipTop || 0,
|
|
8780
9150
|
left: tooltipLeft || 0,
|
|
8781
9151
|
children: /* @__PURE__ */ jsx("div", {
|
|
9152
|
+
className: "a8c-charts-scope",
|
|
8782
9153
|
role: "tooltip",
|
|
8783
9154
|
children: renderTooltip({ tooltipData })
|
|
8784
9155
|
})
|
|
@@ -9077,6 +9448,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9077
9448
|
top: tooltipTop || 0,
|
|
9078
9449
|
left: tooltipLeft || 0,
|
|
9079
9450
|
children: /* @__PURE__ */ jsx("div", {
|
|
9451
|
+
className: "a8c-charts-scope",
|
|
9080
9452
|
role: "tooltip",
|
|
9081
9453
|
children: renderTooltip({ tooltipData })
|
|
9082
9454
|
})
|
|
@@ -9364,7 +9736,7 @@ const Icon$1 = ({ direction }) => {
|
|
|
9364
9736
|
function TrendIndicator({ direction, value, className, style, showIcon = true }) {
|
|
9365
9737
|
const ariaLabel = `${DIRECTION_LABELS[direction]}: ${value}`;
|
|
9366
9738
|
return /* @__PURE__ */ jsxs("span", {
|
|
9367
|
-
className: clsx(trend_indicator_module_default["trend-indicator"], trend_indicator_module_default[`trend-indicator--${direction}`], className),
|
|
9739
|
+
className: clsx(useStandaloneScopeClass(), trend_indicator_module_default["trend-indicator"], trend_indicator_module_default[`trend-indicator--${direction}`], className),
|
|
9368
9740
|
style,
|
|
9369
9741
|
"aria-label": ariaLabel,
|
|
9370
9742
|
children: [showIcon && /* @__PURE__ */ jsx(Icon$1, { direction }), /* @__PURE__ */ jsx("span", {
|
|
@@ -9374,6 +9746,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
|
|
|
9374
9746
|
});
|
|
9375
9747
|
}
|
|
9376
9748
|
//#endregion
|
|
9377
|
-
export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, parseRgbString, prefersLightText, relativeLuminance, useChartLegendItems, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
|
|
9749
|
+
export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, parseRgbString, prefersLightText, relativeLuminance, resolveCssVariable, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
|
|
9378
9750
|
|
|
9379
9751
|
//# sourceMappingURL=index.js.map
|