@automattic/charts 3.2.0 → 4.1.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 +47 -0
- package/SECURITY.md +1 -0
- package/TOKENS.md +53 -58
- package/dist/index.cjs +1483 -664
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +93 -29
- package/dist/index.d.cts +233 -247
- package/dist/index.d.ts +233 -247
- package/dist/index.js +1484 -667
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/charts/area-chart/area-chart.tsx +39 -25
- package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
- package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
- package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
- package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
- package/src/charts/area-chart/types.ts +4 -7
- package/src/charts/bar-chart/bar-chart.tsx +2 -1
- package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
- package/src/charts/geo-chart/geo-chart.module.scss +1 -0
- package/src/charts/geo-chart/geo-chart.tsx +5 -10
- package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +52 -16
- package/src/charts/heatmap-chart/heatmap-chart.tsx +89 -37
- package/src/charts/heatmap-chart/index.ts +2 -1
- package/src/charts/heatmap-chart/private/build-calendar-data.ts +126 -89
- package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
- package/src/charts/heatmap-chart/private/index.ts +1 -1
- package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +3 -0
- package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +142 -12
- package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +118 -0
- package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +8 -0
- package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +248 -0
- package/src/charts/heatmap-chart/types.ts +8 -2
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
- package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
- package/src/charts/leaderboard-chart/types.ts +2 -2
- package/src/charts/line-chart/line-chart.module.scss +10 -18
- package/src/charts/line-chart/line-chart.tsx +128 -26
- package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
- package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
- package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +174 -41
- package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
- package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
- package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
- package/src/charts/line-chart/types.ts +17 -3
- package/src/charts/pie-chart/pie-chart.module.scss +13 -1
- package/src/charts/pie-chart/pie-chart.tsx +30 -40
- package/src/charts/pie-chart/test/pie-chart.test.tsx +59 -12
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
- package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
- package/src/charts/private/test/axis-formatting.test.tsx +85 -0
- package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
- package/src/charts/private/test/time-axis-options.test.ts +267 -0
- package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
- package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
- package/src/charts/private/test/time-axis.test.ts +9 -69
- package/src/charts/private/time-axis-options.ts +106 -0
- package/src/charts/private/time-axis.ts +318 -136
- package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
- package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
- package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
- package/src/charts/sparkline/types.ts +1 -1
- package/src/components/legend/private/base-legend.module.scss +9 -5
- package/src/components/tooltip/accessible-tooltip.tsx +21 -17
- package/src/components/tooltip/base-tooltip.module.scss +3 -3
- package/src/components/tooltip/private/bounded-tooltip.tsx +227 -0
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +30 -4
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +225 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +379 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +326 -0
- package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
- package/src/hooks/test/use-chart-data-transform-zone.test.tsx +36 -0
- package/src/hooks/test/use-chart-margin.test.tsx +173 -3
- package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
- package/src/hooks/use-chart-data-transform.ts +10 -3
- package/src/hooks/use-chart-margin.tsx +54 -9
- package/src/hooks/use-xychart-theme.ts +25 -9
- package/src/index.ts +8 -0
- package/src/providers/chart-context/global-charts-provider.tsx +37 -38
- package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
- package/src/providers/chart-context/index.ts +1 -0
- package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
- package/src/providers/chart-context/private/series-palette.ts +3 -13
- package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
- package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
- package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
- package/src/providers/chart-context/themes.ts +7 -52
- package/src/providers/chart-context/types.ts +3 -0
- package/src/styles/chart-scope.scss +39 -62
- package/src/styles/test/chart-scope.test.ts +19 -47
- package/src/test-utils/series-fixtures.ts +31 -0
- package/src/test-utils/zoned-parsing-suite.ts +90 -0
- package/src/types.ts +79 -166
- package/src/utils/bucket-info.ts +87 -0
- package/src/utils/color-utils.ts +0 -27
- package/src/utils/date-formatting.ts +157 -0
- package/src/utils/date-parsing.ts +103 -52
- package/src/utils/get-edge-tick-widths.ts +33 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/test/bucket-info.test.ts +185 -0
- package/src/utils/test/color-utils.test.ts +1 -48
- package/src/utils/test/date-formatting.test.ts +89 -0
- package/src/utils/test/date-parsing.auckland.test.ts +6 -0
- package/src/utils/test/date-parsing.santiago.test.ts +6 -0
- package/src/utils/test/date-parsing.test.ts +16 -1
- package/src/utils/test/get-edge-tick-widths.test.ts +55 -0
- package/src/utils/test/get-styles.test.ts +10 -7
- package/src/utils/test/merge-themes.test.ts +18 -46
- package/src/utils/warn-once.ts +23 -0
- package/src/visx/types.ts +56 -5
- package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
- package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
- package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
- package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
- package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
- package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
- package/src/styles/test/wpds-fallbacks.test.ts +0 -131
package/dist/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { getStringWidth as getStringWidth$1 } from "./visx/text/index.js";
|
|
2
2
|
import { formatNumber, formatNumberCompact } from "@automattic/number-formatters";
|
|
3
|
-
import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, Grid,
|
|
3
|
+
import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, Grid, TooltipContext, XYChart, buildChartTheme } from "@visx/xychart";
|
|
4
4
|
import { __, _x, sprintf } from "@wordpress/i18n";
|
|
5
5
|
import clsx from "clsx";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { Children, Fragment, createContext, createContext as createContext$1, createElement, forwardRef, forwardRef as forwardRef$1, isValidElement, memo, useCallback, useCallback as useCallback$1, useContext, useContext as useContext$1, useEffect, useEffect as useEffect$1, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useRef, useRef as useRef$1, useState, useState as useState$1 } from "react";
|
|
8
8
|
import { color, hsl } from "@visx/vendor/d3-color";
|
|
9
|
-
import {
|
|
9
|
+
import { differenceInHours, differenceInYears, isValid, parse, parseISO } from "date-fns";
|
|
10
|
+
import { tzOffset } from "@date-fns/tz";
|
|
10
11
|
import { Text, getStringWidth } from "@visx/text";
|
|
11
12
|
import deepmerge from "deepmerge";
|
|
12
13
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
-
import {
|
|
14
|
+
import { Tooltip, defaultStyles, useTooltip } from "@visx/tooltip";
|
|
14
15
|
import { createScale, getTicks, scaleBand, scaleOrdinal, scaleTime } from "@visx/scale";
|
|
15
16
|
import { Group } from "@visx/group";
|
|
16
17
|
import { LegendItem, LegendLabel, LegendOrdinal, LegendShape } from "@visx/legend";
|
|
17
18
|
import { IconButton, Stack, Text as Text$1 } from "@wordpress/ui";
|
|
19
|
+
import { createPortal } from "react-dom";
|
|
18
20
|
import { LinearGradient } from "@visx/gradient";
|
|
19
21
|
import { curveCatmullRom, curveLinear, curveMonotoneX } from "@visx/curve";
|
|
20
22
|
import { useParentSize } from "@visx/responsive";
|
|
@@ -83,46 +85,35 @@ function attachSubComponents(Chart, subComponents) {
|
|
|
83
85
|
return Object.assign(Chart, subComponents);
|
|
84
86
|
}
|
|
85
87
|
//#endregion
|
|
86
|
-
//#region src/utils/
|
|
88
|
+
//#region src/utils/warn-once.ts
|
|
89
|
+
const warned = /* @__PURE__ */ new Set();
|
|
87
90
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* This module provides utilities for parsing various date string formats and converting
|
|
91
|
-
* them to local timezone dates using the battle-tested date-fns library. For formats
|
|
92
|
-
* without timezone info, they're treated as local. For formats with timezone info,
|
|
93
|
-
* they're converted to the equivalent local time.
|
|
91
|
+
* Warns about a host-supplied value once per process, and never in production.
|
|
94
92
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
* @param key - Identifies the value, so a bad prop on every point warns once.
|
|
94
|
+
* @param message - What the host got wrong and what happens instead.
|
|
95
|
+
*/
|
|
96
|
+
const warnOnce = (key, message) => {
|
|
97
|
+
if (warned.has(key) || process.env.NODE_ENV === "production") return;
|
|
98
|
+
warned.add(key);
|
|
99
|
+
console.warn(`[Charts] ${message}`);
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/utils/date-parsing.ts
|
|
103
|
+
/**
|
|
104
|
+
* @file Date parsing: a naive string is dated in a supplied IANA zone, or the runtime's own
|
|
103
105
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* - YYYY-MM-DD HH:mm (treated as local)
|
|
108
|
-
* - YYYY-MM-DDTHH:mm:ss (treated as local)
|
|
109
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
|
|
110
|
-
* - YYYY-MM-DDTHH:mm (treated as local)
|
|
111
|
-
* - YYYY-MM-DDTHH:mm:ssZ (converted to local)
|
|
112
|
-
* - YYYY-MM-DDTHH:mm:ss±HH:mm (converted to local)
|
|
106
|
+
* A string carrying an offset is already an instant and parses the same everywhere. A string
|
|
107
|
+
* without one is only a wall-clock reading, so it means nothing until a zone is named. See
|
|
108
|
+
* `parseAsLocalDate` for the supported formats.
|
|
113
109
|
*
|
|
114
|
-
*
|
|
115
|
-
* ```typescript
|
|
116
|
-
* parseAsLocalDate("2025-01-01"); // Local timezone
|
|
117
|
-
* parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
|
|
118
|
-
* parseAsLocalDate("2025-01-01 14:30"); // Local timezone
|
|
119
|
-
* parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
|
|
120
|
-
* parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
|
|
121
|
-
* parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
|
|
122
|
-
* ```
|
|
110
|
+
* Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
|
|
123
111
|
*/
|
|
124
112
|
/**
|
|
125
113
|
* Checks if a date string contains timezone information
|
|
114
|
+
*
|
|
115
|
+
* The minutes are optional because ISO 8601 allows a bare `±hh`, which `parseISO` reads.
|
|
116
|
+
*
|
|
126
117
|
* @param {string} dateString - The date string to check for timezone information
|
|
127
118
|
* @return {boolean} True if the date string contains timezone information, false otherwise
|
|
128
119
|
*/
|
|
@@ -130,28 +121,57 @@ const hasTimezone = (dateString) => {
|
|
|
130
121
|
const tIndex = dateString.indexOf("T");
|
|
131
122
|
if (tIndex === -1) return false;
|
|
132
123
|
if (dateString.endsWith("Z")) return true;
|
|
133
|
-
return /[+-]\d{2}
|
|
124
|
+
return /[+-]\d{2}(?::?\d{2})?$/.test(dateString.slice(tIndex + 1));
|
|
125
|
+
};
|
|
126
|
+
const NAIVE = /^(\d{1,4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?$/;
|
|
127
|
+
const asUtcMs = ([, ...fields]) => {
|
|
128
|
+
const [year, month, day, hour, minute, second, ms] = fields.map((field) => Number(field ?? 0));
|
|
129
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
130
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
131
|
+
date.setUTCHours(hour, minute, second, ms);
|
|
132
|
+
return date.getTime();
|
|
134
133
|
};
|
|
135
134
|
/**
|
|
136
|
-
*
|
|
135
|
+
* The instant a wall-clock reading names in a given zone.
|
|
137
136
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* local timezone equivalent.
|
|
137
|
+
* A reading a spring-forward gap deleted moves forward past the gap, and one a fall-back
|
|
138
|
+
* repeated resolves to a single instant. Neither is configurable: see CHARTS-268.
|
|
141
139
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* - YYYY-MM-DD HH:mm (local)
|
|
146
|
-
* - YYYY-MM-DDTHH:mm:ss (local)
|
|
147
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (local)
|
|
148
|
-
* - YYYY-MM-DDTHH:mm (local)
|
|
149
|
-
* - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
|
|
150
|
-
* - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
|
|
151
|
-
* @param {string} dateString - The date string to parse into a local timezone date
|
|
152
|
-
* @return {Date} A Date object representing the parsed date in local timezone, or an invalid Date if parsing fails
|
|
140
|
+
* @param wallClock - The reading, as the milliseconds it would be if it were UTC.
|
|
141
|
+
* @param timeZone - IANA zone the reading is dated in.
|
|
142
|
+
* @return The instant, or `null` where `timeZone` is not a zone Intl accepts.
|
|
153
143
|
*/
|
|
154
|
-
const
|
|
144
|
+
const wallClockToInstant = (wallClock, timeZone) => {
|
|
145
|
+
const first = tzOffset(timeZone, new Date(wallClock));
|
|
146
|
+
if (Number.isNaN(first)) {
|
|
147
|
+
warnOnce(`parse:timeZone:${timeZone}`, `timeZone ${JSON.stringify(timeZone)} is not a zone Intl accepts, so dates are read in the browser's zone. Pass an IANA name or a UTC offset such as "+05:30".`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const guess = wallClock - first * 6e4;
|
|
151
|
+
const second = tzOffset(timeZone, new Date(guess));
|
|
152
|
+
if (first === second) return new Date(guess);
|
|
153
|
+
const corrected = wallClock - second * 6e4;
|
|
154
|
+
const third = tzOffset(timeZone, new Date(corrected));
|
|
155
|
+
if (second === third) return new Date(corrected);
|
|
156
|
+
return /* @__PURE__ */ new Date(wallClock - Math.min(second, third) * 6e4);
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Parses any supported date string format into an instant, dated in `timeZone` or the runtime's own
|
|
160
|
+
*
|
|
161
|
+
* Uses date-fns for robust parsing and validation. A string carrying timezone info
|
|
162
|
+
* is already an instant and is returned as one, whatever `timeZone` says. A string
|
|
163
|
+
* without it is a wall-clock reading, dated in `timeZone` when one is supplied and
|
|
164
|
+
* in the runtime's own zone when none is.
|
|
165
|
+
*
|
|
166
|
+
* A wall clock a DST gap deleted is moved forward past the gap; one a fall-back repeated
|
|
167
|
+
* resolves to a single instant. Neither policy is selectable. A zone `Intl` rejects falls
|
|
168
|
+
* back to the runtime's own, and warns once outside production.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} dateString - The date string to parse into a date
|
|
171
|
+
* @param {string} [timeZone] - IANA zone a naive string is read in; the runtime's own when absent, and ignored by a string that carries its own offset
|
|
172
|
+
* @return {Date} A Date object representing the parsed instant, or an invalid Date if parsing fails
|
|
173
|
+
*/
|
|
174
|
+
const parseAsLocalDate = (dateString, timeZone) => {
|
|
155
175
|
const trimmedString = dateString.trim();
|
|
156
176
|
if (hasTimezone(trimmedString)) {
|
|
157
177
|
const isoDate = parseISO(trimmedString);
|
|
@@ -167,11 +187,47 @@ const parseAsLocalDate = (dateString) => {
|
|
|
167
187
|
"yyyy-MM-dd'T'HH:mm"
|
|
168
188
|
]) {
|
|
169
189
|
const result = parse(trimmedString, format, /* @__PURE__ */ new Date());
|
|
170
|
-
if (isValid(result))
|
|
190
|
+
if (!isValid(result)) continue;
|
|
191
|
+
if (!timeZone) return result;
|
|
192
|
+
const fields = NAIVE.exec(trimmedString);
|
|
193
|
+
return (fields && wallClockToInstant(asUtcMs(fields), timeZone)) ?? result;
|
|
171
194
|
}
|
|
172
195
|
return /* @__PURE__ */ new Date(NaN);
|
|
173
196
|
};
|
|
174
197
|
//#endregion
|
|
198
|
+
//#region src/utils/bucket-info.ts
|
|
199
|
+
const getInstant = (point) => {
|
|
200
|
+
if (!point) return;
|
|
201
|
+
if ("date" in point && point.date) return point.date.getTime();
|
|
202
|
+
if ("dateString" in point && point.dateString) return parseAsLocalDate(point.dateString).getTime();
|
|
203
|
+
};
|
|
204
|
+
const getPointSpacingInHours = (data) => {
|
|
205
|
+
return data.reduce((spacing, datom) => {
|
|
206
|
+
const instants = [...new Set(datom.data.map((point) => getInstant(point)))].filter((time) => time !== void 0 && Number.isFinite(time)).sort((a, b) => a - b);
|
|
207
|
+
return instants.reduce((seriesSpacing, time, index) => index === 0 ? seriesSpacing : Math.min(seriesSpacing, Math.abs(differenceInHours(time, instants[index - 1]))), spacing);
|
|
208
|
+
}, Number.POSITIVE_INFINITY);
|
|
209
|
+
};
|
|
210
|
+
const SUB_DAILY_SPACING_HOURS = 23;
|
|
211
|
+
const MONTHLY_SPACING_HOURS = 672;
|
|
212
|
+
const getBucketResolution = (data, tickResolution) => {
|
|
213
|
+
if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
|
|
214
|
+
const spacingInHours = getPointSpacingInHours(data);
|
|
215
|
+
if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
|
|
216
|
+
if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
|
|
217
|
+
return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* How this data was classified, for consumers that render their own labels.
|
|
221
|
+
*
|
|
222
|
+
* @param data - The series the chart draws, in any order, dated by `date` or `dateString`.
|
|
223
|
+
* @param tickResolution - Caller-declared bucket resolution, when known.
|
|
224
|
+
* @return The declared or inferred bucket, and the resolution formats key on.
|
|
225
|
+
*/
|
|
226
|
+
const getBucketInfo = (data, tickResolution) => ({
|
|
227
|
+
bucket: tickResolution ?? getBucketResolution(data),
|
|
228
|
+
displayResolution: getBucketResolution(data, tickResolution)
|
|
229
|
+
});
|
|
230
|
+
//#endregion
|
|
175
231
|
//#region src/utils/format-metric-value.ts
|
|
176
232
|
/**
|
|
177
233
|
* Format a numeric metric value based on type, precision and scale.
|
|
@@ -239,6 +295,31 @@ const formatPercentage = (value) => {
|
|
|
239
295
|
} });
|
|
240
296
|
};
|
|
241
297
|
//#endregion
|
|
298
|
+
//#region src/utils/get-edge-tick-widths.ts
|
|
299
|
+
/**
|
|
300
|
+
* Rendered widths of the first and last tick labels on an axis.
|
|
301
|
+
*
|
|
302
|
+
* An unmeasurable label reserves nothing, which is what a width of 0 already
|
|
303
|
+
* means to every caller, so it is reported as 0 rather than as its own case.
|
|
304
|
+
*
|
|
305
|
+
* @param ticks - Tick values, in axis order.
|
|
306
|
+
* @param formatTick - Function to format a tick.
|
|
307
|
+
* @param {object} labelStyle - Style object for the label.
|
|
308
|
+
* @return {object} - Widths in pixels.
|
|
309
|
+
*/
|
|
310
|
+
const getEdgeTickWidths = (ticks, formatTick, labelStyle) => {
|
|
311
|
+
if (!ticks.length) return {
|
|
312
|
+
first: 0,
|
|
313
|
+
last: 0
|
|
314
|
+
};
|
|
315
|
+
const lastIndex = ticks.length - 1;
|
|
316
|
+
const label = (tick, index) => String(formatTick ? formatTick(tick, index, []) ?? "" : tick);
|
|
317
|
+
return {
|
|
318
|
+
first: getStringWidth(label(ticks[0], 0), labelStyle) ?? 0,
|
|
319
|
+
last: getStringWidth(label(ticks[lastIndex], lastIndex), labelStyle) ?? 0
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
//#endregion
|
|
242
323
|
//#region src/utils/get-longest-tick-width.ts
|
|
243
324
|
/**
|
|
244
325
|
* Returns the width of the longest tick.
|
|
@@ -384,20 +465,6 @@ const parseHslString = (hslString) => {
|
|
|
384
465
|
];
|
|
385
466
|
};
|
|
386
467
|
/**
|
|
387
|
-
* Parse an RGB string like 'rgb(255, 0, 0)' into a hex color.
|
|
388
|
-
*
|
|
389
|
-
* @deprecated Use normalizeColorToHex() instead, which handles all color formats including rgb() and rgba().
|
|
390
|
-
* @param rgbString - RGB color string (not RGBA)
|
|
391
|
-
* @return hex color string or null if invalid
|
|
392
|
-
*/
|
|
393
|
-
const parseRgbString = (rgbString) => {
|
|
394
|
-
const lower = rgbString.toLowerCase().trim();
|
|
395
|
-
if (!lower.startsWith("rgb(") || lower.startsWith("rgba(")) return null;
|
|
396
|
-
const parsed = color(lower);
|
|
397
|
-
if (!parsed) return null;
|
|
398
|
-
return parsed.formatHex();
|
|
399
|
-
};
|
|
400
|
-
/**
|
|
401
468
|
* Normalize any CSS color value to a hex color string.
|
|
402
469
|
* Handles hex, HSL, HSLA, RGB, RGBA, named CSS colors, and CSS variables.
|
|
403
470
|
*
|
|
@@ -625,6 +692,106 @@ const resolveFontSize = (val) => {
|
|
|
625
692
|
}
|
|
626
693
|
};
|
|
627
694
|
//#endregion
|
|
695
|
+
//#region src/utils/date-formatting.ts
|
|
696
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
697
|
+
const getFormatter$1 = (options, { locale, timeZone }) => {
|
|
698
|
+
const key = `${locale ?? ""}|${timeZone ?? ""}|${JSON.stringify(options)}`;
|
|
699
|
+
let formatter = formatters.get(key);
|
|
700
|
+
if (!formatter) {
|
|
701
|
+
formatter = new Intl.DateTimeFormat(locale, {
|
|
702
|
+
...options,
|
|
703
|
+
timeZone
|
|
704
|
+
});
|
|
705
|
+
formatters.set(key, formatter);
|
|
706
|
+
}
|
|
707
|
+
return formatter;
|
|
708
|
+
};
|
|
709
|
+
const isUsable = (formatting) => {
|
|
710
|
+
try {
|
|
711
|
+
Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
|
|
712
|
+
return true;
|
|
713
|
+
} catch {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
const toBcp47 = (locale) => locale?.replace(/_/g, "-");
|
|
718
|
+
/**
|
|
719
|
+
* Repairs a host locale for `Intl`, dropping either half it still cannot use.
|
|
720
|
+
*
|
|
721
|
+
* Every formatter below is built during render, so an unusable string would throw
|
|
722
|
+
* where React unmounts the whole host tree rather than one chart. WordPress hands
|
|
723
|
+
* out the likeliest one either way: `timezone_string` is `''` on a site set to a
|
|
724
|
+
* raw UTC offset, and has no repair.
|
|
725
|
+
*
|
|
726
|
+
* @param formatting - Locale and time zone as the host supplied them.
|
|
727
|
+
* @return The usable halves, each `undefined` where the host's value was not.
|
|
728
|
+
*/
|
|
729
|
+
const sanitizeFormatting = (formatting) => {
|
|
730
|
+
const repaired = {
|
|
731
|
+
...formatting,
|
|
732
|
+
locale: toBcp47(formatting.locale)
|
|
733
|
+
};
|
|
734
|
+
if (isUsable(repaired)) return repaired;
|
|
735
|
+
const locale = isUsable({ locale: repaired.locale }) ? repaired.locale : void 0;
|
|
736
|
+
const timeZone = isUsable({ timeZone: repaired.timeZone }) ? repaired.timeZone : void 0;
|
|
737
|
+
if (locale === void 0 && formatting.locale !== void 0) warnOnce(`locale:${formatting.locale}`, `locale ${JSON.stringify(formatting.locale)} is not a BCP-47 tag Intl accepts, so dates render in the browser's locale.`);
|
|
738
|
+
if (timeZone === void 0 && formatting.timeZone !== void 0) warnOnce(`timeZone:${formatting.timeZone}`, `timeZone ${JSON.stringify(formatting.timeZone)} is not a zone Intl accepts, so dates render in the browser's zone. Pass an IANA name or a UTC offset such as "+05:30".`);
|
|
739
|
+
return {
|
|
740
|
+
locale,
|
|
741
|
+
timeZone
|
|
742
|
+
};
|
|
743
|
+
};
|
|
744
|
+
/**
|
|
745
|
+
* A date formatter bound to the host's locale and time zone.
|
|
746
|
+
*
|
|
747
|
+
* @param options - Date-part options, as for `Intl.DateTimeFormat`.
|
|
748
|
+
* @param formatting - Host locale and time zone; either half absent leaves the runtime's own.
|
|
749
|
+
* @return Formatter taking a timestamp or a `Date`.
|
|
750
|
+
*/
|
|
751
|
+
const createDateFormatter = (options, formatting) => {
|
|
752
|
+
return (value) => {
|
|
753
|
+
if (!Number.isFinite(Number(value))) return new Date(Number(value)).toLocaleDateString();
|
|
754
|
+
return getFormatter$1(options, formatting).format(value);
|
|
755
|
+
};
|
|
756
|
+
};
|
|
757
|
+
const CLOCK_OPTIONS = {
|
|
758
|
+
month: "numeric",
|
|
759
|
+
hour: "numeric",
|
|
760
|
+
hourCycle: "h23"
|
|
761
|
+
};
|
|
762
|
+
const NO_FIELDS = {
|
|
763
|
+
month: NaN,
|
|
764
|
+
hour: NaN
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* A clock reading calendar fields in the host's time zone.
|
|
768
|
+
*
|
|
769
|
+
* `Date`'s own getters read the browser's zone, so a chart viewed from another
|
|
770
|
+
* country anchors its ticks to the wrong midnights and the wrong January
|
|
771
|
+
* without this.
|
|
772
|
+
*
|
|
773
|
+
* @param timeZone - IANA zone name; absent leaves the runtime's own.
|
|
774
|
+
* @return Reader taking a `Date`.
|
|
775
|
+
*/
|
|
776
|
+
const createZonedClock = (timeZone) => {
|
|
777
|
+
if (!timeZone) return (date) => ({
|
|
778
|
+
month: date.getMonth() + 1,
|
|
779
|
+
hour: date.getHours()
|
|
780
|
+
});
|
|
781
|
+
return (date) => {
|
|
782
|
+
if (!Number.isFinite(Number(date))) return NO_FIELDS;
|
|
783
|
+
const parts = getFormatter$1(CLOCK_OPTIONS, {
|
|
784
|
+
locale: "en-US",
|
|
785
|
+
timeZone
|
|
786
|
+
}).formatToParts(date);
|
|
787
|
+
const read = (type) => Number(parts.find((part) => part.type === type)?.value);
|
|
788
|
+
return {
|
|
789
|
+
month: read("month"),
|
|
790
|
+
hour: read("hour")
|
|
791
|
+
};
|
|
792
|
+
};
|
|
793
|
+
};
|
|
794
|
+
//#endregion
|
|
628
795
|
//#region src/providers/chart-scope/chart-scope-context.ts
|
|
629
796
|
/**
|
|
630
797
|
* The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
|
|
@@ -772,150 +939,29 @@ const SERIES_SLOT_1_FALLBACK = "#3858e9";
|
|
|
772
939
|
/** The catalog pointer for every slot, in slot order. */
|
|
773
940
|
const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
|
|
774
941
|
//#endregion
|
|
775
|
-
//#region src/providers/chart-context/private/theme-override-vars.ts
|
|
776
|
-
const ROLE_FOR_FIELD = [
|
|
777
|
-
["--a8c-charts-color-background", (theme) => theme.backgroundColor],
|
|
778
|
-
["--a8c-charts-color-grid", (theme) => theme.gridStyles?.stroke],
|
|
779
|
-
["--a8c-charts-color-axis", (theme) => theme.xAxisLineStyles?.stroke],
|
|
780
|
-
["--a8c-charts-color-tick", (theme) => theme.xTickLineStyles?.stroke],
|
|
781
|
-
["--a8c-charts-color-label-axis", (theme) => theme.svgLabelSmall?.fill],
|
|
782
|
-
["--a8c-charts-color-label-background", (theme) => theme.labelBackgroundColor],
|
|
783
|
-
["--a8c-charts-color-label-on-fill", (theme) => theme.labelTextColor],
|
|
784
|
-
...Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => theme.colors?.[index]])
|
|
785
|
-
];
|
|
786
|
-
let warnedAboutExtraColors = false;
|
|
787
|
-
/**
|
|
788
|
-
* Warns once when a consumer passes more colors than there are slots to publish them into.
|
|
789
|
-
*
|
|
790
|
-
* @param count - How many entries `theme.colors` holds.
|
|
791
|
-
*/
|
|
792
|
-
function warnOnceAboutExtraColors(count) {
|
|
793
|
-
if (warnedAboutExtraColors || process.env.NODE_ENV === "production") return;
|
|
794
|
-
warnedAboutExtraColors = true;
|
|
795
|
-
console.warn(`[Charts] theme.colors holds ${count} colors and the palette has 5 slots, so entries past the 5th are ignored. Set a per-series color with \`options.stroke\` instead.`);
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* The variable a `theme` prop override is published as, one layer outside the role itself.
|
|
799
|
-
*
|
|
800
|
-
* `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.
|
|
801
|
-
*
|
|
802
|
-
* @param role - The catalog role being overridden.
|
|
803
|
-
* @return The custom property carrying the consumer's value.
|
|
804
|
-
*/
|
|
805
|
-
const themeLayerVar = (role) => `${role}-theme`;
|
|
806
|
-
ROLE_FOR_FIELD.map(([role]) => role);
|
|
807
|
-
/**
|
|
808
|
-
* Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
|
|
809
|
-
*
|
|
810
|
-
* 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 color. Verified in Chrome: `--role-theme: var(--role, blue); --role: var(--role-theme, green)` leaves `--role` empty, not `green`.
|
|
811
|
-
*
|
|
812
|
-
* Three things this has to get right, none of which a plain substring test gets:
|
|
813
|
-
*
|
|
814
|
-
* `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
|
|
815
|
-
*
|
|
816
|
-
* 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.
|
|
817
|
-
*
|
|
818
|
-
* `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.
|
|
819
|
-
*
|
|
820
|
-
* @param value - The consumer's value for the field.
|
|
821
|
-
* @param role - The catalog role this value would override.
|
|
822
|
-
* @return True when the value reads `role`, so it must not be published.
|
|
823
|
-
*/
|
|
824
|
-
const readsOwnRole = (value, role) => value.trim() === role || new RegExp(`var\\(\\s*${role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(value);
|
|
825
|
-
/**
|
|
826
|
-
* Maps a sparse consumer theme onto the theme-layer variables its overridden catalog roles read, so CSS-painted and JS-resolved colors read one source.
|
|
827
|
-
*
|
|
828
|
-
* 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.
|
|
829
|
-
*
|
|
830
|
-
* `theme.colors` publishes through the same mechanism, one slot per entry, so a CSS declaration of `--a8c-charts-color-series-N` still beats it and a short array leaves the later slots unset rather than blank.
|
|
831
|
-
*
|
|
832
|
-
* @param theme - The consumer's `theme` prop, before merging with the default theme.
|
|
833
|
-
* @return The wrapper's custom properties, and the roles the consumer overrode.
|
|
834
|
-
*/
|
|
835
|
-
const themeOverrideVars = (theme) => {
|
|
836
|
-
if (!theme) return {
|
|
837
|
-
vars: {},
|
|
838
|
-
roles: []
|
|
839
|
-
};
|
|
840
|
-
if (Array.isArray(theme.colors) && theme.colors.length > 5) warnOnceAboutExtraColors(theme.colors.length);
|
|
841
|
-
const vars = {};
|
|
842
|
-
const roles = [];
|
|
843
|
-
for (const [role, read] of ROLE_FOR_FIELD) {
|
|
844
|
-
const value = read(theme);
|
|
845
|
-
if (typeof value !== "string" || value === "") continue;
|
|
846
|
-
roles.push(role);
|
|
847
|
-
if (!readsOwnRole(value, role)) vars[themeLayerVar(role)] = value;
|
|
848
|
-
}
|
|
849
|
-
return {
|
|
850
|
-
vars,
|
|
851
|
-
roles
|
|
852
|
-
};
|
|
853
|
-
};
|
|
854
|
-
//#endregion
|
|
855
942
|
//#region src/providers/chart-context/themes.ts
|
|
856
943
|
/**
|
|
857
|
-
* Default theme configuration
|
|
944
|
+
* Default theme configuration: the shape and spacing a consumer can override.
|
|
945
|
+
*
|
|
946
|
+
* For a color, set the matching `--a8c-charts-color-*` role in CSS instead; the `var()`
|
|
947
|
+
* chains that deliver those live in `private/catalog-pointers.ts`.
|
|
858
948
|
*/
|
|
859
949
|
const defaultTheme = {
|
|
860
|
-
|
|
861
|
-
labelBackgroundColor: "var(--a8c-charts-color-label-background, transparent)",
|
|
862
|
-
labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
|
|
863
|
-
colors: [...SERIES_PALETTE_POINTERS],
|
|
864
|
-
gridStyles: {
|
|
865
|
-
stroke: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
866
|
-
strokeWidth: 1
|
|
867
|
-
},
|
|
950
|
+
gridStyles: { strokeWidth: 1 },
|
|
868
951
|
tickLength: 4,
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
xTickLineStyles: {
|
|
872
|
-
stroke: "var(--a8c-charts-color-tick, #dbdbdb)",
|
|
873
|
-
strokeWidth: 1
|
|
874
|
-
},
|
|
875
|
-
xAxisLineStyles: {
|
|
876
|
-
stroke: "var(--a8c-charts-color-axis, #dbdbdb)",
|
|
877
|
-
strokeWidth: 1
|
|
878
|
-
},
|
|
952
|
+
xTickLineStyles: { strokeWidth: 1 },
|
|
953
|
+
xAxisLineStyles: { strokeWidth: 1 },
|
|
879
954
|
legend: {
|
|
880
|
-
labelStyles: {
|
|
955
|
+
labelStyles: {},
|
|
881
956
|
containerStyles: {},
|
|
882
957
|
shapeStyles: []
|
|
883
958
|
},
|
|
884
959
|
seriesLineStyles: [],
|
|
885
960
|
glyphs: [],
|
|
886
|
-
svgLabelSmall: {
|
|
887
|
-
fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
888
|
-
fontFamily: "inherit"
|
|
889
|
-
},
|
|
961
|
+
svgLabelSmall: { fontFamily: "inherit" },
|
|
890
962
|
svgLabelBig: { fontFamily: "inherit" },
|
|
891
|
-
annotationStyles: {
|
|
892
|
-
|
|
893
|
-
anchorLineStroke: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
894
|
-
backgroundFill: "var(--a8c-charts-color-surface, #fff)"
|
|
895
|
-
},
|
|
896
|
-
connector: { stroke: "var(--a8c-charts-color-annotation, #1e1e1e)" },
|
|
897
|
-
circleSubject: {
|
|
898
|
-
stroke: "transparent",
|
|
899
|
-
fill: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
900
|
-
radius: 5
|
|
901
|
-
}
|
|
902
|
-
},
|
|
903
|
-
geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)" },
|
|
904
|
-
leaderboardChart: {
|
|
905
|
-
rowGap: 12,
|
|
906
|
-
columnGap: 4,
|
|
907
|
-
labelSpacing: "xs",
|
|
908
|
-
deltaColors: [
|
|
909
|
-
"var(--a8c-charts-color-trend-down, #cc1818)",
|
|
910
|
-
"var(--a8c-charts-color-trend-neutral, #707070)",
|
|
911
|
-
"var(--a8c-charts-color-trend-up, #008030)"
|
|
912
|
-
]
|
|
913
|
-
},
|
|
914
|
-
conversionFunnelChart: {
|
|
915
|
-
backgroundColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
916
|
-
positiveChangeColor: "var(--a8c-charts-color-trend-up, #008030)",
|
|
917
|
-
negativeChangeColor: "var(--a8c-charts-color-trend-down, #cc1818)"
|
|
918
|
-
},
|
|
963
|
+
annotationStyles: {},
|
|
964
|
+
leaderboardChart: { labelSpacing: "xs" },
|
|
919
965
|
lineChart: { lineStyles: { comparison: {
|
|
920
966
|
strokeDasharray: "4 4",
|
|
921
967
|
strokeLinecap: "square"
|
|
@@ -939,70 +985,9 @@ const defaultTheme = {
|
|
|
939
985
|
}
|
|
940
986
|
};
|
|
941
987
|
//#endregion
|
|
942
|
-
//#region src/providers/chart-context/private/with-catalog-pointers.ts
|
|
943
|
-
/**
|
|
944
|
-
* The pointer one palette slot is restored to.
|
|
945
|
-
*
|
|
946
|
-
* Unlike the other roles, this keeps the consumer's own color as the pointer's terminal literal instead of the catalog's. The literal is only ever reached where `getComputedStyle` resolves nothing — SSR and jsdom — and there the palette is the one field where falling back to the catalog default would be visible: every series would paint the same seeded blue. In a browser the slot resolves first, so a CSS declaration of it still outranks the consumer's `theme.colors`.
|
|
947
|
-
*
|
|
948
|
-
* @param index - The slot's zero-based index in `colors`.
|
|
949
|
-
* @param value - The merged theme's entry for it.
|
|
950
|
-
* @return The catalog pointer for that slot.
|
|
951
|
-
*/
|
|
952
|
-
const seriesPointer = (index, value) => {
|
|
953
|
-
const role = seriesRole(index + 1);
|
|
954
|
-
if (typeof value === "string" && value.startsWith(`var(${role}`)) return value;
|
|
955
|
-
if (typeof value !== "string" || value === "") return SERIES_PALETTE_POINTERS[index];
|
|
956
|
-
return `var(${role}, ${value})`;
|
|
957
|
-
};
|
|
958
|
-
const CATALOG_RESTORE_FOR_ROLE = {
|
|
959
|
-
...Object.fromEntries(Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => ({ colors: SERIES_PALETTE_POINTERS.map((_pointer, slot) => seriesPointer(slot, theme.colors?.[slot])) })])),
|
|
960
|
-
"--a8c-charts-color-background": () => ({ backgroundColor: defaultTheme.backgroundColor }),
|
|
961
|
-
"--a8c-charts-color-grid": (theme) => ({ gridStyles: {
|
|
962
|
-
...theme.gridStyles,
|
|
963
|
-
stroke: defaultTheme.gridStyles.stroke
|
|
964
|
-
} }),
|
|
965
|
-
"--a8c-charts-color-axis": (theme) => ({ xAxisLineStyles: {
|
|
966
|
-
...theme.xAxisLineStyles,
|
|
967
|
-
stroke: defaultTheme.xAxisLineStyles.stroke
|
|
968
|
-
} }),
|
|
969
|
-
"--a8c-charts-color-tick": (theme) => ({ xTickLineStyles: {
|
|
970
|
-
...theme.xTickLineStyles,
|
|
971
|
-
stroke: defaultTheme.xTickLineStyles.stroke
|
|
972
|
-
} }),
|
|
973
|
-
"--a8c-charts-color-label-axis": (theme) => ({ svgLabelSmall: {
|
|
974
|
-
...theme.svgLabelSmall,
|
|
975
|
-
fill: defaultTheme.svgLabelSmall.fill
|
|
976
|
-
} }),
|
|
977
|
-
"--a8c-charts-color-label-background": () => ({ labelBackgroundColor: defaultTheme.labelBackgroundColor }),
|
|
978
|
-
"--a8c-charts-color-label-on-fill": () => ({ labelTextColor: defaultTheme.labelTextColor })
|
|
979
|
-
};
|
|
980
|
-
/**
|
|
981
|
-
* 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.
|
|
982
|
-
*
|
|
983
|
-
* 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`.
|
|
984
|
-
*
|
|
985
|
-
* Every field outside the 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.
|
|
986
|
-
*
|
|
987
|
-
* @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
|
|
988
|
-
* @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
|
|
989
|
-
* @return A new theme object; `merged` is not mutated.
|
|
990
|
-
*/
|
|
991
|
-
const withCatalogPointers = (merged, overriddenRoles) => {
|
|
992
|
-
let result = merged;
|
|
993
|
-
for (const role of overriddenRoles) {
|
|
994
|
-
const restore = CATALOG_RESTORE_FOR_ROLE[role];
|
|
995
|
-
if (restore) result = {
|
|
996
|
-
...result,
|
|
997
|
-
...restore(result)
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
return result;
|
|
1001
|
-
};
|
|
1002
|
-
//#endregion
|
|
1003
988
|
//#region src/providers/chart-context/global-charts-provider.tsx
|
|
1004
989
|
const GlobalChartsContext = createContext(null);
|
|
1005
|
-
const GlobalChartsProvider = ({ children, theme }) => {
|
|
990
|
+
const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
1006
991
|
const [charts, setCharts] = useState(() => /* @__PURE__ */ new Map());
|
|
1007
992
|
const [hiddenSeries, setHiddenSeries] = useState(() => /* @__PURE__ */ new Map());
|
|
1008
993
|
const seededCharts = useRef(/* @__PURE__ */ new Set());
|
|
@@ -1012,11 +997,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1012
997
|
wrapperRef.current = node;
|
|
1013
998
|
setScopeNode(node);
|
|
1014
999
|
}, []);
|
|
1015
|
-
const
|
|
1016
|
-
const providerTheme = useMemo(() => {
|
|
1017
|
-
if (!theme) return defaultTheme;
|
|
1018
|
-
return withCatalogPointers(mergeThemes(defaultTheme, theme), overriddenRoles);
|
|
1019
|
-
}, [theme, overriddenRoles]);
|
|
1000
|
+
const providerTheme = useMemo(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
|
|
1020
1001
|
const [colorCache, setColorCache] = useState(() => ({
|
|
1021
1002
|
colors: [],
|
|
1022
1003
|
hues: [],
|
|
@@ -1032,7 +1013,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1032
1013
|
const existingHslColors = [];
|
|
1033
1014
|
let minHue = 360;
|
|
1034
1015
|
let maxHue = 0;
|
|
1035
|
-
for (const color of
|
|
1016
|
+
for (const color of SERIES_PALETTE_POINTERS) {
|
|
1036
1017
|
const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
|
|
1037
1018
|
if (normalizedColor.startsWith("#")) {
|
|
1038
1019
|
resolvedColors.push(normalizedColor);
|
|
@@ -1057,7 +1038,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1057
1038
|
minHue,
|
|
1058
1039
|
maxHue
|
|
1059
1040
|
});
|
|
1060
|
-
}, [
|
|
1041
|
+
}, []);
|
|
1061
1042
|
useEffect(() => {
|
|
1062
1043
|
if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
|
|
1063
1044
|
}, [colorCache]);
|
|
@@ -1149,12 +1130,17 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1149
1130
|
const set = hiddenSeries.get(chartId);
|
|
1150
1131
|
return set ? new Set(set) : /* @__PURE__ */ new Set();
|
|
1151
1132
|
}, [hiddenSeries]);
|
|
1133
|
+
const formatting = useMemo(() => sanitizeFormatting({
|
|
1134
|
+
locale,
|
|
1135
|
+
timeZone
|
|
1136
|
+
}), [locale, timeZone]);
|
|
1152
1137
|
const value = useMemo(() => ({
|
|
1153
1138
|
charts,
|
|
1154
1139
|
registerChart,
|
|
1155
1140
|
unregisterChart,
|
|
1156
1141
|
getChartData,
|
|
1157
1142
|
theme: providerTheme,
|
|
1143
|
+
formatting,
|
|
1158
1144
|
getElementStyles,
|
|
1159
1145
|
toggleSeriesVisibility,
|
|
1160
1146
|
setSeriesVisibility,
|
|
@@ -1170,6 +1156,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1170
1156
|
unregisterChart,
|
|
1171
1157
|
getChartData,
|
|
1172
1158
|
providerTheme,
|
|
1159
|
+
formatting,
|
|
1173
1160
|
getElementStyles,
|
|
1174
1161
|
toggleSeriesVisibility,
|
|
1175
1162
|
setSeriesVisibility,
|
|
@@ -1185,10 +1172,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
1185
1172
|
children: /* @__PURE__ */ jsx("div", {
|
|
1186
1173
|
ref: setWrapperNode,
|
|
1187
1174
|
className: CHART_SCOPE_CLASS,
|
|
1188
|
-
style: {
|
|
1189
|
-
display: "contents",
|
|
1190
|
-
...overrideVars
|
|
1191
|
-
},
|
|
1175
|
+
style: { display: "contents" },
|
|
1192
1176
|
children: /* @__PURE__ */ jsx(ChartScopeContext.Provider, {
|
|
1193
1177
|
value: scopeNode,
|
|
1194
1178
|
children
|
|
@@ -1252,6 +1236,39 @@ const useDeepMemo = (value) => {
|
|
|
1252
1236
|
return ref.current;
|
|
1253
1237
|
};
|
|
1254
1238
|
//#endregion
|
|
1239
|
+
//#region src/providers/chart-context/private/catalog-pointers.ts
|
|
1240
|
+
const CATALOG_POINTERS = {
|
|
1241
|
+
background: "var(--a8c-charts-color-background, #fff)",
|
|
1242
|
+
labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
|
|
1243
|
+
grid: "var(--a8c-charts-color-grid, #dbdbdb)",
|
|
1244
|
+
axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
|
|
1245
|
+
tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
|
|
1246
|
+
axisY: "var(--a8c-charts-color-axis-y, none)",
|
|
1247
|
+
tickY: "var(--a8c-charts-color-tick-y, none)",
|
|
1248
|
+
annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
|
|
1249
|
+
surface: "var(--a8c-charts-color-surface, #fff)",
|
|
1250
|
+
surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
|
|
1251
|
+
trendUp: "var(--a8c-charts-color-trend-up, #008030)",
|
|
1252
|
+
trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
|
|
1253
|
+
series: SERIES_PALETTE_POINTERS
|
|
1254
|
+
};
|
|
1255
|
+
/**
|
|
1256
|
+
* The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
|
|
1257
|
+
* rides along as the base the theme and the per-datum styles merge onto.
|
|
1258
|
+
*/
|
|
1259
|
+
const ANNOTATION_POINTERS = {
|
|
1260
|
+
label: {
|
|
1261
|
+
anchorLineStroke: CATALOG_POINTERS.annotation,
|
|
1262
|
+
backgroundFill: CATALOG_POINTERS.surface
|
|
1263
|
+
},
|
|
1264
|
+
connector: { stroke: CATALOG_POINTERS.annotation },
|
|
1265
|
+
circleSubject: {
|
|
1266
|
+
stroke: "transparent",
|
|
1267
|
+
fill: CATALOG_POINTERS.annotation,
|
|
1268
|
+
radius: 5
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
//#endregion
|
|
1255
1272
|
//#region src/providers/chart-scope/use-chart-scope-element.ts
|
|
1256
1273
|
/**
|
|
1257
1274
|
* Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
|
|
@@ -1278,13 +1295,34 @@ const useXYChartTheme = (data) => {
|
|
|
1278
1295
|
return useMemo(() => {
|
|
1279
1296
|
const resolve = createCssVariableResolver(scopeElement);
|
|
1280
1297
|
const resolveColor = (value) => value ? resolve(value) ?? value : value;
|
|
1281
|
-
const paletteColors = [...JSON.parse(seriesColorKey), ...
|
|
1282
|
-
const
|
|
1298
|
+
const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
|
|
1299
|
+
const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
|
|
1300
|
+
const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
|
|
1283
1301
|
return buildChartTheme({
|
|
1284
1302
|
...theme,
|
|
1303
|
+
gridColor: "",
|
|
1304
|
+
gridColorDark: "",
|
|
1285
1305
|
colors: paletteColors,
|
|
1286
|
-
backgroundColor: resolveColor(
|
|
1287
|
-
htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0
|
|
1306
|
+
backgroundColor: resolveColor(CATALOG_POINTERS.background),
|
|
1307
|
+
htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
|
|
1308
|
+
gridStyles: {
|
|
1309
|
+
...theme.gridStyles,
|
|
1310
|
+
stroke: CATALOG_POINTERS.grid
|
|
1311
|
+
},
|
|
1312
|
+
xAxisLineStyles: {
|
|
1313
|
+
...theme.xAxisLineStyles,
|
|
1314
|
+
stroke: CATALOG_POINTERS.axisX
|
|
1315
|
+
},
|
|
1316
|
+
xTickLineStyles: {
|
|
1317
|
+
...theme.xTickLineStyles,
|
|
1318
|
+
stroke: CATALOG_POINTERS.tickX
|
|
1319
|
+
},
|
|
1320
|
+
yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
|
|
1321
|
+
yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
|
|
1322
|
+
svgLabelSmall: {
|
|
1323
|
+
...theme.svgLabelSmall,
|
|
1324
|
+
fill: CATALOG_POINTERS.labelAxis
|
|
1325
|
+
}
|
|
1288
1326
|
});
|
|
1289
1327
|
}, [
|
|
1290
1328
|
theme,
|
|
@@ -1293,13 +1331,28 @@ const useXYChartTheme = (data) => {
|
|
|
1293
1331
|
]);
|
|
1294
1332
|
};
|
|
1295
1333
|
//#endregion
|
|
1334
|
+
//#region src/providers/chart-context/hooks/use-chart-formatting.ts
|
|
1335
|
+
const RUNTIME_FORMATTING = {};
|
|
1336
|
+
/**
|
|
1337
|
+
* The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
|
|
1338
|
+
*
|
|
1339
|
+
* Falls back to the runtime's own rather than throwing: charts render outside a
|
|
1340
|
+
* provider, and a host that sets neither gets exactly the browser-default
|
|
1341
|
+
* behavior this package had before the context existed.
|
|
1342
|
+
*
|
|
1343
|
+
* @return The host's formatting context.
|
|
1344
|
+
*/
|
|
1345
|
+
const useChartFormatting = () => {
|
|
1346
|
+
return useContext(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
|
|
1347
|
+
};
|
|
1348
|
+
//#endregion
|
|
1296
1349
|
//#region src/hooks/use-chart-data-transform.ts
|
|
1297
1350
|
/**
|
|
1298
1351
|
* Hook that transforms and sorts chart data, handling date parsing and sorting
|
|
1299
1352
|
*
|
|
1300
1353
|
* This hook extracts the common data transformation logic used in both line-chart
|
|
1301
1354
|
* and bar-chart components. It:
|
|
1302
|
-
* 1. Parses date strings into Date objects
|
|
1355
|
+
* 1. Parses date strings into Date objects, dated in the provider's time zone
|
|
1303
1356
|
* 2. Sorts data points by date when date properties are present
|
|
1304
1357
|
* 3. Returns the original data unchanged when no date properties are found
|
|
1305
1358
|
*
|
|
@@ -1307,6 +1360,7 @@ const useXYChartTheme = (data) => {
|
|
|
1307
1360
|
* @return {SeriesData[]} The transformed and sorted data
|
|
1308
1361
|
*/
|
|
1309
1362
|
const useChartDataTransform = (data) => {
|
|
1363
|
+
const { timeZone } = useChartFormatting();
|
|
1310
1364
|
return useMemo(() => {
|
|
1311
1365
|
const firstPoint = data?.[0]?.data?.[0];
|
|
1312
1366
|
if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
|
|
@@ -1315,7 +1369,7 @@ const useChartDataTransform = (data) => {
|
|
|
1315
1369
|
data: series.data.map((point) => {
|
|
1316
1370
|
let date;
|
|
1317
1371
|
if ("date" in point && point.date) date = point.date;
|
|
1318
|
-
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
|
|
1372
|
+
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
|
|
1319
1373
|
return {
|
|
1320
1374
|
...point,
|
|
1321
1375
|
date
|
|
@@ -1325,7 +1379,7 @@ const useChartDataTransform = (data) => {
|
|
|
1325
1379
|
return a.date.getTime() - b.date.getTime();
|
|
1326
1380
|
})
|
|
1327
1381
|
}));
|
|
1328
|
-
}, [data]);
|
|
1382
|
+
}, [data, timeZone]);
|
|
1329
1383
|
};
|
|
1330
1384
|
//#endregion
|
|
1331
1385
|
//#region src/hooks/use-chart-margin.tsx
|
|
@@ -1367,11 +1421,32 @@ const DEFAULT_TICK_LENGTH = 8;
|
|
|
1367
1421
|
* measure them via getLongestTickWidth.
|
|
1368
1422
|
*/
|
|
1369
1423
|
const DEFAULT_Y_TICK_WIDTH = 40;
|
|
1424
|
+
/**
|
|
1425
|
+
* Copy a label style with its lengths spelled out in px.
|
|
1426
|
+
*
|
|
1427
|
+
* `getStringWidth` applies the style through CSSOM. Blink resolves a bare number
|
|
1428
|
+
* on an SVG `<text>`, but that is its own leniency rather than the CSS rule, and
|
|
1429
|
+
* `buildChartTheme` hands us bare numbers.
|
|
1430
|
+
*
|
|
1431
|
+
* @param style - Raw label style from the theme.
|
|
1432
|
+
* @return The same style with px-qualified lengths.
|
|
1433
|
+
*/
|
|
1434
|
+
const toMeasurableStyle = (style) => {
|
|
1435
|
+
if (!style) return style;
|
|
1436
|
+
const fontSize = resolveFontSize(style.fontSize);
|
|
1437
|
+
const { letterSpacing } = style;
|
|
1438
|
+
return {
|
|
1439
|
+
...style,
|
|
1440
|
+
...fontSize === void 0 ? {} : { fontSize: `${fontSize}px` },
|
|
1441
|
+
...typeof letterSpacing === "number" ? { letterSpacing: `${letterSpacing}px` } : {}
|
|
1442
|
+
};
|
|
1443
|
+
};
|
|
1370
1444
|
const getXAxisLabelMetrics = (theme, orientation) => {
|
|
1371
1445
|
const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
|
|
1372
1446
|
return {
|
|
1373
1447
|
fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
|
|
1374
|
-
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
|
|
1448
|
+
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
|
|
1449
|
+
tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
|
|
1375
1450
|
};
|
|
1376
1451
|
};
|
|
1377
1452
|
const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
@@ -1383,7 +1458,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1383
1458
|
const maxY = Math.max(...allDataPoints.map((d) => d.value));
|
|
1384
1459
|
const yScale = createScale({
|
|
1385
1460
|
...options.yScale,
|
|
1386
|
-
domain: [minY, maxY],
|
|
1461
|
+
domain: options.yScale?.domain ?? [minY, maxY],
|
|
1387
1462
|
range: [height, 0]
|
|
1388
1463
|
});
|
|
1389
1464
|
return getTicks(yScale, options.axis?.y?.numTicks);
|
|
@@ -1402,18 +1477,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1402
1477
|
};
|
|
1403
1478
|
const yAxisOrientation = options.axis?.y?.orientation;
|
|
1404
1479
|
const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
|
|
1405
|
-
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
|
|
1480
|
+
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
|
|
1406
1481
|
const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
|
|
1407
1482
|
const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
|
|
1408
|
-
if (
|
|
1409
|
-
|
|
1483
|
+
if (options.axis?.y?.display !== false) {
|
|
1484
|
+
if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
|
|
1485
|
+
else defaultMargin.left = yMarginValue;
|
|
1486
|
+
}
|
|
1410
1487
|
const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
|
|
1411
|
-
const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1488
|
+
const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1412
1489
|
const computedXMargin = fontSize + tickLength;
|
|
1413
1490
|
if (xOrientation === "top") {
|
|
1414
1491
|
defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
|
|
1415
1492
|
defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
|
|
1416
1493
|
} else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
|
|
1494
|
+
if (options.axis?.x?.display !== false) {
|
|
1495
|
+
const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
|
|
1496
|
+
defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
|
|
1497
|
+
defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
|
|
1498
|
+
}
|
|
1417
1499
|
return defaultMargin;
|
|
1418
1500
|
}, [
|
|
1419
1501
|
options,
|
|
@@ -2126,15 +2208,370 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
|
|
|
2126
2208
|
});
|
|
2127
2209
|
};
|
|
2128
2210
|
//#endregion
|
|
2211
|
+
//#region src/components/tooltip/private/bounded-tooltip.tsx
|
|
2212
|
+
const DEFAULT_OFFSET = 10;
|
|
2213
|
+
const POINTER_HEIGHT = 6;
|
|
2214
|
+
const clamp = (position, min, max, size) => Math.min(Math.max(position, min), Math.max(min, max - size));
|
|
2215
|
+
const isClipping = (element) => {
|
|
2216
|
+
const { overflow, overflowX, overflowY } = getComputedStyle(element);
|
|
2217
|
+
return [
|
|
2218
|
+
overflow,
|
|
2219
|
+
overflowX,
|
|
2220
|
+
overflowY
|
|
2221
|
+
].some((value) => value && value !== "visible");
|
|
2222
|
+
};
|
|
2223
|
+
const findClippingAncestor = (wrapper) => {
|
|
2224
|
+
let element = wrapper.parentElement;
|
|
2225
|
+
while (element && element !== document.body) {
|
|
2226
|
+
if (isClipping(element)) return element;
|
|
2227
|
+
element = element.parentElement;
|
|
2228
|
+
}
|
|
2229
|
+
return null;
|
|
2230
|
+
};
|
|
2231
|
+
/**
|
|
2232
|
+
* Where the box goes, in wrapper coordinates. Automatic placement flips to
|
|
2233
|
+
* the side that clips less, then clamps inside `bounds`; below-axis placement
|
|
2234
|
+
* is centered on the anchor and clamped horizontally.
|
|
2235
|
+
*
|
|
2236
|
+
* The flip measures against the visible part of the wrapper, its intersection
|
|
2237
|
+
* with `bounds`. Only the clamp is bound by the page: a box that fits inside the
|
|
2238
|
+
* chart on one side stays there, so it does not reach past the chart into
|
|
2239
|
+
* siblings that paint over it.
|
|
2240
|
+
*
|
|
2241
|
+
* @param params - Anchor, offsets, the box size and the rects to keep inside.
|
|
2242
|
+
* @param params.left - Anchor x, in wrapper coordinates.
|
|
2243
|
+
* @param params.top - Anchor y, in wrapper coordinates.
|
|
2244
|
+
* @param params.offsetLeft - Gap between the anchor and the box, horizontally.
|
|
2245
|
+
* @param params.offsetTop - Gap between the anchor and the box, vertically.
|
|
2246
|
+
* @param params.box - Rendered size of the box.
|
|
2247
|
+
* @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
|
|
2248
|
+
* @param params.placement - Fixed below-axis placement or automatic flipping.
|
|
2249
|
+
* @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
|
|
2250
|
+
* @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
|
|
2251
|
+
*/
|
|
2252
|
+
const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bounds, placement = "auto" }) => {
|
|
2253
|
+
if (placement === "below-axis") return {
|
|
2254
|
+
x: Math.round(clamp(left - box.width / 2, bounds.left, bounds.right, box.width)),
|
|
2255
|
+
y: top + POINTER_HEIGHT
|
|
2256
|
+
};
|
|
2257
|
+
const fit = {
|
|
2258
|
+
left: Math.max(wrapper.left, bounds.left),
|
|
2259
|
+
top: Math.max(wrapper.top, bounds.top),
|
|
2260
|
+
right: Math.min(wrapper.right, bounds.right),
|
|
2261
|
+
bottom: Math.min(wrapper.bottom, bounds.bottom)
|
|
2262
|
+
};
|
|
2263
|
+
const rightX = left + offsetLeft;
|
|
2264
|
+
const leftX = left - offsetLeft - box.width;
|
|
2265
|
+
const rightOverflow = rightX + box.width - fit.right;
|
|
2266
|
+
const leftOverflow = fit.left - leftX;
|
|
2267
|
+
let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
|
|
2268
|
+
const downY = top + offsetTop;
|
|
2269
|
+
const upY = top - offsetTop - box.height;
|
|
2270
|
+
const downOverflow = downY + box.height - fit.bottom;
|
|
2271
|
+
const upOverflow = fit.top - upY;
|
|
2272
|
+
let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
|
|
2273
|
+
x = clamp(x, bounds.left, bounds.right, box.width);
|
|
2274
|
+
y = clamp(y, bounds.top, bounds.bottom, box.height);
|
|
2275
|
+
return {
|
|
2276
|
+
x: Math.round(x),
|
|
2277
|
+
y: Math.round(y)
|
|
2278
|
+
};
|
|
2279
|
+
};
|
|
2280
|
+
/**
|
|
2281
|
+
* visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
|
|
2282
|
+
* nearest clipping ancestor — or the viewport when there is none — rather than
|
|
2283
|
+
* inside its own parent. Rendered in-tree, a tooltip's parent is the chart
|
|
2284
|
+
* wrapper, which is often narrower than the box; measuring against the parent
|
|
2285
|
+
* alone would let the box spill into an `overflow: hidden` card and be cut off.
|
|
2286
|
+
*
|
|
2287
|
+
* Re-measures on every render, so a box whose content changes width between
|
|
2288
|
+
* two hovers is placed for its current size. See `getBoundedPosition` for the
|
|
2289
|
+
* below-axis exception.
|
|
2290
|
+
*
|
|
2291
|
+
* @param props - visx `Tooltip` props.
|
|
2292
|
+
* @param props.left - Anchor x, in wrapper coordinates.
|
|
2293
|
+
* @param props.top - Anchor y, in wrapper coordinates.
|
|
2294
|
+
* @param props.offsetLeft - Gap between the anchor and the box, horizontally.
|
|
2295
|
+
* @param props.offsetTop - Gap between the anchor and the box, vertically.
|
|
2296
|
+
* @param props.style - Box styles; visx's defaults unless `unstyled`.
|
|
2297
|
+
* @param props.unstyled - Skip `style` and leave the box bare.
|
|
2298
|
+
* @param props.children - Box content.
|
|
2299
|
+
* @param props.placement - Fixed below-axis placement or automatic flipping.
|
|
2300
|
+
* @return The tooltip box.
|
|
2301
|
+
*/
|
|
2302
|
+
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
|
|
2303
|
+
const nodeRef = useRef(null);
|
|
2304
|
+
const clipRef = useRef(null);
|
|
2305
|
+
const [position, setPosition] = useState(null);
|
|
2306
|
+
useLayoutEffect(() => {
|
|
2307
|
+
const node = nodeRef.current;
|
|
2308
|
+
const wrapper = node?.parentElement;
|
|
2309
|
+
if (!node || !wrapper) return;
|
|
2310
|
+
if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
|
|
2311
|
+
wrapper,
|
|
2312
|
+
clip: findClippingAncestor(wrapper)
|
|
2313
|
+
};
|
|
2314
|
+
const own = node.getBoundingClientRect();
|
|
2315
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
2316
|
+
const clipRect = clipRef.current.clip?.getBoundingClientRect() ?? {
|
|
2317
|
+
left: 0,
|
|
2318
|
+
top: 0,
|
|
2319
|
+
right: window.innerWidth,
|
|
2320
|
+
bottom: window.innerHeight
|
|
2321
|
+
};
|
|
2322
|
+
const next = getBoundedPosition({
|
|
2323
|
+
left,
|
|
2324
|
+
top,
|
|
2325
|
+
offsetLeft,
|
|
2326
|
+
offsetTop,
|
|
2327
|
+
placement,
|
|
2328
|
+
box: {
|
|
2329
|
+
width: own.width,
|
|
2330
|
+
height: own.height
|
|
2331
|
+
},
|
|
2332
|
+
wrapper: {
|
|
2333
|
+
left: 0,
|
|
2334
|
+
top: 0,
|
|
2335
|
+
right: wrapperRect.width,
|
|
2336
|
+
bottom: wrapperRect.height
|
|
2337
|
+
},
|
|
2338
|
+
bounds: {
|
|
2339
|
+
left: clipRect.left - wrapperRect.left,
|
|
2340
|
+
top: clipRect.top - wrapperRect.top,
|
|
2341
|
+
right: clipRect.right - wrapperRect.left,
|
|
2342
|
+
bottom: clipRect.bottom - wrapperRect.top
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
setPosition((current) => current && current.x === next.x && current.y === next.y ? current : next);
|
|
2346
|
+
}, [
|
|
2347
|
+
left,
|
|
2348
|
+
top,
|
|
2349
|
+
offsetLeft,
|
|
2350
|
+
offsetTop,
|
|
2351
|
+
children,
|
|
2352
|
+
placement
|
|
2353
|
+
]);
|
|
2354
|
+
const x = position?.x ?? left + offsetLeft;
|
|
2355
|
+
const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
|
|
2356
|
+
return /* @__PURE__ */ jsxs(Tooltip, {
|
|
2357
|
+
ref: nodeRef,
|
|
2358
|
+
style: {
|
|
2359
|
+
position: "absolute",
|
|
2360
|
+
left: 0,
|
|
2361
|
+
top: 0,
|
|
2362
|
+
transform: `translate(${x}px, ${y}px)`,
|
|
2363
|
+
...!unstyled && style
|
|
2364
|
+
},
|
|
2365
|
+
...rest,
|
|
2366
|
+
children: [placement === "below-axis" && /* @__PURE__ */ jsx("span", {
|
|
2367
|
+
"aria-hidden": "true",
|
|
2368
|
+
style: {
|
|
2369
|
+
position: "absolute",
|
|
2370
|
+
left: left - x - POINTER_HEIGHT,
|
|
2371
|
+
top: -6,
|
|
2372
|
+
width: 12,
|
|
2373
|
+
height: POINTER_HEIGHT,
|
|
2374
|
+
background: "inherit",
|
|
2375
|
+
clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
|
|
2376
|
+
pointerEvents: "none"
|
|
2377
|
+
}
|
|
2378
|
+
}), children]
|
|
2379
|
+
});
|
|
2380
|
+
};
|
|
2381
|
+
//#endregion
|
|
2382
|
+
//#region src/components/tooltip/xy-chart-tooltip.tsx
|
|
2383
|
+
const CROSSHAIR_STROKE_WIDTH = 1.5;
|
|
2384
|
+
const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2385
|
+
"stroke",
|
|
2386
|
+
"strokeWidth",
|
|
2387
|
+
"strokeOpacity",
|
|
2388
|
+
"strokeDasharray",
|
|
2389
|
+
"strokeLinecap",
|
|
2390
|
+
"opacity"
|
|
2391
|
+
]);
|
|
2392
|
+
const crosshairPaintProps = (props = {}) => {
|
|
2393
|
+
const pickPaint = (value) => Object.fromEntries(Object.entries(value).filter(([key]) => CROSSHAIR_PAINT_PROPERTIES.has(key)));
|
|
2394
|
+
return {
|
|
2395
|
+
...pickPaint(props),
|
|
2396
|
+
...props.className === void 0 ? {} : { className: props.className },
|
|
2397
|
+
...props.style === void 0 ? {} : { style: pickPaint(props.style) }
|
|
2398
|
+
};
|
|
2399
|
+
};
|
|
2400
|
+
const DEFAULT_GLYPH_RADIUS = 4;
|
|
2401
|
+
const FALLBACK_COLOR = "#222";
|
|
2402
|
+
const DEFAULT_TOOLTIP_Z_INDEX = 3;
|
|
2403
|
+
const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
|
|
2404
|
+
"scroll",
|
|
2405
|
+
"debounce",
|
|
2406
|
+
"resizeObserverPolyfill"
|
|
2407
|
+
]);
|
|
2408
|
+
const isValidNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
2409
|
+
const scaleBandwidth = (scale) => {
|
|
2410
|
+
const bandwidth = scale?.bandwidth;
|
|
2411
|
+
return typeof bandwidth === "function" ? Number(bandwidth()) : 0;
|
|
2412
|
+
};
|
|
2413
|
+
const DefaultGlyph$1 = ({ x, y, size, color, glyphStyle, seriesKey }) => {
|
|
2414
|
+
const { theme } = useContext(DataContext) || {};
|
|
2415
|
+
return /* @__PURE__ */ jsx("circle", {
|
|
2416
|
+
cx: x,
|
|
2417
|
+
cy: y,
|
|
2418
|
+
r: size,
|
|
2419
|
+
fill: color,
|
|
2420
|
+
stroke: theme?.backgroundColor,
|
|
2421
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2422
|
+
paintOrder: "fill",
|
|
2423
|
+
...glyphStyle
|
|
2424
|
+
});
|
|
2425
|
+
};
|
|
2426
|
+
const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ jsx(DefaultGlyph$1, {
|
|
2427
|
+
seriesKey: key,
|
|
2428
|
+
...props
|
|
2429
|
+
}, key);
|
|
2430
|
+
const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, tooltipPlacement = "auto", zIndex = DEFAULT_TOOLTIP_Z_INDEX, style, ...rest }) => {
|
|
2431
|
+
const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
|
|
2432
|
+
const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = useContext(DataContext) || {};
|
|
2433
|
+
const tooltipContent = renderTooltip ? renderTooltip({
|
|
2434
|
+
...tooltipContext,
|
|
2435
|
+
colorScale
|
|
2436
|
+
}) : null;
|
|
2437
|
+
if (tooltipContent == null) return null;
|
|
2438
|
+
const scaleX = xScale;
|
|
2439
|
+
const scaleY = yScale;
|
|
2440
|
+
const getDatumLeftTop = (key, datum) => {
|
|
2441
|
+
const entry = dataRegistry?.get(key);
|
|
2442
|
+
return {
|
|
2443
|
+
left: scaleX && entry?.xAccessor ? Number(scaleX(entry.xAccessor(datum))) + scaleBandwidth(xScale) / 2 : void 0,
|
|
2444
|
+
top: scaleY && entry?.yAccessor ? Number(scaleY(entry.yAccessor(datum))) + scaleBandwidth(yScale) / 2 : void 0
|
|
2445
|
+
};
|
|
2446
|
+
};
|
|
2447
|
+
const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
|
|
2448
|
+
let { tooltipLeft, tooltipTop } = tooltipContext;
|
|
2449
|
+
if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
|
|
2450
|
+
const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
|
|
2451
|
+
if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
|
|
2452
|
+
if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
|
|
2453
|
+
}
|
|
2454
|
+
const glyphs = [];
|
|
2455
|
+
if (showDatumGlyph || showSeriesGlyphs) {
|
|
2456
|
+
const size = Number(glyphStyle?.radius ?? DEFAULT_GLYPH_RADIUS);
|
|
2457
|
+
const labelColor = theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
2458
|
+
const fallbackColor = showSeriesGlyphs ? labelColor : theme?.gridStyles?.stroke ?? labelColor;
|
|
2459
|
+
let entries = [];
|
|
2460
|
+
if (showSeriesGlyphs) entries = Object.values(tooltipContext.tooltipData?.datumByKey ?? {});
|
|
2461
|
+
else if (nearestDatum) entries = [nearestDatum];
|
|
2462
|
+
for (const { key, datum, index } of entries) {
|
|
2463
|
+
const { left, top } = getDatumLeftTop(key, datum);
|
|
2464
|
+
if (!isValidNumber(left) || !isValidNumber(top)) continue;
|
|
2465
|
+
glyphs.push(/* @__PURE__ */ jsx("g", {
|
|
2466
|
+
className: "visx-tooltip-glyph",
|
|
2467
|
+
transform: `translate(${left}, ${top})`,
|
|
2468
|
+
children: renderGlyph({
|
|
2469
|
+
key,
|
|
2470
|
+
color: colorScale?.(key) ?? fallbackColor,
|
|
2471
|
+
datum,
|
|
2472
|
+
index,
|
|
2473
|
+
size,
|
|
2474
|
+
x: 0,
|
|
2475
|
+
y: 0,
|
|
2476
|
+
glyphStyle,
|
|
2477
|
+
isNearestDatum: nearestDatum?.key === key
|
|
2478
|
+
})
|
|
2479
|
+
}, key));
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
2483
|
+
const marginTop = margin?.top ?? 0;
|
|
2484
|
+
const marginLeft = margin?.left ?? 0;
|
|
2485
|
+
const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : Tooltip;
|
|
2486
|
+
const boxStyle = {
|
|
2487
|
+
...defaultStyles,
|
|
2488
|
+
zIndex,
|
|
2489
|
+
backgroundColor: theme?.backgroundColor ?? "white",
|
|
2490
|
+
boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
|
|
2491
|
+
...theme?.htmlLabel,
|
|
2492
|
+
...style
|
|
2493
|
+
};
|
|
2494
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
|
|
2495
|
+
className: "visx-tooltip-overlay",
|
|
2496
|
+
pointerEvents: "none",
|
|
2497
|
+
children: [
|
|
2498
|
+
showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ jsx("line", {
|
|
2499
|
+
className: "visx-crosshair visx-crosshair-vertical",
|
|
2500
|
+
stroke: crosshairStroke,
|
|
2501
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2502
|
+
...crosshairPaintProps(verticalCrosshairStyle),
|
|
2503
|
+
x1: tooltipLeft,
|
|
2504
|
+
x2: tooltipLeft,
|
|
2505
|
+
y1: marginTop,
|
|
2506
|
+
y2: marginTop + innerHeight
|
|
2507
|
+
}),
|
|
2508
|
+
showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ jsx("line", {
|
|
2509
|
+
className: "visx-crosshair visx-crosshair-horizontal",
|
|
2510
|
+
stroke: crosshairStroke,
|
|
2511
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2512
|
+
...crosshairPaintProps(horizontalCrosshairStyle),
|
|
2513
|
+
x1: marginLeft,
|
|
2514
|
+
x2: marginLeft + innerWidth,
|
|
2515
|
+
y1: tooltipTop,
|
|
2516
|
+
y2: tooltipTop
|
|
2517
|
+
}),
|
|
2518
|
+
glyphs
|
|
2519
|
+
]
|
|
2520
|
+
}), container && createPortal(/* @__PURE__ */ jsx(TooltipComponent, {
|
|
2521
|
+
left: tooltipLeft,
|
|
2522
|
+
top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
|
|
2523
|
+
style: boxStyle,
|
|
2524
|
+
applyPositionStyle: true,
|
|
2525
|
+
...tooltipProps,
|
|
2526
|
+
...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
|
|
2527
|
+
children: tooltipContent
|
|
2528
|
+
}), container)] });
|
|
2529
|
+
};
|
|
2530
|
+
/**
|
|
2531
|
+
* In-tree replacement for `@visx/xychart`'s `Tooltip`.
|
|
2532
|
+
*
|
|
2533
|
+
* visx renders its tooltip box, glyphs and crosshairs through portals appended
|
|
2534
|
+
* to `document.body`. That puts them in the page's root stacking context, where
|
|
2535
|
+
* they paint above every sticky or fixed element that lives inside a nested
|
|
2536
|
+
* stacking context, and no z-index on that element can change the order. This
|
|
2537
|
+
* component draws the glyphs and crosshairs straight into the chart SVG and
|
|
2538
|
+
* renders the tooltip box into the SVG's parent element, so all of them stack
|
|
2539
|
+
* as ordinary descendants of the chart.
|
|
2540
|
+
*
|
|
2541
|
+
* Render it as a child of `XYChart`. The element wrapping that `XYChart` must
|
|
2542
|
+
* be `position: relative` with the SVG at its origin, and `isolation: isolate`:
|
|
2543
|
+
* the box is placed with the SVG-local coordinates visx reports, and the
|
|
2544
|
+
* isolation keeps the box's `zIndex` from competing with page chrome. The box
|
|
2545
|
+
* flips and clamps to stay inside the nearest ancestor that clips its overflow
|
|
2546
|
+
* (or the viewport), so it may extend past the chart wrapper but is never cut
|
|
2547
|
+
* off unless that ancestor is smaller than the box itself. Below-axis placement
|
|
2548
|
+
* pins it beneath the x-axis and applies horizontal bounds only.
|
|
2549
|
+
*
|
|
2550
|
+
* @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
|
|
2551
|
+
* @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
|
|
2552
|
+
*/
|
|
2553
|
+
const XyChartTooltip = (props) => {
|
|
2554
|
+
const tooltipContext = useContext(TooltipContext);
|
|
2555
|
+
const [container, setContainer] = useState(null);
|
|
2556
|
+
const anchorRef = useCallback((node) => {
|
|
2557
|
+
setContainer(node?.ownerSVGElement?.parentElement ?? null);
|
|
2558
|
+
}, []);
|
|
2559
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("g", { ref: anchorRef }), tooltipContext?.tooltipOpen && /* @__PURE__ */ jsx(XyChartTooltipContent, {
|
|
2560
|
+
...props,
|
|
2561
|
+
tooltipContext,
|
|
2562
|
+
container
|
|
2563
|
+
})] });
|
|
2564
|
+
};
|
|
2565
|
+
//#endregion
|
|
2129
2566
|
//#region src/components/tooltip/accessible-tooltip.tsx
|
|
2130
2567
|
const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
|
|
2131
2568
|
const tooltipContext = useContext(TooltipContext);
|
|
2132
2569
|
const scopeElement = useChartScopeElement();
|
|
2133
|
-
const gridStroke = useGlobalChartsTheme().gridStyles?.stroke;
|
|
2134
2570
|
const crosshairStroke = useMemo(() => {
|
|
2135
|
-
const stroke =
|
|
2571
|
+
const stroke = resolveCssVariable(CATALOG_POINTERS.grid, scopeElement);
|
|
2136
2572
|
return stroke ? { stroke } : void 0;
|
|
2137
|
-
}, [
|
|
2573
|
+
}, [scopeElement]);
|
|
2574
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
2138
2575
|
const tooltipData = useMemo(() => {
|
|
2139
2576
|
if (mode !== "individual") return [];
|
|
2140
2577
|
if (series.length === 0) return [];
|
|
@@ -2195,11 +2632,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2195
2632
|
tabIndex: -1,
|
|
2196
2633
|
role: "tooltip",
|
|
2197
2634
|
"aria-atomic": "true",
|
|
2198
|
-
className: clsx(
|
|
2635
|
+
className: clsx(standaloneScopeClass, keyboardFocusedClassName),
|
|
2199
2636
|
children: tooltipContent
|
|
2200
2637
|
}, `chart-tooltip-${selectedIndex}`);
|
|
2201
2638
|
return /* @__PURE__ */ jsx("div", {
|
|
2202
|
-
className:
|
|
2639
|
+
className: standaloneScopeClass,
|
|
2203
2640
|
role: "tooltip",
|
|
2204
2641
|
"aria-live": "polite",
|
|
2205
2642
|
children: tooltipContent
|
|
@@ -2209,9 +2646,10 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2209
2646
|
renderTooltip,
|
|
2210
2647
|
selectedIndex,
|
|
2211
2648
|
tooltipRef,
|
|
2212
|
-
keyboardFocusedClassName
|
|
2649
|
+
keyboardFocusedClassName,
|
|
2650
|
+
standaloneScopeClass
|
|
2213
2651
|
]);
|
|
2214
|
-
return /* @__PURE__ */ jsx(
|
|
2652
|
+
return /* @__PURE__ */ jsx(XyChartTooltip, {
|
|
2215
2653
|
...props,
|
|
2216
2654
|
verticalCrosshairStyle: {
|
|
2217
2655
|
...crosshairStroke,
|
|
@@ -2224,11 +2662,14 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2224
2662
|
renderTooltip: focusableRenderTooltip
|
|
2225
2663
|
});
|
|
2226
2664
|
};
|
|
2227
|
-
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
|
|
2665
|
+
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
|
|
2228
2666
|
return {
|
|
2229
2667
|
tooltipRef: useCallback((element) => {
|
|
2230
|
-
if (element && selectedIndex !== void 0)
|
|
2231
|
-
|
|
2668
|
+
if (element && selectedIndex !== void 0) {
|
|
2669
|
+
if (preventTooltipScroll) element.focus({ preventScroll: true });
|
|
2670
|
+
else element.focus();
|
|
2671
|
+
}
|
|
2672
|
+
}, [preventTooltipScroll, selectedIndex]),
|
|
2232
2673
|
onChartFocus: useCallback(() => {
|
|
2233
2674
|
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2234
2675
|
}, [
|
|
@@ -2559,72 +3000,90 @@ const getCurveType = (type, smoothing) => {
|
|
|
2559
3000
|
default: return curveLinear;
|
|
2560
3001
|
}
|
|
2561
3002
|
};
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2564
|
-
return (timestamp) => {
|
|
2565
|
-
if (!Number.isFinite(Number(timestamp))) return new Date(timestamp).toLocaleDateString();
|
|
2566
|
-
formatter = formatter ?? new Intl.DateTimeFormat(void 0, options);
|
|
2567
|
-
return formatter.format(timestamp);
|
|
2568
|
-
};
|
|
2569
|
-
};
|
|
2570
|
-
const formatYearTick = dateTimeFormat({ year: "numeric" });
|
|
2571
|
-
const formatDateTick = dateTimeFormat({
|
|
3003
|
+
const YEAR_TICK = { year: "numeric" };
|
|
3004
|
+
const DATE_TICK = {
|
|
2572
3005
|
month: "short",
|
|
2573
3006
|
day: "numeric"
|
|
2574
|
-
}
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
hour12: true
|
|
2578
|
-
});
|
|
2579
|
-
const formatMonthTick = dateTimeFormat({ month: "short" });
|
|
3007
|
+
};
|
|
3008
|
+
const HOUR_TICK = { hour: "numeric" };
|
|
3009
|
+
const MONTH_TICK = { month: "short" };
|
|
2580
3010
|
const boundaryFormat = (isAnchor, atBoundary, between) => {
|
|
2581
3011
|
const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
|
|
2582
3012
|
format.isAnchor = isAnchor;
|
|
2583
3013
|
return format;
|
|
2584
3014
|
};
|
|
2585
|
-
const
|
|
2586
|
-
const
|
|
3015
|
+
const tickFormats = (formatting) => {
|
|
3016
|
+
const clock = createZonedClock(formatting.timeZone);
|
|
3017
|
+
const year = createDateFormatter(YEAR_TICK, formatting);
|
|
3018
|
+
const date = createDateFormatter(DATE_TICK, formatting);
|
|
3019
|
+
const hour = createDateFormatter(HOUR_TICK, formatting);
|
|
3020
|
+
const month = createDateFormatter(MONTH_TICK, formatting);
|
|
3021
|
+
const datedTick = (timestamp) => date(timestamp);
|
|
3022
|
+
datedTick.anchorsAtLabelChange = true;
|
|
3023
|
+
return {
|
|
3024
|
+
year,
|
|
3025
|
+
date: datedTick,
|
|
3026
|
+
dateOrHour: boundaryFormat((value) => clock(value).hour === 0, date, hour),
|
|
3027
|
+
hour,
|
|
3028
|
+
monthOrYear: boundaryFormat((value) => clock(value).month === 1, year, month)
|
|
3029
|
+
};
|
|
3030
|
+
};
|
|
2587
3031
|
const getSpan = (sortedData) => {
|
|
2588
|
-
const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => first
|
|
3032
|
+
const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => Number.isFinite(Number(first)) && Number.isFinite(Number(last)));
|
|
2589
3033
|
if (!bounds.length) return null;
|
|
2590
3034
|
return {
|
|
2591
3035
|
minX: Math.min(...bounds.map(([first]) => Number(first))),
|
|
2592
3036
|
maxX: Math.max(...bounds.map(([, last]) => Number(last)))
|
|
2593
3037
|
};
|
|
2594
3038
|
};
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
const
|
|
2605
|
-
|
|
2606
|
-
const spacingInHours = getPointSpacingInHours(sortedData);
|
|
2607
|
-
if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
|
|
2608
|
-
if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
|
|
2609
|
-
return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
|
|
2610
|
-
};
|
|
2611
|
-
const getFormatter = (sortedData, tickResolution) => {
|
|
3039
|
+
/**
|
|
3040
|
+
* The most informative tick format for a bucket resolution and the span on screen.
|
|
3041
|
+
*
|
|
3042
|
+
* @param sortedData - Series as returned by `useChartDataTransform`.
|
|
3043
|
+
* @param tickResolution - Caller-declared bucket resolution, when known.
|
|
3044
|
+
* @param formatting - Host locale and time zone.
|
|
3045
|
+
* @param domain - The effective scale domain, or undefined for the data's own extent.
|
|
3046
|
+
* @return The formatter the axis labels its ticks with.
|
|
3047
|
+
*/
|
|
3048
|
+
const getFormatter = (sortedData, tickResolution, formatting = {}, domain) => {
|
|
3049
|
+
const format = tickFormats(formatting);
|
|
2612
3050
|
const resolution = getBucketResolution(sortedData, tickResolution);
|
|
2613
|
-
if (resolution === "year") return
|
|
2614
|
-
if (resolution === "month") return
|
|
2615
|
-
const span =
|
|
2616
|
-
|
|
3051
|
+
if (resolution === "year") return format.year;
|
|
3052
|
+
if (resolution === "month") return format.monthOrYear;
|
|
3053
|
+
const span = domain ? {
|
|
3054
|
+
minX: domain[0].getTime(),
|
|
3055
|
+
maxX: domain[1].getTime()
|
|
3056
|
+
} : getSpan(sortedData);
|
|
3057
|
+
if (!span) return format.date;
|
|
2617
3058
|
const diffInHours = Math.abs(differenceInHours(span.maxX, span.minX));
|
|
2618
3059
|
if (resolution === "hour") {
|
|
2619
|
-
if (diffInHours
|
|
2620
|
-
if (diffInHours <= 168) return
|
|
3060
|
+
if (diffInHours < 24) return format.hour;
|
|
3061
|
+
if (diffInHours <= 168) return format.dateOrHour;
|
|
2621
3062
|
}
|
|
2622
|
-
return Math.abs(differenceInYears(span.maxX, span.minX)) <= 1 ?
|
|
3063
|
+
return Math.abs(differenceInYears(span.maxX, span.minX)) <= 1 ? format.date : format.year;
|
|
3064
|
+
};
|
|
3065
|
+
/**
|
|
3066
|
+
* The instants a set of series spans, as a scale domain.
|
|
3067
|
+
*
|
|
3068
|
+
* @param sortedData - Series as returned by `useChartDataTransform`.
|
|
3069
|
+
* @return Earliest and latest dated point, or undefined when nothing is dated.
|
|
3070
|
+
*/
|
|
3071
|
+
const getSeriesExtent = (sortedData) => {
|
|
3072
|
+
const span = getSpan(sortedData);
|
|
3073
|
+
return span ? [new Date(span.minX), new Date(span.maxX)] : void 0;
|
|
3074
|
+
};
|
|
3075
|
+
const getAnchorIndices = (domain, tickFormatter, labels) => {
|
|
3076
|
+
if (tickFormatter.anchorsAtLabelChange) return labels.reduce((indices, label, index) => {
|
|
3077
|
+
if (index === 0 || label !== labels[index - 1]) indices.push(index);
|
|
3078
|
+
return indices;
|
|
3079
|
+
}, []);
|
|
3080
|
+
const { isAnchor } = tickFormatter;
|
|
3081
|
+
if (!isAnchor) return [];
|
|
3082
|
+
return domain.reduce((indices, date, index) => {
|
|
3083
|
+
if (isAnchor(date)) indices.push(index);
|
|
3084
|
+
return indices;
|
|
3085
|
+
}, []);
|
|
2623
3086
|
};
|
|
2624
|
-
const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
|
|
2625
|
-
if (isAnchor(date)) indices.push(index);
|
|
2626
|
-
return indices;
|
|
2627
|
-
}, []);
|
|
2628
3087
|
const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
|
|
2629
3088
|
const steps = /* @__PURE__ */ new Set();
|
|
2630
3089
|
for (let count = maxTicks; count > 1; count--) {
|
|
@@ -2639,26 +3098,25 @@ const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
|
|
|
2639
3098
|
return steps;
|
|
2640
3099
|
};
|
|
2641
3100
|
/**
|
|
2642
|
-
* Tick values for
|
|
3101
|
+
* Tick values sampled by index, for the bar chart's band axis and for evenly
|
|
3102
|
+
* spaced points on a continuous one.
|
|
2643
3103
|
*
|
|
2644
|
-
* visx samples
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2647
|
-
*
|
|
2648
|
-
*
|
|
2649
|
-
*
|
|
2650
|
-
* identical labels side by side.
|
|
3104
|
+
* visx samples by index from offset zero, blind to which labels carry a boundary
|
|
3105
|
+
* and without collapsing repeats — so the tick that prints the year or the date
|
|
3106
|
+
* often isn't sampled at all, and a long series can show the same label twice.
|
|
3107
|
+
* Choose the values instead: sweep the evenly spaced candidates, including those
|
|
3108
|
+
* that step from anchor to anchor, and keep the one that reaches the most anchors
|
|
3109
|
+
* without thinning the axis or putting two identical labels side by side.
|
|
2651
3110
|
*
|
|
2652
|
-
* @param domain -
|
|
3111
|
+
* @param domain - Buckets in axis order.
|
|
2653
3112
|
* @param tickFormatter - Formatter the axis will render these values with.
|
|
2654
3113
|
* @param maxTicks - Most ticks the axis should carry.
|
|
2655
3114
|
* @return Values to hand the axis as `tickValues`.
|
|
2656
3115
|
*/
|
|
2657
3116
|
const getBandTickValues = (domain, tickFormatter, maxTicks) => {
|
|
2658
3117
|
if (!domain.length) return [];
|
|
2659
|
-
const { isAnchor } = tickFormatter;
|
|
2660
|
-
const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
|
|
2661
3118
|
const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
|
|
3119
|
+
const anchorIndices = getAnchorIndices(domain, tickFormatter, domainLabels);
|
|
2662
3120
|
const candidates = [];
|
|
2663
3121
|
const consider = (indices) => {
|
|
2664
3122
|
if (!indices.length || indices.length > maxTicks) return;
|
|
@@ -2670,15 +3128,108 @@ const getBandTickValues = (domain, tickFormatter, maxTicks) => {
|
|
|
2670
3128
|
for (let index = offset; index < domain.length; index += step) indices.push(index);
|
|
2671
3129
|
consider(indices);
|
|
2672
3130
|
}
|
|
2673
|
-
|
|
3131
|
+
const minStride = Math.max(1, Math.ceil(anchorIndices.length / maxTicks));
|
|
3132
|
+
for (let stride = minStride; stride <= anchorIndices.length; stride++) {
|
|
3133
|
+
const stepped = [];
|
|
3134
|
+
for (let position = 0; position < anchorIndices.length; position += stride) stepped.push(anchorIndices[position]);
|
|
3135
|
+
consider(stepped);
|
|
3136
|
+
}
|
|
2674
3137
|
if (!candidates.length) return [domain[0]];
|
|
2675
3138
|
const densest = candidates.reduce((most, indices) => Math.max(most, indices.length), 0);
|
|
2676
|
-
const
|
|
3139
|
+
const anchorIndexSet = new Set(anchorIndices);
|
|
3140
|
+
const anchorsIn = (indices) => indices.filter((index) => anchorIndexSet.has(index)).length;
|
|
2677
3141
|
return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
|
|
2678
3142
|
const gain = anchorsIn(indices) - anchorsIn(chosen);
|
|
2679
3143
|
return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
|
|
2680
3144
|
}).map((index) => domain[index]);
|
|
2681
3145
|
};
|
|
3146
|
+
/**
|
|
3147
|
+
* The most x-axis ticks a chart of this width has room for.
|
|
3148
|
+
*
|
|
3149
|
+
* @param chartWidth - Chart width in pixels.
|
|
3150
|
+
* @return A tick budget of at least one.
|
|
3151
|
+
*/
|
|
3152
|
+
const getMaxTicksForWidth = (chartWidth) => Math.max(1, Math.floor(chartWidth / X_TICK_WIDTH));
|
|
3153
|
+
const MIN_GAP_IN_LABELS = .9;
|
|
3154
|
+
const nearestValue = (sorted, target) => {
|
|
3155
|
+
let low = 0;
|
|
3156
|
+
let high = sorted.length - 1;
|
|
3157
|
+
while (low < high) {
|
|
3158
|
+
const middle = Math.floor((low + high) / 2);
|
|
3159
|
+
if (sorted[middle] < target) low = middle + 1;
|
|
3160
|
+
else high = middle;
|
|
3161
|
+
}
|
|
3162
|
+
const above = sorted[low];
|
|
3163
|
+
const below = low > 0 ? sorted[low - 1] : above;
|
|
3164
|
+
return Math.abs(above - target) <= Math.abs(below - target) ? above : below;
|
|
3165
|
+
};
|
|
3166
|
+
const getPositionTickValues = (points, tickFormatter, maxTicks, axis) => {
|
|
3167
|
+
const times = points.map((point) => point.getTime());
|
|
3168
|
+
const { first, last } = axis;
|
|
3169
|
+
if (maxTicks <= 1 || times.length === 1 || first === last) return [points[0]];
|
|
3170
|
+
const step = (last - first) / (maxTicks - 1);
|
|
3171
|
+
const labels = points.map((point) => tickFormatter(point.getTime()));
|
|
3172
|
+
const minGap = (last - first) / maxTicks * MIN_GAP_IN_LABELS;
|
|
3173
|
+
const anchors = getAnchorIndices(points, tickFormatter, labels).map((index) => times[index]);
|
|
3174
|
+
const chosen = [];
|
|
3175
|
+
let lastLabel = null;
|
|
3176
|
+
for (let index = 0; index < maxTicks; index++) {
|
|
3177
|
+
const target = first + index * step;
|
|
3178
|
+
const anchor = anchors.length ? nearestValue(anchors, target) : null;
|
|
3179
|
+
let tick = anchor !== null && Math.abs(anchor - target) <= step / 2 ? anchor : null;
|
|
3180
|
+
if (tick === null) {
|
|
3181
|
+
const point = nearestValue(times, target);
|
|
3182
|
+
tick = Math.abs(point - target) <= step / 2 ? point : target;
|
|
3183
|
+
}
|
|
3184
|
+
const previous = chosen[chosen.length - 1];
|
|
3185
|
+
if (previous !== void 0 && (tick <= previous || tick - previous < minGap)) continue;
|
|
3186
|
+
const label = tickFormatter(tick);
|
|
3187
|
+
if (label === lastLabel) continue;
|
|
3188
|
+
chosen.push(tick);
|
|
3189
|
+
lastLabel = label;
|
|
3190
|
+
}
|
|
3191
|
+
return chosen.map((timestamp) => new Date(timestamp));
|
|
3192
|
+
};
|
|
3193
|
+
const isIndexSamplingUnusable = (ticks, points, maxTicks, axis) => {
|
|
3194
|
+
if (ticks.length < 2 || points.length < 2) return false;
|
|
3195
|
+
const span = axis.last - axis.first;
|
|
3196
|
+
if (!span) return false;
|
|
3197
|
+
return ticks.slice(1).reduce((nearest, tick, index) => Math.min(nearest, tick.getTime() - ticks[index].getTime()), Infinity) < span / maxTicks * MIN_GAP_IN_LABELS || ticks.length < Math.min(maxTicks, points.length) / 3;
|
|
3198
|
+
};
|
|
3199
|
+
/**
|
|
3200
|
+
* Tick values for a continuous time axis, chosen from the points it draws.
|
|
3201
|
+
*
|
|
3202
|
+
* Sampled by index while the points are evenly spaced, which keeps the axis on
|
|
3203
|
+
* the calendar boundaries `getBandTickValues` steers towards, and by position
|
|
3204
|
+
* once they are not, where an index stride crowds one end of the scale.
|
|
3205
|
+
*
|
|
3206
|
+
* Values are taken from the data wherever there is one to take, so a local time
|
|
3207
|
+
* that does not exist cannot become a tick and DST needs no special case. Only a
|
|
3208
|
+
* target stranded in a gap is constructed, as a plain instant.
|
|
3209
|
+
*
|
|
3210
|
+
* @param sortedData - Series as returned by `useChartDataTransform`.
|
|
3211
|
+
* @param domain - The effective scale domain, or undefined for all data.
|
|
3212
|
+
* @param tickFormatter - The formatter the ticks will be labeled with.
|
|
3213
|
+
* @param maxTicks - The most ticks the axis has room for.
|
|
3214
|
+
* @return Tick dates, or null when no series carries a usable date.
|
|
3215
|
+
*/
|
|
3216
|
+
const getTimeAxisTickValues = (sortedData, domain, tickFormatter, maxTicks) => {
|
|
3217
|
+
const byTimestamp = /* @__PURE__ */ new Map();
|
|
3218
|
+
for (const series of sortedData) for (const point of series.data) {
|
|
3219
|
+
const date = point.date;
|
|
3220
|
+
if (date instanceof Date && Number.isFinite(date.getTime())) byTimestamp.set(date.getTime(), date);
|
|
3221
|
+
}
|
|
3222
|
+
if (!byTimestamp.size) return null;
|
|
3223
|
+
const min = domain?.[0]?.getTime();
|
|
3224
|
+
const max = domain?.[1]?.getTime();
|
|
3225
|
+
const visible = [...byTimestamp.keys()].sort((a, b) => a - b).filter((timestamp) => (min === void 0 || timestamp >= min) && (max === void 0 || timestamp <= max)).map((timestamp) => byTimestamp.get(timestamp));
|
|
3226
|
+
const axis = {
|
|
3227
|
+
first: min ?? visible[0].getTime(),
|
|
3228
|
+
last: max ?? visible[visible.length - 1].getTime()
|
|
3229
|
+
};
|
|
3230
|
+
const ticks = getBandTickValues(visible, tickFormatter, maxTicks);
|
|
3231
|
+
return isIndexSamplingUnusable(ticks, visible, maxTicks, axis) ? getPositionTickValues(visible, tickFormatter, maxTicks, axis) : ticks;
|
|
3232
|
+
};
|
|
2682
3233
|
const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
|
|
2683
3234
|
const span = getSpan(data);
|
|
2684
3235
|
if (!span) return 1;
|
|
@@ -2693,7 +3244,43 @@ const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
|
|
|
2693
3244
|
if (ticks.some((tick, idx) => idx > 0 && tick === ticks[idx - 1])) continue;
|
|
2694
3245
|
return ticks.length;
|
|
2695
3246
|
}
|
|
2696
|
-
return secondBestGuess;
|
|
3247
|
+
return Math.max(1, secondBestGuess);
|
|
3248
|
+
};
|
|
3249
|
+
//#endregion
|
|
3250
|
+
//#region src/charts/private/time-axis-options.ts
|
|
3251
|
+
const toDateDomain = (domain) => domain ? [new Date(domain[0]), new Date(domain[1])] : void 0;
|
|
3252
|
+
/**
|
|
3253
|
+
* The x-axis options for a continuous time chart, shared by LineChart and AreaChart.
|
|
3254
|
+
*
|
|
3255
|
+
* Kept in one place so the tick-selection logic isn't duplicated between them.
|
|
3256
|
+
*
|
|
3257
|
+
* @param args - Named arguments.
|
|
3258
|
+
* @param args.dataSorted - Series as returned by `useChartDataTransform`.
|
|
3259
|
+
* @param args.width - Chart width in pixels, which bounds how many ticks fit.
|
|
3260
|
+
* @param args.axisOptions - The caller's `options.axis.x`.
|
|
3261
|
+
* @param args.scaleDomain - The caller's `options.xScale.domain`.
|
|
3262
|
+
* @param args.zoomDomain - The chart's current zoom window, when zoomed.
|
|
3263
|
+
* @param args.formatting - Host locale and time zone.
|
|
3264
|
+
* @param args.isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it. Defaults to all of them.
|
|
3265
|
+
* @return Options ready to spread into the chart's `axis.x`; `orientation` is always set.
|
|
3266
|
+
*/
|
|
3267
|
+
const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoomDomain, formatting, isSeriesRendered }) => {
|
|
3268
|
+
const { tickResolution, tickFormat, tickValues: callerTickValues, numTicks: callerNumTicks, orientation: callerOrientation, display: callerDisplay, ...rest } = axisOptions ?? {};
|
|
3269
|
+
const rendered = isSeriesRendered ? dataSorted.filter(isSeriesRendered) : dataSorted;
|
|
3270
|
+
const effectiveDomain = zoomDomain ?? toDateDomain(scaleDomain) ?? getSeriesExtent(rendered);
|
|
3271
|
+
const ownFormatter = getFormatter(rendered, tickResolution, formatting, effectiveDomain);
|
|
3272
|
+
const formatter = tickFormat || ownFormatter;
|
|
3273
|
+
const ownTickValues = tickFormat || callerTickValues ? null : getTimeAxisTickValues(rendered, effectiveDomain, ownFormatter, callerNumTicks ?? getMaxTicksForWidth(width));
|
|
3274
|
+
const tickValues = callerTickValues ?? (ownTickValues?.length ? ownTickValues : void 0);
|
|
3275
|
+
const numTicks = callerNumTicks ?? (tickValues ? void 0 : guessOptimalNumTicks(rendered, width, formatter));
|
|
3276
|
+
return {
|
|
3277
|
+
orientation: callerOrientation ?? "bottom",
|
|
3278
|
+
tickFormat: formatter,
|
|
3279
|
+
display: callerDisplay ?? true,
|
|
3280
|
+
...rest,
|
|
3281
|
+
...tickValues ? { tickValues } : {},
|
|
3282
|
+
...numTicks === void 0 ? {} : { numTicks }
|
|
3283
|
+
};
|
|
2697
3284
|
};
|
|
2698
3285
|
//#endregion
|
|
2699
3286
|
//#region src/charts/private/with-responsive/with-responsive.module.scss
|
|
@@ -2974,6 +3561,9 @@ function ZoomResetButton({ onClick }) {
|
|
|
2974
3561
|
});
|
|
2975
3562
|
}
|
|
2976
3563
|
//#endregion
|
|
3564
|
+
//#region src/charts/private/xy-plot/xy-plot.module.scss
|
|
3565
|
+
var xy_plot_module_default = { "xy-plot": "a8ccharts-bIyhgG-xy-plot" };
|
|
3566
|
+
//#endregion
|
|
2977
3567
|
//#region src/charts/line-chart/line-chart.module.scss
|
|
2978
3568
|
var line_chart_module_default = {
|
|
2979
3569
|
"line-chart": "a8ccharts-inuQka-line-chart",
|
|
@@ -3214,12 +3804,14 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
|
|
|
3214
3804
|
};
|
|
3215
3805
|
const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
|
|
3216
3806
|
const providerTheme = useGlobalChartsTheme();
|
|
3217
|
-
const scopeElement = useChartScopeElement();
|
|
3218
3807
|
const { xScale, yScale } = useContext(DataContext) || {};
|
|
3219
3808
|
const labelRef = useRef(null);
|
|
3220
3809
|
const [height, setHeight] = useState(null);
|
|
3221
|
-
const styles =
|
|
3222
|
-
|
|
3810
|
+
const styles = useMemo(() => deepmerge.all([
|
|
3811
|
+
ANNOTATION_POINTERS,
|
|
3812
|
+
providerTheme.annotationStyles ?? {},
|
|
3813
|
+
datumStyles ?? {}
|
|
3814
|
+
]), [providerTheme.annotationStyles, datumStyles]);
|
|
3223
3815
|
useEffect(() => {
|
|
3224
3816
|
if (labelRef.current?.getBBox) {
|
|
3225
3817
|
const bbox = labelRef.current.getBBox();
|
|
@@ -3305,15 +3897,8 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
|
|
|
3305
3897
|
dx,
|
|
3306
3898
|
dy,
|
|
3307
3899
|
children: [
|
|
3308
|
-
/* @__PURE__ */ jsx(Connector, {
|
|
3309
|
-
|
|
3310
|
-
stroke: resolveColor(styles?.connector?.stroke)
|
|
3311
|
-
}),
|
|
3312
|
-
subjectType === "circle" && /* @__PURE__ */ jsx(CircleSubject, {
|
|
3313
|
-
...styles?.circleSubject,
|
|
3314
|
-
fill: resolveColor(styles?.circleSubject?.fill),
|
|
3315
|
-
stroke: resolveColor(styles?.circleSubject?.stroke)
|
|
3316
|
-
}),
|
|
3900
|
+
/* @__PURE__ */ jsx(Connector, { ...styles?.connector }),
|
|
3901
|
+
subjectType === "circle" && /* @__PURE__ */ jsx(CircleSubject, { ...styles?.circleSubject }),
|
|
3317
3902
|
subjectType === "line-vertical" && /* @__PURE__ */ jsx(LineSubject, {
|
|
3318
3903
|
min: yMax,
|
|
3319
3904
|
max: yMin,
|
|
@@ -3347,8 +3932,6 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
|
|
|
3347
3932
|
title,
|
|
3348
3933
|
subtitle,
|
|
3349
3934
|
...styles?.label,
|
|
3350
|
-
anchorLineStroke: resolveColor(styles?.label?.anchorLineStroke),
|
|
3351
|
-
backgroundFill: resolveColor(styles?.label?.backgroundFill),
|
|
3352
3935
|
...labelPosition,
|
|
3353
3936
|
horizontalAnchor: getHorizontalAnchor(subjectType, isFlippedHorizontally),
|
|
3354
3937
|
verticalAnchor: getVerticalAnchor(subjectType, isFlippedVertically, y, yMax, height ?? ANNOTATION_INIT_HEIGHT)
|
|
@@ -3396,16 +3979,34 @@ const toNumber = (val) => {
|
|
|
3396
3979
|
const num = typeof val === "number" ? val : parseFloat(val);
|
|
3397
3980
|
return isNaN(num) ? void 0 : num;
|
|
3398
3981
|
};
|
|
3982
|
+
const TOOLTIP_DATE = {};
|
|
3983
|
+
const TOOLTIP_FORMAT_BY_RESOLUTION$1 = {
|
|
3984
|
+
hour: {
|
|
3985
|
+
year: "numeric",
|
|
3986
|
+
month: "numeric",
|
|
3987
|
+
day: "numeric",
|
|
3988
|
+
hour: "numeric"
|
|
3989
|
+
},
|
|
3990
|
+
day: TOOLTIP_DATE,
|
|
3991
|
+
month: TOOLTIP_DATE,
|
|
3992
|
+
year: TOOLTIP_DATE
|
|
3993
|
+
};
|
|
3994
|
+
const TooltipDate = ({ date, displayResolution }) => {
|
|
3995
|
+
const formatting = useChartFormatting();
|
|
3996
|
+
const format = useMemo(() => createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION$1[displayResolution], formatting), [displayResolution, formatting]);
|
|
3997
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: date ? format(date) : null });
|
|
3998
|
+
};
|
|
3399
3999
|
/**
|
|
3400
4000
|
* Default visx-tooltip render that prints the hovered date as a heading and
|
|
3401
4001
|
* one row per visible series (label + formatted value), sorted descending by
|
|
3402
4002
|
* value. Reused by AreaChart, which has the same multi-series shape.
|
|
3403
4003
|
*
|
|
3404
|
-
* @param params
|
|
4004
|
+
* @param params - visx tooltip data and the chart's optional `bucketInfo`.
|
|
4005
|
+
* @param contentStyle - Explicit tooltip content color overrides.
|
|
3405
4006
|
* @return Tooltip JSX, or `null` when no datum is hovered.
|
|
3406
4007
|
*/
|
|
3407
|
-
const renderDefaultTooltip = (params) => {
|
|
3408
|
-
const { tooltipData } = params;
|
|
4008
|
+
const renderDefaultTooltip = (params, contentStyle) => {
|
|
4009
|
+
const { tooltipData, bucketInfo } = params;
|
|
3409
4010
|
const nearestDatum = tooltipData?.nearestDatum?.datum;
|
|
3410
4011
|
if (!nearestDatum) return null;
|
|
3411
4012
|
const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
|
|
@@ -3414,9 +4015,13 @@ const renderDefaultTooltip = (params) => {
|
|
|
3414
4015
|
})).sort((a, b) => b.value - a.value);
|
|
3415
4016
|
return /* @__PURE__ */ jsxs("div", {
|
|
3416
4017
|
className: line_chart_module_default["line-chart__tooltip"],
|
|
4018
|
+
style: contentStyle,
|
|
3417
4019
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3418
4020
|
className: line_chart_module_default["line-chart__tooltip-date"],
|
|
3419
|
-
children:
|
|
4021
|
+
children: /* @__PURE__ */ jsx(TooltipDate, {
|
|
4022
|
+
date: nearestDatum.date,
|
|
4023
|
+
displayResolution: bucketInfo?.displayResolution ?? "day"
|
|
4024
|
+
})
|
|
3420
4025
|
}), tooltipPoints.map((point) => /* @__PURE__ */ jsxs(Stack, {
|
|
3421
4026
|
direction: "row",
|
|
3422
4027
|
align: "center",
|
|
@@ -3460,14 +4065,14 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
|
|
|
3460
4065
|
]);
|
|
3461
4066
|
return null;
|
|
3462
4067
|
};
|
|
3463
|
-
const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
|
|
4068
|
+
const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
|
|
3464
4069
|
const legendInteractive = legend.interactive ?? false;
|
|
3465
4070
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
3466
4071
|
const legendShape = legend.shape ?? "line";
|
|
3467
4072
|
const legendPosition = legend.position ?? "bottom";
|
|
3468
|
-
const
|
|
4073
|
+
const formatting = useChartFormatting();
|
|
3469
4074
|
const theme = useXYChartTheme(data);
|
|
3470
|
-
const resolvedBackgroundColor = theme.backgroundColor
|
|
4075
|
+
const resolvedBackgroundColor = theme.backgroundColor;
|
|
3471
4076
|
const chartId = useChartId(providedChartId);
|
|
3472
4077
|
const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
|
|
3473
4078
|
const isSeriesVisible = useCallback((seriesLabel) => !hiddenSeries.has(seriesLabel), [hiddenSeries]);
|
|
@@ -3538,20 +4143,21 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3538
4143
|
setIsNavigating,
|
|
3539
4144
|
chartRef,
|
|
3540
4145
|
totalPoints: dataSorted[0]?.data.length || 0,
|
|
3541
|
-
onActivate: activateSelectedPoint
|
|
4146
|
+
onActivate: activateSelectedPoint,
|
|
4147
|
+
preventTooltipScroll: tooltipPlacement === "below-axis"
|
|
3542
4148
|
});
|
|
3543
4149
|
const chartOptions = useMemo(() => {
|
|
3544
|
-
const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
|
|
3545
|
-
const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
|
|
3546
4150
|
return {
|
|
3547
4151
|
axis: {
|
|
3548
|
-
x: {
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
4152
|
+
x: buildTimeAxisOptions({
|
|
4153
|
+
dataSorted,
|
|
4154
|
+
width,
|
|
4155
|
+
axisOptions: options?.axis?.x,
|
|
4156
|
+
scaleDomain: options?.xScale?.domain,
|
|
4157
|
+
zoomDomain: zoom.domain,
|
|
4158
|
+
formatting,
|
|
4159
|
+
isSeriesRendered: (series) => isSeriesVisible(series.label)
|
|
4160
|
+
}),
|
|
3555
4161
|
y: {
|
|
3556
4162
|
orientation: "left",
|
|
3557
4163
|
numTicks: 4,
|
|
@@ -3578,7 +4184,14 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3578
4184
|
dataSorted,
|
|
3579
4185
|
width,
|
|
3580
4186
|
zoom.domain,
|
|
3581
|
-
stableYDomain
|
|
4187
|
+
stableYDomain,
|
|
4188
|
+
formatting,
|
|
4189
|
+
isSeriesVisible
|
|
4190
|
+
]);
|
|
4191
|
+
const bucketInfo = useMemo(() => getBucketInfo(dataSorted.filter((series) => isSeriesVisible(series.label)), options?.axis?.x?.tickResolution), [
|
|
4192
|
+
dataSorted,
|
|
4193
|
+
isSeriesVisible,
|
|
4194
|
+
options?.axis?.x?.tickResolution
|
|
3582
4195
|
]);
|
|
3583
4196
|
const tooltipRenderGlyph = useMemo(() => {
|
|
3584
4197
|
return (props) => {
|
|
@@ -3640,6 +4253,29 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3640
4253
|
xAccessor: (d) => d?.date,
|
|
3641
4254
|
yAccessor: (d) => d?.value
|
|
3642
4255
|
};
|
|
4256
|
+
const resolvedTooltipStyle = useMemo(() => {
|
|
4257
|
+
if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
|
|
4258
|
+
if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
|
|
4259
|
+
return {
|
|
4260
|
+
backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
|
|
4261
|
+
...tooltipStyle
|
|
4262
|
+
};
|
|
4263
|
+
}, [renderTooltip, tooltipStyle]);
|
|
4264
|
+
const tooltipRenderer = useMemo(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
|
|
4265
|
+
...params,
|
|
4266
|
+
bucketInfo
|
|
4267
|
+
}, {
|
|
4268
|
+
color: tooltipStyle?.color,
|
|
4269
|
+
background: tooltipStyle?.background,
|
|
4270
|
+
backgroundColor: tooltipStyle?.backgroundColor
|
|
4271
|
+
}) : renderTooltip({
|
|
4272
|
+
...params,
|
|
4273
|
+
bucketInfo
|
|
4274
|
+
}), [
|
|
4275
|
+
renderTooltip,
|
|
4276
|
+
bucketInfo,
|
|
4277
|
+
tooltipStyle
|
|
4278
|
+
]);
|
|
3643
4279
|
if (error) return /* @__PURE__ */ jsx("div", {
|
|
3644
4280
|
className: clsx("line-chart", line_chart_module_default["line-chart"]),
|
|
3645
4281
|
children: error
|
|
@@ -3688,7 +4324,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3688
4324
|
onBlur: onChartBlur,
|
|
3689
4325
|
children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
|
|
3690
4326
|
ref: chartRef,
|
|
3691
|
-
|
|
4327
|
+
className: xy_plot_module_default["xy-plot"],
|
|
3692
4328
|
children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
|
|
3693
4329
|
theme,
|
|
3694
4330
|
width,
|
|
@@ -3778,13 +4414,17 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3778
4414
|
withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
|
|
3779
4415
|
detectBounds: true,
|
|
3780
4416
|
snapTooltipToDatumX: true,
|
|
4417
|
+
tooltipPlacement,
|
|
4418
|
+
style: resolvedTooltipStyle,
|
|
3781
4419
|
snapTooltipToDatumY: true,
|
|
3782
4420
|
showSeriesGlyphs: true,
|
|
3783
|
-
renderTooltip,
|
|
4421
|
+
renderTooltip: tooltipRenderer,
|
|
3784
4422
|
renderGlyph: tooltipRenderGlyph,
|
|
3785
4423
|
glyphStyle,
|
|
3786
4424
|
showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
|
|
3787
4425
|
showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
|
|
4426
|
+
verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
|
|
4427
|
+
horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
|
|
3788
4428
|
selectedIndex,
|
|
3789
4429
|
tooltipRef,
|
|
3790
4430
|
keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
|
|
@@ -3918,12 +4558,11 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
|
|
|
3918
4558
|
};
|
|
3919
4559
|
//#endregion
|
|
3920
4560
|
//#region src/charts/area-chart/area-chart.tsx
|
|
3921
|
-
const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange
|
|
4561
|
+
const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
|
|
3922
4562
|
const legendInteractive = legend.interactive ?? false;
|
|
3923
4563
|
const legendShape = legend.shape ?? "rect";
|
|
3924
4564
|
const legendPosition = legend.position ?? "bottom";
|
|
3925
|
-
const
|
|
3926
|
-
const providerTheme = useGlobalChartsTheme();
|
|
4565
|
+
const formatting = useChartFormatting();
|
|
3927
4566
|
const theme = useXYChartTheme(data);
|
|
3928
4567
|
const chartId = useChartId(providedChartId);
|
|
3929
4568
|
const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
|
|
@@ -3973,7 +4612,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
3973
4612
|
totalPoints: dataSorted[0]?.data.length || 0
|
|
3974
4613
|
});
|
|
3975
4614
|
const fixedYDomain = useMemo(() => {
|
|
3976
|
-
if (
|
|
4615
|
+
if (rescaleYOnVisibilityChange || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
|
|
3977
4616
|
if (stacked) {
|
|
3978
4617
|
const numPoints = Math.max(...dataSorted.map((s) => s.data.length));
|
|
3979
4618
|
let posMax = 0;
|
|
@@ -4007,20 +4646,19 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4007
4646
|
dataSorted,
|
|
4008
4647
|
stacked,
|
|
4009
4648
|
stackOffset,
|
|
4010
|
-
|
|
4649
|
+
rescaleYOnVisibilityChange
|
|
4011
4650
|
]);
|
|
4012
4651
|
const chartOptions = useMemo(() => {
|
|
4013
|
-
const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
|
|
4014
|
-
const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
|
|
4015
4652
|
return {
|
|
4016
4653
|
axis: {
|
|
4017
|
-
x: {
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4654
|
+
x: buildTimeAxisOptions({
|
|
4655
|
+
dataSorted,
|
|
4656
|
+
width,
|
|
4657
|
+
axisOptions: options?.axis?.x,
|
|
4658
|
+
scaleDomain: options?.xScale?.domain,
|
|
4659
|
+
zoomDomain: zoom.domain,
|
|
4660
|
+
formatting
|
|
4661
|
+
}),
|
|
4024
4662
|
y: {
|
|
4025
4663
|
orientation: "left",
|
|
4026
4664
|
numTicks: 4,
|
|
@@ -4048,7 +4686,8 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4048
4686
|
width,
|
|
4049
4687
|
stacked,
|
|
4050
4688
|
fixedYDomain,
|
|
4051
|
-
zoom.domain
|
|
4689
|
+
zoom.domain,
|
|
4690
|
+
formatting
|
|
4052
4691
|
]);
|
|
4053
4692
|
const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme);
|
|
4054
4693
|
const error = validateData$3(dataSorted);
|
|
@@ -4084,9 +4723,17 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4084
4723
|
};
|
|
4085
4724
|
const zeroYAccessor = useCallback(() => 0, []);
|
|
4086
4725
|
const visibleLabels = useMemo(() => new Set(seriesWithVisibility.filter((s) => s.isVisible).map((s) => s.series.label)), [seriesWithVisibility]);
|
|
4726
|
+
const bucketInfo = useMemo(() => getBucketInfo(dataSorted.filter((series) => visibleLabels.has(series.label)), options?.axis?.x?.tickResolution), [
|
|
4727
|
+
dataSorted,
|
|
4728
|
+
visibleLabels,
|
|
4729
|
+
options?.axis?.x?.tickResolution
|
|
4730
|
+
]);
|
|
4087
4731
|
const filteredRenderTooltip = useCallback((params) => {
|
|
4088
4732
|
const datumByKey = params?.tooltipData?.datumByKey;
|
|
4089
|
-
if (!datumByKey) return renderTooltip(
|
|
4733
|
+
if (!datumByKey) return renderTooltip({
|
|
4734
|
+
...params,
|
|
4735
|
+
bucketInfo
|
|
4736
|
+
});
|
|
4090
4737
|
const filtered = Object.fromEntries(Object.entries(datumByKey).filter(([key]) => visibleLabels.has(key)));
|
|
4091
4738
|
if (Object.keys(filtered).length === 0) return null;
|
|
4092
4739
|
const nearestDatum = params?.tooltipData?.nearestDatum;
|
|
@@ -4096,13 +4743,18 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4096
4743
|
};
|
|
4097
4744
|
return renderTooltip({
|
|
4098
4745
|
...params,
|
|
4746
|
+
bucketInfo,
|
|
4099
4747
|
tooltipData: {
|
|
4100
4748
|
...params.tooltipData,
|
|
4101
4749
|
datumByKey: filtered,
|
|
4102
4750
|
nearestDatum: nextNearest
|
|
4103
4751
|
}
|
|
4104
4752
|
});
|
|
4105
|
-
}, [
|
|
4753
|
+
}, [
|
|
4754
|
+
renderTooltip,
|
|
4755
|
+
visibleLabels,
|
|
4756
|
+
bucketInfo
|
|
4757
|
+
]);
|
|
4106
4758
|
const resolvedFillOpacity = fillOpacity ?? (stacked ? .85 : .4);
|
|
4107
4759
|
const resolvedWithStroke = withStroke ?? !stacked;
|
|
4108
4760
|
if (error) return /* @__PURE__ */ jsx("div", {
|
|
@@ -4177,7 +4829,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4177
4829
|
onBlur: onChartBlur,
|
|
4178
4830
|
children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
|
|
4179
4831
|
ref: chartRef,
|
|
4180
|
-
|
|
4832
|
+
className: xy_plot_module_default["xy-plot"],
|
|
4181
4833
|
children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
|
|
4182
4834
|
theme,
|
|
4183
4835
|
width,
|
|
@@ -4233,7 +4885,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4233
4885
|
stacked,
|
|
4234
4886
|
stackOffset,
|
|
4235
4887
|
getElementStyles,
|
|
4236
|
-
strokeColor: theme.backgroundColor
|
|
4888
|
+
strokeColor: theme.backgroundColor
|
|
4237
4889
|
})] }),
|
|
4238
4890
|
/* @__PURE__ */ jsx(AreaChartScalesRef, {
|
|
4239
4891
|
chartRef: internalChartRef,
|
|
@@ -4401,8 +5053,7 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
|
|
|
4401
5053
|
year: "numeric",
|
|
4402
5054
|
month: "long",
|
|
4403
5055
|
day: "numeric",
|
|
4404
|
-
hour: "numeric"
|
|
4405
|
-
hour12: true
|
|
5056
|
+
hour: "numeric"
|
|
4406
5057
|
},
|
|
4407
5058
|
day: {
|
|
4408
5059
|
year: "numeric",
|
|
@@ -4421,16 +5072,16 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
|
|
|
4421
5072
|
*
|
|
4422
5073
|
* @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
|
|
4423
5074
|
* @param tickResolution - Caller-declared bucket resolution, when known.
|
|
5075
|
+
* @param formatting - Host locale and time zone.
|
|
4424
5076
|
* @return Tooltip label formatter.
|
|
4425
5077
|
*/
|
|
4426
|
-
const getTooltipFormatter = (data, tickResolution) => {
|
|
4427
|
-
if (tickResolution === "week")
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
}));
|
|
5078
|
+
const getTooltipFormatter = (data, tickResolution, formatting) => {
|
|
5079
|
+
if (tickResolution === "week") {
|
|
5080
|
+
const formatDay = createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION.day, formatting);
|
|
5081
|
+
return (timestamp) => sprintf(__("Week of %s", "jetpack-charts"), formatDay(timestamp));
|
|
5082
|
+
}
|
|
4432
5083
|
const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
|
|
4433
|
-
return (
|
|
5084
|
+
return createDateFormatter(format, formatting);
|
|
4434
5085
|
};
|
|
4435
5086
|
const identity = (label) => label;
|
|
4436
5087
|
const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
|
|
@@ -4481,6 +5132,7 @@ const getGroupPadding = (scale) => {
|
|
|
4481
5132
|
*/
|
|
4482
5133
|
function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
|
|
4483
5134
|
const stableOptions = useDeepMemo(options);
|
|
5135
|
+
const formatting = useChartFormatting();
|
|
4484
5136
|
const axisConfig = useMemo(() => {
|
|
4485
5137
|
const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, tickFormat: xTickFormat, ...xAxisOptions } = stableOptions.axis?.x || {};
|
|
4486
5138
|
const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, tickFormat: yTickFormat, ...yAxisOptions } = stableOptions.axis?.y || {};
|
|
@@ -4507,9 +5159,9 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
4507
5159
|
zero: false
|
|
4508
5160
|
};
|
|
4509
5161
|
const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
|
|
4510
|
-
const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
|
|
5162
|
+
const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution, formatting);
|
|
4511
5163
|
const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
|
|
4512
|
-
const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
|
|
5164
|
+
const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
|
|
4513
5165
|
const valueFormatter = formatNumberCompact;
|
|
4514
5166
|
const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
|
|
4515
5167
|
const valueAccessor = (d) => {
|
|
@@ -4545,7 +5197,8 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
4545
5197
|
}, [
|
|
4546
5198
|
data,
|
|
4547
5199
|
tickResolution,
|
|
4548
|
-
isSeriesRendered
|
|
5200
|
+
isSeriesRendered,
|
|
5201
|
+
formatting
|
|
4549
5202
|
]);
|
|
4550
5203
|
return useMemo(() => {
|
|
4551
5204
|
const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
|
|
@@ -5054,6 +5707,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5054
5707
|
onBlur: onChartBlur,
|
|
5055
5708
|
children: chartHeight > 0 && /* @__PURE__ */ jsx("div", {
|
|
5056
5709
|
ref: chartRef,
|
|
5710
|
+
className: xy_plot_module_default["xy-plot"],
|
|
5057
5711
|
children: /* @__PURE__ */ jsxs(XYChart, {
|
|
5058
5712
|
theme,
|
|
5059
5713
|
width,
|
|
@@ -5221,7 +5875,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
|
|
|
5221
5875
|
right: 20,
|
|
5222
5876
|
bottom: 0,
|
|
5223
5877
|
top: 0
|
|
5224
|
-
}, ...rest }) => {
|
|
5878
|
+
}, className, ...rest }) => {
|
|
5225
5879
|
const { getElementStyles } = useGlobalChartsContext();
|
|
5226
5880
|
const tintedData = useMemo(() => {
|
|
5227
5881
|
if (data.length > 1) return data;
|
|
@@ -5273,6 +5927,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
|
|
|
5273
5927
|
return /* @__PURE__ */ jsx(BarChart, {
|
|
5274
5928
|
orientation: "horizontal",
|
|
5275
5929
|
gridVisibility: "none",
|
|
5930
|
+
className: clsx("bar-list-chart", className),
|
|
5276
5931
|
data: tintedData,
|
|
5277
5932
|
width,
|
|
5278
5933
|
height,
|
|
@@ -5309,6 +5964,9 @@ const BarListChartResponsive = withResponsive(BarListChart);
|
|
|
5309
5964
|
var conversion_funnel_chart_module_default = {
|
|
5310
5965
|
"bar-container": "a8ccharts-mGEVca-bar-container",
|
|
5311
5966
|
"change-indicator": "a8ccharts-mGEVca-change-indicator",
|
|
5967
|
+
"change-indicator--negative": "a8ccharts-mGEVca-change-indicator--negative",
|
|
5968
|
+
"change-indicator--positive": "a8ccharts-mGEVca-change-indicator--positive",
|
|
5969
|
+
"conversion-funnel-chart": "a8ccharts-mGEVca-conversion-funnel-chart",
|
|
5312
5970
|
"conversion-funnel-chart--loading": "a8ccharts-mGEVca-conversion-funnel-chart--loading",
|
|
5313
5971
|
"empty-state": "a8ccharts-mGEVca-empty-state",
|
|
5314
5972
|
"funnel-bar": "a8ccharts-mGEVca-funnel-bar",
|
|
@@ -5389,23 +6047,14 @@ const useFunnelSelection = (hideTooltip) => {
|
|
|
5389
6047
|
*/
|
|
5390
6048
|
const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
|
|
5391
6049
|
const chartId = useChartId(providedChartId);
|
|
5392
|
-
const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
|
|
5393
6050
|
const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
|
|
5394
6051
|
const chartRef = useRef(null);
|
|
5395
6052
|
const selectedBarRef = useRef(null);
|
|
5396
6053
|
const [scopeNode, setScopeNode] = useState(null);
|
|
5397
6054
|
const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = useTooltip();
|
|
5398
6055
|
const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
|
|
5399
|
-
const
|
|
5400
|
-
detectBounds: true,
|
|
5401
|
-
scroll: true
|
|
5402
|
-
});
|
|
5403
|
-
const portalContainerRefRef = useRef(portalContainerRef);
|
|
5404
|
-
useEffect(() => {
|
|
5405
|
-
portalContainerRefRef.current = portalContainerRef;
|
|
5406
|
-
}, [portalContainerRef]);
|
|
6056
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
5407
6057
|
const setChartRef = useCallback((node) => {
|
|
5408
|
-
portalContainerRefRef.current(node);
|
|
5409
6058
|
chartRef.current = node;
|
|
5410
6059
|
setScopeNode(node);
|
|
5411
6060
|
}, []);
|
|
@@ -5422,30 +6071,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5422
6071
|
});
|
|
5423
6072
|
}, [showTooltip]);
|
|
5424
6073
|
const getMouseTooltipCoords = useCallback((event) => {
|
|
5425
|
-
|
|
6074
|
+
const bounds = chartRef.current?.getBoundingClientRect();
|
|
6075
|
+
if (!bounds) return null;
|
|
5426
6076
|
return {
|
|
5427
|
-
x: event.clientX -
|
|
5428
|
-
y: event.clientY -
|
|
6077
|
+
x: event.clientX - bounds.left,
|
|
6078
|
+
y: event.clientY - bounds.top
|
|
5429
6079
|
};
|
|
5430
|
-
}, [
|
|
5431
|
-
containerBounds.width,
|
|
5432
|
-
containerBounds.height,
|
|
5433
|
-
containerBounds.left,
|
|
5434
|
-
containerBounds.top
|
|
5435
|
-
]);
|
|
6080
|
+
}, []);
|
|
5436
6081
|
const getKeyboardTooltipCoords = useCallback((event) => {
|
|
5437
|
-
|
|
6082
|
+
const bounds = chartRef.current?.getBoundingClientRect();
|
|
6083
|
+
if (!bounds) return null;
|
|
5438
6084
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
5439
6085
|
return {
|
|
5440
|
-
x: rect.left + rect.width / 2 -
|
|
5441
|
-
y: rect.top -
|
|
6086
|
+
x: rect.left + rect.width / 2 - bounds.left,
|
|
6087
|
+
y: rect.top - bounds.top
|
|
5442
6088
|
};
|
|
5443
|
-
}, [
|
|
5444
|
-
containerBounds.width,
|
|
5445
|
-
containerBounds.height,
|
|
5446
|
-
containerBounds.left,
|
|
5447
|
-
containerBounds.top
|
|
5448
|
-
]);
|
|
6089
|
+
}, []);
|
|
5449
6090
|
const handleStepInteraction = useCallback((step, event, interactionType) => {
|
|
5450
6091
|
selectedBarRef.current = event.currentTarget;
|
|
5451
6092
|
const { isClicked } = getStepState(step.id);
|
|
@@ -5506,19 +6147,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5506
6147
|
};
|
|
5507
6148
|
}, [clearSelectionAndRef]);
|
|
5508
6149
|
const resolvedHeight = height ?? style?.height ?? "100%";
|
|
5509
|
-
const {
|
|
5510
|
-
const
|
|
5511
|
-
|
|
5512
|
-
overrideColor: primaryColor
|
|
5513
|
-
}) : { color: primaryColor || "#000000" };
|
|
5514
|
-
const changeColor = changeIndicator?.startsWith("+") ? positiveChangeColor : negativeChangeColor;
|
|
5515
|
-
const barBackgroundColor = backgroundColor || hexToRgba(barColor, .08) || "rgba(0, 0, 0, 0.08)";
|
|
6150
|
+
const { color: barColor } = getElementStyles ? getElementStyles({ index: 0 }) : { color: "#000000" };
|
|
6151
|
+
const isPositiveChange = changeIndicator?.startsWith("+");
|
|
6152
|
+
const changeColor = isPositiveChange ? CATALOG_POINTERS.trendUp : CATALOG_POINTERS.trendDown;
|
|
5516
6153
|
const renderDefaultMainMetric = () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
5517
6154
|
className: conversion_funnel_chart_module_default["main-rate"],
|
|
5518
6155
|
children: formatPercentage(mainRate)
|
|
5519
6156
|
}), changeIndicator && /* @__PURE__ */ jsx("span", {
|
|
5520
|
-
className: conversion_funnel_chart_module_default["change-indicator"],
|
|
5521
|
-
style: { color: changeColor },
|
|
6157
|
+
className: clsx(conversion_funnel_chart_module_default["change-indicator"], conversion_funnel_chart_module_default[isPositiveChange ? "change-indicator--positive" : "change-indicator--negative"]),
|
|
5522
6158
|
children: changeIndicator
|
|
5523
6159
|
})] });
|
|
5524
6160
|
const renderDefaultTooltip = (step) => /* @__PURE__ */ jsxs(Stack, {
|
|
@@ -5556,7 +6192,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5556
6192
|
align: "center",
|
|
5557
6193
|
justify: "center",
|
|
5558
6194
|
ref: setScopeNode,
|
|
5559
|
-
className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
6195
|
+
className: clsx("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5560
6196
|
style: {
|
|
5561
6197
|
...style,
|
|
5562
6198
|
height: resolvedHeight
|
|
@@ -5567,16 +6203,16 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5567
6203
|
})
|
|
5568
6204
|
});
|
|
5569
6205
|
const maxRate = Math.max(...steps.map((step) => step.rate));
|
|
5570
|
-
return /* @__PURE__ */ jsxs(
|
|
6206
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
5571
6207
|
direction: "column",
|
|
5572
6208
|
gap: "xl",
|
|
5573
6209
|
ref: setChartRef,
|
|
5574
|
-
className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
6210
|
+
className: clsx("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
|
|
5575
6211
|
style: {
|
|
5576
6212
|
...style,
|
|
5577
6213
|
height: resolvedHeight
|
|
5578
6214
|
},
|
|
5579
|
-
children: /* @__PURE__ */ jsxs(ChartScopeContext.Provider, {
|
|
6215
|
+
children: [/* @__PURE__ */ jsxs(ChartScopeContext.Provider, {
|
|
5580
6216
|
value: scopeNode,
|
|
5581
6217
|
children: [renderMainMetric ? renderMainMetric({
|
|
5582
6218
|
mainRate,
|
|
@@ -5628,7 +6264,6 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5628
6264
|
role: "button",
|
|
5629
6265
|
tabIndex: isBlurred ? -1 : 0,
|
|
5630
6266
|
"aria-label": step.label,
|
|
5631
|
-
style: { backgroundColor: barBackgroundColor },
|
|
5632
6267
|
children: /* @__PURE__ */ jsx("div", {
|
|
5633
6268
|
className: clsx(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
|
|
5634
6269
|
style: {
|
|
@@ -5640,23 +6275,23 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
|
|
|
5640
6275
|
}, step.id);
|
|
5641
6276
|
})
|
|
5642
6277
|
})]
|
|
5643
|
-
})
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
}
|
|
5659
|
-
})
|
|
6278
|
+
}), tooltipOpen && tooltipData && (() => {
|
|
6279
|
+
const tooltipContent = renderTooltip ? renderTooltip({
|
|
6280
|
+
step: tooltipData,
|
|
6281
|
+
index: steps.findIndex((s) => s.id === tooltipData.id),
|
|
6282
|
+
top: tooltipTop,
|
|
6283
|
+
left: tooltipLeft,
|
|
6284
|
+
className: conversion_funnel_chart_module_default["tooltip-wrapper"]
|
|
6285
|
+
}) : renderDefaultTooltip(tooltipData);
|
|
6286
|
+
if (!tooltipContent) return null;
|
|
6287
|
+
return /* @__PURE__ */ jsx(BoundedTooltip, {
|
|
6288
|
+
top: tooltipTop,
|
|
6289
|
+
left: tooltipLeft,
|
|
6290
|
+
className: clsx(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
|
|
6291
|
+
children: tooltipContent
|
|
6292
|
+
});
|
|
6293
|
+
})()]
|
|
6294
|
+
});
|
|
5660
6295
|
};
|
|
5661
6296
|
/**
|
|
5662
6297
|
* ConversionFunnelChart component with provider wrapper
|
|
@@ -5802,7 +6437,7 @@ function normalizeGeoChartError(eventArgs) {
|
|
|
5802
6437
|
* @return A React component displaying an interactive map with data visualization
|
|
5803
6438
|
*/
|
|
5804
6439
|
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
|
|
5805
|
-
const { getElementStyles
|
|
6440
|
+
const { getElementStyles } = useGlobalChartsContext();
|
|
5806
6441
|
const scopeElement = useChartScopeElement();
|
|
5807
6442
|
const containerRef = useRef(null);
|
|
5808
6443
|
const reportedErrorIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -5836,8 +6471,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
5836
6471
|
});
|
|
5837
6472
|
const fullColorHex = getElementStyles({ index: 0 }).color;
|
|
5838
6473
|
const lightColorHex = lightenHexColor(fullColorHex, .8);
|
|
5839
|
-
const backgroundColorHex = normalizeColorToHex(
|
|
5840
|
-
const defaultFillColorHex = normalizeColorToHex(
|
|
6474
|
+
const backgroundColorHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
|
|
6475
|
+
const defaultFillColorHex = normalizeColorToHex(CATALOG_POINTERS.surfaceSecondary, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
|
|
5841
6476
|
const sanitizedData = useMemo(() => {
|
|
5842
6477
|
if (data.length === 0) return {
|
|
5843
6478
|
data,
|
|
@@ -5898,8 +6533,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
5898
6533
|
className: clsx("geo-chart", geo_chart_module_default.container, className),
|
|
5899
6534
|
style: {
|
|
5900
6535
|
width,
|
|
5901
|
-
height
|
|
5902
|
-
backgroundColor
|
|
6536
|
+
height
|
|
5903
6537
|
},
|
|
5904
6538
|
children: /* @__PURE__ */ jsx(Chart, {
|
|
5905
6539
|
chartType: "GeoChart",
|
|
@@ -5943,12 +6577,17 @@ var heatmap_chart_module_default = {
|
|
|
5943
6577
|
"heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
|
|
5944
6578
|
"heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
|
|
5945
6579
|
"heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
|
|
6580
|
+
"heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
|
|
5946
6581
|
"heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
|
|
5947
6582
|
"heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
|
|
6583
|
+
"heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
|
|
5948
6584
|
"heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
|
|
6585
|
+
"heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
|
|
6586
|
+
"heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
|
|
5949
6587
|
"heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
|
|
5950
6588
|
"heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
|
|
5951
6589
|
"heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
|
|
6590
|
+
"heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
|
|
5952
6591
|
"heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
|
|
5953
6592
|
"heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
|
|
5954
6593
|
"heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
|
|
@@ -5958,17 +6597,21 @@ var heatmap_chart_module_default = {
|
|
|
5958
6597
|
//#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
|
|
5959
6598
|
const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
|
|
5960
6599
|
/**
|
|
5961
|
-
* Get the min and max values from heatmap data, ignoring null/NaN.
|
|
6600
|
+
* Get the min and max values from heatmap data, ignoring null/NaN. Summary
|
|
6601
|
+
* columns stay out: a roll-up on the scale would flatten every real cell.
|
|
5962
6602
|
* @param data - The heatmap columns
|
|
5963
6603
|
* @return Tuple of [min, max] values
|
|
5964
6604
|
*/
|
|
5965
6605
|
const getValueExtent = (data) => {
|
|
5966
6606
|
let min = Infinity;
|
|
5967
6607
|
let max = -Infinity;
|
|
5968
|
-
for (const column of data)
|
|
5969
|
-
if (
|
|
5970
|
-
|
|
5971
|
-
|
|
6608
|
+
for (const column of data) {
|
|
6609
|
+
if (column.summary) continue;
|
|
6610
|
+
for (const cell of column.data) {
|
|
6611
|
+
if (!isPresent(cell.value)) continue;
|
|
6612
|
+
if (cell.value < min) min = cell.value;
|
|
6613
|
+
if (cell.value > max) max = cell.value;
|
|
6614
|
+
}
|
|
5972
6615
|
}
|
|
5973
6616
|
if (min === Infinity) return [0, 0];
|
|
5974
6617
|
return [min, max];
|
|
@@ -5989,6 +6632,119 @@ const getNormalizedValue = (value, extent) => {
|
|
|
5989
6632
|
return Math.min(1, Math.max(0, (value - min) / (max - min)));
|
|
5990
6633
|
};
|
|
5991
6634
|
//#endregion
|
|
6635
|
+
//#region src/charts/heatmap-chart/private/civil-day.ts
|
|
6636
|
+
const DAY_MS = 864e5;
|
|
6637
|
+
const KEY_OPTIONS = {
|
|
6638
|
+
calendar: "gregory",
|
|
6639
|
+
numberingSystem: "latn",
|
|
6640
|
+
year: "numeric",
|
|
6641
|
+
month: "2-digit",
|
|
6642
|
+
day: "2-digit"
|
|
6643
|
+
};
|
|
6644
|
+
const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
6645
|
+
const pad = (value, length) => String(value).padStart(length, "0");
|
|
6646
|
+
/**
|
|
6647
|
+
* The day a UTC proxy stands for.
|
|
6648
|
+
*
|
|
6649
|
+
* @param date - UTC proxy.
|
|
6650
|
+
* @return Its day key.
|
|
6651
|
+
*/
|
|
6652
|
+
const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
|
|
6653
|
+
/**
|
|
6654
|
+
* A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
|
|
6655
|
+
* `DAY_MS`.
|
|
6656
|
+
*
|
|
6657
|
+
* @param key - Day key.
|
|
6658
|
+
* @return The proxy, or null when the key names no real day.
|
|
6659
|
+
*/
|
|
6660
|
+
const civilDate = (key) => {
|
|
6661
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
|
6662
|
+
if (!match) return null;
|
|
6663
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
6664
|
+
date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
6665
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
6666
|
+
return civilKey(date) === key ? date : null;
|
|
6667
|
+
};
|
|
6668
|
+
/**
|
|
6669
|
+
* The day a written date names, ignoring any time and zone it carries.
|
|
6670
|
+
*
|
|
6671
|
+
* @param text - `yyyy-MM-dd`, optionally followed by a time.
|
|
6672
|
+
* @return Its day key, or null.
|
|
6673
|
+
*/
|
|
6674
|
+
const writtenDayKey = (text) => {
|
|
6675
|
+
const match = DATE_PREFIX.exec(text);
|
|
6676
|
+
if (!match) return null;
|
|
6677
|
+
const key = `${match[1]}-${match[2]}-${match[3]}`;
|
|
6678
|
+
return civilDate(key) ? key : null;
|
|
6679
|
+
};
|
|
6680
|
+
/**
|
|
6681
|
+
* Reads the day an instant falls on in a zone.
|
|
6682
|
+
*
|
|
6683
|
+
* @param timeZone - IANA zone name; absent leaves the runtime's own.
|
|
6684
|
+
* @return Reader taking a `Date`, null for a day no key can hold.
|
|
6685
|
+
*/
|
|
6686
|
+
const instantDayReader = (timeZone) => {
|
|
6687
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
6688
|
+
...KEY_OPTIONS,
|
|
6689
|
+
timeZone
|
|
6690
|
+
});
|
|
6691
|
+
return (date) => {
|
|
6692
|
+
const parts = formatter.formatToParts(date);
|
|
6693
|
+
const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
6694
|
+
const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
|
|
6695
|
+
return civilDate(key) ? key : null;
|
|
6696
|
+
};
|
|
6697
|
+
};
|
|
6698
|
+
/**
|
|
6699
|
+
* The day a series point belongs to.
|
|
6700
|
+
*
|
|
6701
|
+
* A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
|
|
6702
|
+
* zone. Anything else is a wall clock the host wrote, so its date part is the day.
|
|
6703
|
+
*
|
|
6704
|
+
* @param point - Series point.
|
|
6705
|
+
* @param readInstant - Reader from `instantDayReader`.
|
|
6706
|
+
* @return Its day key, or null when the point carries no usable date.
|
|
6707
|
+
*/
|
|
6708
|
+
const pointDayKey = (point, readInstant) => {
|
|
6709
|
+
if (point.date instanceof Date && !isNaN(point.date.getTime())) {
|
|
6710
|
+
const key = readInstant(point.date);
|
|
6711
|
+
if (key) return key;
|
|
6712
|
+
}
|
|
6713
|
+
const text = point.dateString;
|
|
6714
|
+
if (!text) return null;
|
|
6715
|
+
if (!hasTimezone(text)) return writtenDayKey(text);
|
|
6716
|
+
const parsed = parseISO(text);
|
|
6717
|
+
return isNaN(parsed.getTime()) ? null : readInstant(parsed);
|
|
6718
|
+
};
|
|
6719
|
+
/**
|
|
6720
|
+
* Steps a proxy by whole days.
|
|
6721
|
+
*
|
|
6722
|
+
* @param date - UTC proxy.
|
|
6723
|
+
* @param days - Days to add; may be negative.
|
|
6724
|
+
* @return The shifted proxy.
|
|
6725
|
+
*/
|
|
6726
|
+
const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
|
|
6727
|
+
/**
|
|
6728
|
+
* Rounds a proxy down to its week.
|
|
6729
|
+
*
|
|
6730
|
+
* @param date - UTC proxy.
|
|
6731
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
6732
|
+
* @return The proxy for that week's first day.
|
|
6733
|
+
*/
|
|
6734
|
+
const startOfCivilWeek = (date, weekStartsOn) => {
|
|
6735
|
+
const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
|
|
6736
|
+
return addCivilDays(date, -daysSinceWeekStart);
|
|
6737
|
+
};
|
|
6738
|
+
/**
|
|
6739
|
+
* Counts the columns a span needs.
|
|
6740
|
+
*
|
|
6741
|
+
* @param from - Earlier UTC proxy.
|
|
6742
|
+
* @param to - Later UTC proxy.
|
|
6743
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
6744
|
+
* @return Columns needed to cover the span, inclusive.
|
|
6745
|
+
*/
|
|
6746
|
+
const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
|
|
6747
|
+
//#endregion
|
|
5992
6748
|
//#region src/charts/heatmap-chart/private/build-calendar-data.ts
|
|
5993
6749
|
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
5994
6750
|
const LABELLED_ROWS = [
|
|
@@ -6003,69 +6759,88 @@ const LABELLED_ROWS = [
|
|
|
6003
6759
|
* @param bound - Requested bound as `yyyy-MM-dd`, if any.
|
|
6004
6760
|
* @param fallback - The series' own bound on this side.
|
|
6005
6761
|
* @param direction - Which way the bound is allowed to move.
|
|
6006
|
-
* @return The
|
|
6762
|
+
* @return The day key to draw to.
|
|
6007
6763
|
*/
|
|
6008
6764
|
const widenTo = (bound, fallback, direction) => {
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
return parsed > fallback ? parsed : fallback;
|
|
6014
|
-
};
|
|
6015
|
-
const toDate = (point) => {
|
|
6016
|
-
if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
|
|
6017
|
-
if (point.dateString) {
|
|
6018
|
-
const parsed = parseISO(point.dateString);
|
|
6019
|
-
if (!isNaN(parsed.getTime())) return parsed;
|
|
6765
|
+
const key = bound ? writtenDayKey(bound) : null;
|
|
6766
|
+
if (!key) {
|
|
6767
|
+
if (bound) warnOnce(`heatmap:gridSpan:${bound}`, `gridSpan.${direction === "earlier" ? "start" : "end"} ${JSON.stringify(bound)} is not a \`yyyy-MM-dd\` day, so the grid is drawn over the series' own span.`);
|
|
6768
|
+
return fallback;
|
|
6020
6769
|
}
|
|
6021
|
-
return
|
|
6770
|
+
if (direction === "earlier") return key < fallback ? key : fallback;
|
|
6771
|
+
return key > fallback ? key : fallback;
|
|
6022
6772
|
};
|
|
6773
|
+
/**
|
|
6774
|
+
* Lay a day-bucketed series out as calendar columns.
|
|
6775
|
+
*
|
|
6776
|
+
* @param series - Points to bucket.
|
|
6777
|
+
* @param options - Grid shape, plus the locale and zone to read days in.
|
|
6778
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
6779
|
+
*/
|
|
6023
6780
|
const buildCalendarHeatmapData = (series, options = {}) => {
|
|
6024
6781
|
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
6025
6782
|
const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
|
|
6026
|
-
const
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
})
|
|
6030
|
-
|
|
6783
|
+
const { locale, timeZone } = sanitizeFormatting({
|
|
6784
|
+
locale: options.locale,
|
|
6785
|
+
timeZone: options.timeZone
|
|
6786
|
+
});
|
|
6787
|
+
const readInstant = instantDayReader(timeZone);
|
|
6788
|
+
const valueByDay = /* @__PURE__ */ new Map();
|
|
6789
|
+
let minDayKey;
|
|
6790
|
+
let maxDayKey;
|
|
6791
|
+
for (const point of series) {
|
|
6792
|
+
const key = pointDayKey(point, readInstant);
|
|
6793
|
+
if (!key) {
|
|
6794
|
+
const offending = point.dateString ?? (point.date && String(point.date));
|
|
6795
|
+
warnOnce(`heatmap:unreadableDate:${offending}`, offending ? `${JSON.stringify(offending)} is not a day this can read, so its point is left out of the calendar. A \`dateString\` must start \`yyyy-MM-dd\`.` : "A point carries neither `date` nor `dateString`, so it is left out of the calendar.");
|
|
6796
|
+
continue;
|
|
6797
|
+
}
|
|
6798
|
+
valueByDay.set(key, point.value);
|
|
6799
|
+
if (!minDayKey || key < minDayKey) minDayKey = key;
|
|
6800
|
+
if (!maxDayKey || key > maxDayKey) maxDayKey = key;
|
|
6801
|
+
}
|
|
6802
|
+
if (!minDayKey || !maxDayKey) return {
|
|
6031
6803
|
data: [],
|
|
6032
6804
|
rowLabels: []
|
|
6033
6805
|
};
|
|
6034
|
-
const
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
}
|
|
6042
|
-
const
|
|
6043
|
-
const
|
|
6044
|
-
const
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
const
|
|
6806
|
+
const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
|
|
6807
|
+
const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
|
|
6808
|
+
const requestedMinDate = civilDate(requestedMinDayKey);
|
|
6809
|
+
const gridMaxDate = civilDate(gridMaxDayKey);
|
|
6810
|
+
const labelFormatting = {
|
|
6811
|
+
locale,
|
|
6812
|
+
timeZone: "UTC"
|
|
6813
|
+
};
|
|
6814
|
+
const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
|
|
6815
|
+
const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
|
|
6816
|
+
const formatDay = createDateFormatter({
|
|
6817
|
+
weekday: "short",
|
|
6818
|
+
month: "short",
|
|
6819
|
+
day: "numeric",
|
|
6820
|
+
year: "numeric"
|
|
6821
|
+
}, labelFormatting);
|
|
6822
|
+
const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
|
|
6823
|
+
const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
|
|
6824
|
+
const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
|
|
6825
|
+
const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
|
|
6051
6826
|
const MIN_FIRST_MONTH_WEEKS = 2;
|
|
6052
|
-
const firstMonth = gridStart.
|
|
6827
|
+
const firstMonth = gridStart.getUTCMonth();
|
|
6053
6828
|
let firstMonthWeeks = 0;
|
|
6054
|
-
while (firstMonthWeeks < weekCount &&
|
|
6829
|
+
while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
|
|
6055
6830
|
const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
|
|
6056
6831
|
const data = [];
|
|
6057
6832
|
let previousMonth = -1;
|
|
6058
6833
|
for (let week = 0; week < weekCount; week++) {
|
|
6059
|
-
const columnStart =
|
|
6060
|
-
const month = columnStart.
|
|
6061
|
-
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ?
|
|
6834
|
+
const columnStart = addCivilDays(gridStart, week * 7);
|
|
6835
|
+
const month = columnStart.getUTCMonth();
|
|
6836
|
+
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
|
|
6062
6837
|
previousMonth = month;
|
|
6063
6838
|
const cells = [];
|
|
6064
6839
|
for (let row = 0; row < 7; row++) {
|
|
6065
|
-
const day =
|
|
6066
|
-
const key =
|
|
6840
|
+
const day = addCivilDays(gridStart, week * 7 + row);
|
|
6841
|
+
const key = civilKey(day);
|
|
6067
6842
|
const cell = {
|
|
6068
|
-
label:
|
|
6843
|
+
label: formatDay(day),
|
|
6069
6844
|
value: valueByDay.has(key) ? valueByDay.get(key) : null
|
|
6070
6845
|
};
|
|
6071
6846
|
if (key < gridMinDayKey || key > gridMaxDayKey) {
|
|
@@ -6091,7 +6866,7 @@ const HeatmapContext = createContext(null);
|
|
|
6091
6866
|
//#region src/charts/heatmap-chart/private/heatmap-legend.tsx
|
|
6092
6867
|
const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
6093
6868
|
const context = useContext(HeatmapContext);
|
|
6094
|
-
const { legend
|
|
6869
|
+
const { legend } = useGlobalChartsTheme();
|
|
6095
6870
|
if (!context) return null;
|
|
6096
6871
|
const { primaryColorHex } = context;
|
|
6097
6872
|
const labelStyle = legend.labelStyles;
|
|
@@ -6102,6 +6877,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6102
6877
|
children: [
|
|
6103
6878
|
/* @__PURE__ */ jsx(Text$1, {
|
|
6104
6879
|
variant: "body-sm",
|
|
6880
|
+
className: heatmap_chart_module_default["heatmap-chart__legend-label"],
|
|
6105
6881
|
style: labelStyle,
|
|
6106
6882
|
children: lessLabel ?? __("Less", "jetpack-charts")
|
|
6107
6883
|
}),
|
|
@@ -6115,7 +6891,6 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6115
6891
|
className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
|
|
6116
6892
|
style: {
|
|
6117
6893
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
6118
|
-
"--a8c-charts-color-heatmap-background": backgroundColor,
|
|
6119
6894
|
"--a8c-charts-heatmap-cell-intensity": intensity
|
|
6120
6895
|
}
|
|
6121
6896
|
}, index);
|
|
@@ -6123,6 +6898,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6123
6898
|
}),
|
|
6124
6899
|
/* @__PURE__ */ jsx(Text$1, {
|
|
6125
6900
|
variant: "body-sm",
|
|
6901
|
+
className: heatmap_chart_module_default["heatmap-chart__legend-label"],
|
|
6126
6902
|
style: labelStyle,
|
|
6127
6903
|
children: moreLabel ?? __("More", "jetpack-charts")
|
|
6128
6904
|
})
|
|
@@ -6132,7 +6908,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6132
6908
|
//#endregion
|
|
6133
6909
|
//#region src/charts/heatmap-chart/heatmap-chart.tsx
|
|
6134
6910
|
const CELL_MIX_FLOOR = .15;
|
|
6135
|
-
const
|
|
6911
|
+
const NO_ROW_LABELS = [];
|
|
6912
|
+
const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, primaryColor, gap = "md", withTooltips = false, renderTooltip, children }) => {
|
|
6136
6913
|
const chartId = useChartId(providedChartId);
|
|
6137
6914
|
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
6138
6915
|
const scopeElement = useChartScopeElement();
|
|
@@ -6140,17 +6917,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6140
6917
|
const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
|
|
6141
6918
|
const [selectedIndex, setSelectedIndex] = useState();
|
|
6142
6919
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = useTooltip();
|
|
6143
|
-
const
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
});
|
|
6147
|
-
const containerBoundsRef = useRef(containerBounds);
|
|
6148
|
-
containerBoundsRef.current = containerBounds;
|
|
6920
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
6921
|
+
const containerRef = useRef(null);
|
|
6922
|
+
const getTooltipOrigin = useCallback(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
|
|
6149
6923
|
const { color: primaryColorHex } = getElementStyles({
|
|
6150
6924
|
index: 0,
|
|
6151
|
-
overrideColor: primaryColor
|
|
6925
|
+
overrideColor: primaryColor
|
|
6152
6926
|
});
|
|
6153
|
-
const chartBackgroundHex = normalizeColorToHex(
|
|
6927
|
+
const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
|
|
6154
6928
|
const primaryHex = normalizeColorToHex(primaryColorHex);
|
|
6155
6929
|
const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
|
|
6156
6930
|
const extent = useMemo(() => getValueExtent(data), [data]);
|
|
@@ -6226,19 +7000,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6226
7000
|
]);
|
|
6227
7001
|
const handleCellMouseMove = useCallback((event) => {
|
|
6228
7002
|
if (!withTooltips) return;
|
|
7003
|
+
const origin = getTooltipOrigin();
|
|
7004
|
+
if (!origin) return;
|
|
6229
7005
|
const target = event.currentTarget;
|
|
6230
7006
|
const columnIndex = Number(target.dataset.column);
|
|
6231
7007
|
const rowIndex = Number(target.dataset.row);
|
|
6232
|
-
const bounds = containerBoundsRef.current;
|
|
6233
7008
|
showTooltip({
|
|
6234
|
-
tooltipLeft: event.clientX -
|
|
6235
|
-
tooltipTop: event.clientY -
|
|
7009
|
+
tooltipLeft: event.clientX - origin.left,
|
|
7010
|
+
tooltipTop: event.clientY - origin.top,
|
|
6236
7011
|
tooltipData: buildTooltipData(columnIndex, rowIndex)
|
|
6237
7012
|
});
|
|
6238
7013
|
}, [
|
|
6239
7014
|
withTooltips,
|
|
6240
7015
|
showTooltip,
|
|
6241
|
-
buildTooltipData
|
|
7016
|
+
buildTooltipData,
|
|
7017
|
+
getTooltipOrigin
|
|
6242
7018
|
]);
|
|
6243
7019
|
const handleCellMouseLeave = useCallback(() => {
|
|
6244
7020
|
if (withTooltips && selectedIndex === void 0) hideTooltip();
|
|
@@ -6249,13 +7025,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6249
7025
|
]);
|
|
6250
7026
|
useEffect(() => {
|
|
6251
7027
|
if (!withTooltips || selectedIndex === void 0) return;
|
|
7028
|
+
const origin = getTooltipOrigin();
|
|
7029
|
+
if (!origin) return;
|
|
6252
7030
|
const col = Math.floor(selectedIndex / rows);
|
|
6253
7031
|
const row = selectedIndex % rows;
|
|
6254
7032
|
const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
|
|
6255
|
-
const bounds = containerBoundsRef.current;
|
|
6256
7033
|
showTooltip({
|
|
6257
|
-
tooltipLeft: rect ? rect.left + rect.width / 2 -
|
|
6258
|
-
tooltipTop: rect ? rect.top + rect.height / 2 -
|
|
7034
|
+
tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
|
|
7035
|
+
tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
|
|
6259
7036
|
tooltipData: buildTooltipData(col, row)
|
|
6260
7037
|
});
|
|
6261
7038
|
}, [
|
|
@@ -6264,7 +7041,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6264
7041
|
rows,
|
|
6265
7042
|
chartId,
|
|
6266
7043
|
buildTooltipData,
|
|
6267
|
-
showTooltip
|
|
7044
|
+
showTooltip,
|
|
7045
|
+
getTooltipOrigin
|
|
6268
7046
|
]);
|
|
6269
7047
|
const defaultRenderTooltip = useCallback((info) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("strong", { children: info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim() }), /* @__PURE__ */ jsx("div", { children: info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value) })] }), []);
|
|
6270
7048
|
if (!columns || !rows) return /* @__PURE__ */ jsx(Center, {
|
|
@@ -6282,14 +7060,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6282
7060
|
const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
|
|
6283
7061
|
const gridStyle = {
|
|
6284
7062
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
6285
|
-
"
|
|
6286
|
-
gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
|
|
7063
|
+
gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
|
|
6287
7064
|
gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
|
|
6288
7065
|
};
|
|
6289
7066
|
if (compact) {
|
|
6290
7067
|
gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
|
|
6291
7068
|
gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
|
|
6292
7069
|
}
|
|
7070
|
+
const summaryGaps = (columnIndex) => {
|
|
7071
|
+
if (!data[columnIndex]?.summary) return {};
|
|
7072
|
+
return {
|
|
7073
|
+
[heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
|
|
7074
|
+
[heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
|
|
7075
|
+
};
|
|
7076
|
+
};
|
|
6293
7077
|
const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
|
|
6294
7078
|
const heightCapped = !compact && Boolean(maxCellHeight);
|
|
6295
7079
|
return /* @__PURE__ */ jsx(HeatmapContext.Provider, {
|
|
@@ -6327,7 +7111,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6327
7111
|
"aria-hidden": "true",
|
|
6328
7112
|
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
6329
7113
|
children: [/* @__PURE__ */ jsx("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ jsx("span", {
|
|
6330
|
-
className: heatmap_chart_module_default["heatmap-chart__col-label"],
|
|
7114
|
+
className: clsx(heatmap_chart_module_default["heatmap-chart__col-label"], {
|
|
7115
|
+
[heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
|
|
7116
|
+
...summaryGaps(columnIndex)
|
|
7117
|
+
}),
|
|
6331
7118
|
children: column.label
|
|
6332
7119
|
}, `col-${columnIndex}`))]
|
|
6333
7120
|
}), Array.from({ length: rows }).map((_row, rowIndex) => {
|
|
@@ -6352,7 +7139,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6352
7139
|
}, `cell-${columnIndex}-${rowIndex}`);
|
|
6353
7140
|
const value = cell?.value ?? null;
|
|
6354
7141
|
const present = isPresent(value);
|
|
6355
|
-
const
|
|
7142
|
+
const filled = present && !column.summary;
|
|
7143
|
+
const normalized = filled ? getNormalizedValue(value, extent) : 0;
|
|
6356
7144
|
const flatIndex = columnIndex * rows + rowIndex;
|
|
6357
7145
|
const info = buildTooltipData(columnIndex, rowIndex);
|
|
6358
7146
|
const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value)}`;
|
|
@@ -6365,14 +7153,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6365
7153
|
"data-column": columnIndex,
|
|
6366
7154
|
"data-row": rowIndex,
|
|
6367
7155
|
className: clsx(heatmap_chart_module_default["heatmap-chart__cell"], {
|
|
6368
|
-
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]:
|
|
6369
|
-
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]:
|
|
7156
|
+
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
|
|
7157
|
+
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
|
|
7158
|
+
[heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
|
|
7159
|
+
...summaryGaps(columnIndex),
|
|
6370
7160
|
[heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
|
|
6371
7161
|
}),
|
|
6372
|
-
style:
|
|
7162
|
+
style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
|
|
6373
7163
|
onMouseMove: handleCellMouseMove,
|
|
6374
7164
|
onMouseLeave: handleCellMouseLeave,
|
|
6375
|
-
children: drawValues && present && /* @__PURE__ */ jsx("span", {
|
|
7165
|
+
children: (drawValues || column.summary) && present && /* @__PURE__ */ jsx("span", {
|
|
6376
7166
|
className: heatmap_chart_module_default["heatmap-chart__cell-value"],
|
|
6377
7167
|
children: formatNumberCompact(value)
|
|
6378
7168
|
})
|
|
@@ -6380,11 +7170,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6380
7170
|
})]
|
|
6381
7171
|
}, `row-${rowIndex}`);
|
|
6382
7172
|
})]
|
|
6383
|
-
}), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(
|
|
7173
|
+
}), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
|
|
6384
7174
|
top: tooltipTop,
|
|
6385
7175
|
left: tooltipLeft,
|
|
6386
7176
|
children: /* @__PURE__ */ jsx("div", {
|
|
6387
|
-
className:
|
|
7177
|
+
className: standaloneScopeClass,
|
|
6388
7178
|
role: "tooltip",
|
|
6389
7179
|
tabIndex: -1,
|
|
6390
7180
|
children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
|
|
@@ -6408,7 +7198,43 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ jsx(HeatmapChartW
|
|
|
6408
7198
|
HeatmapChartResponsiveInner.displayName = "HeatmapChart";
|
|
6409
7199
|
const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
|
|
6410
7200
|
//#endregion
|
|
6411
|
-
//#region
|
|
7201
|
+
//#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
|
|
7202
|
+
/**
|
|
7203
|
+
* `buildCalendarHeatmapData` with the locale and zone taken from
|
|
7204
|
+
* `GlobalChartsProvider` where the caller names neither.
|
|
7205
|
+
*
|
|
7206
|
+
* @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
|
|
7207
|
+
* @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
|
|
7208
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
7209
|
+
*/
|
|
7210
|
+
const useCalendarHeatmapData = (series, options = {}) => {
|
|
7211
|
+
const formatting = useChartFormatting();
|
|
7212
|
+
const locale = options.locale ?? formatting.locale;
|
|
7213
|
+
const timeZone = options.timeZone ?? formatting.timeZone;
|
|
7214
|
+
const { weekStartsOn, hideOutOfRangeDays } = options;
|
|
7215
|
+
const gridStart = options.gridSpan?.start;
|
|
7216
|
+
const gridEnd = options.gridSpan?.end;
|
|
7217
|
+
return useMemo(() => buildCalendarHeatmapData(series, {
|
|
7218
|
+
locale,
|
|
7219
|
+
timeZone,
|
|
7220
|
+
weekStartsOn,
|
|
7221
|
+
hideOutOfRangeDays,
|
|
7222
|
+
gridSpan: {
|
|
7223
|
+
start: gridStart,
|
|
7224
|
+
end: gridEnd
|
|
7225
|
+
}
|
|
7226
|
+
}), [
|
|
7227
|
+
series,
|
|
7228
|
+
locale,
|
|
7229
|
+
timeZone,
|
|
7230
|
+
weekStartsOn,
|
|
7231
|
+
hideOutOfRangeDays,
|
|
7232
|
+
gridStart,
|
|
7233
|
+
gridEnd
|
|
7234
|
+
]);
|
|
7235
|
+
};
|
|
7236
|
+
//#endregion
|
|
7237
|
+
//#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
|
|
6412
7238
|
/*!
|
|
6413
7239
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
6414
7240
|
*
|
|
@@ -6475,7 +7301,7 @@ function paramCase(input, options) {
|
|
|
6475
7301
|
return dotCase(input, __assign({ delimiter: "-" }, options));
|
|
6476
7302
|
}
|
|
6477
7303
|
//#endregion
|
|
6478
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
7304
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
|
|
6479
7305
|
function useUpdateEffect(effect, deps) {
|
|
6480
7306
|
const mountedRef = useRef$1(false);
|
|
6481
7307
|
useEffect$1(() => {
|
|
@@ -8045,7 +8871,7 @@ _createEmotion.css;
|
|
|
8045
8871
|
_createEmotion.sheet;
|
|
8046
8872
|
_createEmotion.cache;
|
|
8047
8873
|
//#endregion
|
|
8048
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
8874
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
|
|
8049
8875
|
var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
|
|
8050
8876
|
var useCx = () => {
|
|
8051
8877
|
const cache = __unsafe_useEmotionCache();
|
|
@@ -8152,7 +8978,7 @@ function memize(fn, options) {
|
|
|
8152
8978
|
return memoized;
|
|
8153
8979
|
}
|
|
8154
8980
|
//#endregion
|
|
8155
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
8981
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
|
|
8156
8982
|
var white = "#fff";
|
|
8157
8983
|
var GRAY = {
|
|
8158
8984
|
900: "#1e1e1e",
|
|
@@ -8231,7 +9057,7 @@ var COLORS = Object.freeze({
|
|
|
8231
9057
|
ui: UI
|
|
8232
9058
|
});
|
|
8233
9059
|
//#endregion
|
|
8234
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9060
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/config-values.mjs
|
|
8235
9061
|
var CONTROL_HEIGHT = "36px";
|
|
8236
9062
|
var CONTROL_PROPS = {
|
|
8237
9063
|
controlPaddingX: 12,
|
|
@@ -8246,9 +9072,6 @@ var CONTROL_PROPS = {
|
|
|
8246
9072
|
};
|
|
8247
9073
|
var config_values_default = Object.assign({}, CONTROL_PROPS, {
|
|
8248
9074
|
colorDivider: "rgba(0, 0, 0, 0.1)",
|
|
8249
|
-
colorScrollbarThumb: "rgba(0, 0, 0, 0.2)",
|
|
8250
|
-
colorScrollbarThumbHover: "rgba(0, 0, 0, 0.5)",
|
|
8251
|
-
colorScrollbarTrack: "rgba(0, 0, 0, 0.04)",
|
|
8252
9075
|
elevationIntensity: 1,
|
|
8253
9076
|
radiusXSmall: "1px",
|
|
8254
9077
|
radiusSmall: "2px",
|
|
@@ -8259,7 +9082,6 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
|
|
|
8259
9082
|
borderWidth: "1px",
|
|
8260
9083
|
borderWidthFocus: "1.5px",
|
|
8261
9084
|
borderWidthTab: "4px",
|
|
8262
|
-
spinnerSize: 16,
|
|
8263
9085
|
fontSize: "13px",
|
|
8264
9086
|
fontSizeH1: "calc(2.44 * 13px)",
|
|
8265
9087
|
fontSizeH2: "calc(1.95 * 13px)",
|
|
@@ -8336,10 +9158,10 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
8336
9158
|
};
|
|
8337
9159
|
}));
|
|
8338
9160
|
//#endregion
|
|
8339
|
-
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.
|
|
9161
|
+
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
|
|
8340
9162
|
var logged = /* @__PURE__ */ new Set();
|
|
8341
9163
|
//#endregion
|
|
8342
|
-
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.
|
|
9164
|
+
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
|
|
8343
9165
|
function isDev() {
|
|
8344
9166
|
return globalThis.SCRIPT_DEBUG === true;
|
|
8345
9167
|
}
|
|
@@ -8353,7 +9175,7 @@ function warning(message) {
|
|
|
8353
9175
|
logged.add(message);
|
|
8354
9176
|
}
|
|
8355
9177
|
//#endregion
|
|
8356
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9178
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
|
|
8357
9179
|
var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
|
|
8358
9180
|
var ComponentsContext = createContext$1(
|
|
8359
9181
|
/** @type {Record<string, any>} */
|
|
@@ -8380,18 +9202,18 @@ var BaseContextSystemProvider = ({ children, value }) => {
|
|
|
8380
9202
|
};
|
|
8381
9203
|
memo(BaseContextSystemProvider);
|
|
8382
9204
|
//#endregion
|
|
8383
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9205
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/constants.mjs
|
|
8384
9206
|
var COMPONENT_NAMESPACE = "data-wp-component";
|
|
8385
9207
|
var CONNECTED_NAMESPACE = "data-wp-c16t";
|
|
8386
9208
|
var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
|
|
8387
9209
|
//#endregion
|
|
8388
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9210
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
|
|
8389
9211
|
function getStyledClassName(namespace) {
|
|
8390
9212
|
return `components-${paramCase(namespace)}`;
|
|
8391
9213
|
}
|
|
8392
9214
|
var getStyledClassNameFromKey = memize(getStyledClassName);
|
|
8393
9215
|
//#endregion
|
|
8394
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9216
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-connect.mjs
|
|
8395
9217
|
function contextConnect(Component, namespace) {
|
|
8396
9218
|
return _contextConnect(Component, namespace, { forwardsRef: true });
|
|
8397
9219
|
}
|
|
@@ -8408,7 +9230,7 @@ function _contextConnect(Component, namespace, options) {
|
|
|
8408
9230
|
});
|
|
8409
9231
|
}
|
|
8410
9232
|
//#endregion
|
|
8411
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9233
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/utils.mjs
|
|
8412
9234
|
function getNamespace(componentName) {
|
|
8413
9235
|
return { [COMPONENT_NAMESPACE]: componentName };
|
|
8414
9236
|
}
|
|
@@ -8416,7 +9238,7 @@ function getConnectedNamespace() {
|
|
|
8416
9238
|
return { [CONNECTED_NAMESPACE]: true };
|
|
8417
9239
|
}
|
|
8418
9240
|
//#endregion
|
|
8419
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9241
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
|
|
8420
9242
|
function useContextSystem(props, namespace) {
|
|
8421
9243
|
const contextSystemProps = useComponentsContext();
|
|
8422
9244
|
if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
|
|
@@ -8436,7 +9258,7 @@ function useContextSystem(props, namespace) {
|
|
|
8436
9258
|
return finalComponentProps;
|
|
8437
9259
|
}
|
|
8438
9260
|
//#endregion
|
|
8439
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9261
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
|
|
8440
9262
|
var visuallyHidden = {
|
|
8441
9263
|
border: 0,
|
|
8442
9264
|
clip: "rect(1px, 1px, 1px, 1px)",
|
|
@@ -8452,7 +9274,7 @@ var visuallyHidden = {
|
|
|
8452
9274
|
wordBreak: "normal"
|
|
8453
9275
|
};
|
|
8454
9276
|
//#endregion
|
|
8455
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9277
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
|
|
8456
9278
|
var customAttributeRegExp = /^(data|aria|x)-/i;
|
|
8457
9279
|
var eventHandlerRegExp = /^on[A-Z]/;
|
|
8458
9280
|
var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
|
|
@@ -8542,7 +9364,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
|
|
|
8542
9364
|
}
|
|
8543
9365
|
var PolymorphicElement = forwardRef$1(UnforwardedPolymorphicElement);
|
|
8544
9366
|
//#endregion
|
|
8545
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9367
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/view/component.mjs
|
|
8546
9368
|
function UnforwardedView({ css, ...restProps }, ref) {
|
|
8547
9369
|
return /* @__PURE__ */ jsx(PolymorphicElement, {
|
|
8548
9370
|
ref,
|
|
@@ -8551,7 +9373,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
|
|
|
8551
9373
|
}
|
|
8552
9374
|
var component_default$2 = Object.assign(forwardRef$1(UnforwardedView), { selector: ".components-view" });
|
|
8553
9375
|
//#endregion
|
|
8554
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9376
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
|
|
8555
9377
|
function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
8556
9378
|
const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
|
|
8557
9379
|
return /* @__PURE__ */ jsx(component_default$2, {
|
|
@@ -8566,7 +9388,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
|
8566
9388
|
}
|
|
8567
9389
|
var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
|
|
8568
9390
|
//#endregion
|
|
8569
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9391
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
|
|
8570
9392
|
var breakpoints = [
|
|
8571
9393
|
"40em",
|
|
8572
9394
|
"52em",
|
|
@@ -8600,7 +9422,7 @@ function useResponsiveValue(values, options = {}) {
|
|
|
8600
9422
|
return array[index >= array.length ? array.length - 1 : index];
|
|
8601
9423
|
}
|
|
8602
9424
|
//#endregion
|
|
8603
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9425
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/utils.mjs
|
|
8604
9426
|
var ALIGNMENTS = {
|
|
8605
9427
|
bottom: {
|
|
8606
9428
|
alignItems: "flex-end",
|
|
@@ -8648,7 +9470,7 @@ function getAlignmentProps(alignment) {
|
|
|
8648
9470
|
return alignment ? ALIGNMENTS[alignment] : {};
|
|
8649
9471
|
}
|
|
8650
9472
|
//#endregion
|
|
8651
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9473
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/hook.mjs
|
|
8652
9474
|
function useGrid(props) {
|
|
8653
9475
|
const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
|
|
8654
9476
|
const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
|
|
@@ -8669,7 +9491,7 @@ function useGrid(props) {
|
|
|
8669
9491
|
justifyContent: justify,
|
|
8670
9492
|
verticalAlign: isInline ? "middle" : void 0,
|
|
8671
9493
|
...alignmentProps
|
|
8672
|
-
}, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,
|
|
9494
|
+
}, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNENzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5pbXBvcnQgdHlwZSB7IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyB1c2VDb250ZXh0U3lzdGVtIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyBnZXRBbGlnbm1lbnRQcm9wcyB9IGZyb20gJy4vdXRpbHMnO1xuaW1wb3J0IHsgdXNlUmVzcG9uc2l2ZVZhbHVlIH0gZnJvbSAnLi4vdXRpbHMvdXNlLXJlc3BvbnNpdmUtdmFsdWUnO1xuaW1wb3J0IENPTkZJRyBmcm9tICcuLi91dGlscy9jb25maWctdmFsdWVzJztcbmltcG9ydCB7IHVzZUN4IH0gZnJvbSAnLi4vdXRpbHMvaG9va3MvdXNlLWN4JztcbmltcG9ydCB0eXBlIHsgR3JpZFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIHVzZUdyaWQoXG5cdHByb3BzOiBXb3JkUHJlc3NDb21wb25lbnRQcm9wczwgR3JpZFByb3BzLCAnZGl2JyA+XG4pIHtcblx0Y29uc3Qge1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGNvbHVtbnMgPSAyLFxuXHRcdGdhcCA9IDMsXG5cdFx0aXNJbmxpbmUgPSBmYWxzZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XHRyb3dzLFxuXHRcdHRlbXBsYXRlQ29sdW1ucyxcblx0XHR0ZW1wbGF0ZVJvd3MsXG5cdFx0Li4ub3RoZXJQcm9wc1xuXHR9ID0gdXNlQ29udGV4dFN5c3RlbSggcHJvcHMsICdHcmlkJyApO1xuXG5cdGNvbnN0IGNvbHVtbnNBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggY29sdW1ucyApID8gY29sdW1ucyA6IFsgY29sdW1ucyBdO1xuXHRjb25zdCBjb2x1bW4gPSB1c2VSZXNwb25zaXZlVmFsdWUoIGNvbHVtbnNBc0FycmF5ICk7XG5cdGNvbnN0IHJvd3NBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggcm93cyApID8gcm93cyA6IFsgcm93cyBdO1xuXHRjb25zdCByb3cgPSB1c2VSZXNwb25zaXZlVmFsdWUoIHJvd3NBc0FycmF5ICk7XG5cblx0Y29uc3QgZ3JpZFRlbXBsYXRlQ29sdW1ucyA9XG5cdFx0dGVtcGxhdGVDb2x1bW5zIHx8ICggISEgY29sdW1ucyAmJiBgcmVwZWF0KCAkeyBjb2x1bW4gfSwgMWZyIClgICk7XG5cdGNvbnN0IGdyaWRUZW1wbGF0ZVJvd3MgPVxuXHRcdHRlbXBsYXRlUm93cyB8fCAoICEhIHJvd3MgJiYgYHJlcGVhdCggJHsgcm93IH0sIDFmciApYCApO1xuXG5cdGNvbnN0IGN4ID0gdXNlQ3goKTtcblxuXHRjb25zdCBjbGFzc2VzID0gdXNlTWVtbyggKCkgPT4ge1xuXHRcdGNvbnN0IGFsaWdubWVudFByb3BzID0gZ2V0QWxpZ25tZW50UHJvcHMoIGFsaWdubWVudCApO1xuXG5cdFx0Y29uc3QgZ3JpZENsYXNzZXMgPSBjc3MoIHtcblx0XHRcdGFsaWduSXRlbXM6IGFsaWduLFxuXHRcdFx0ZGlzcGxheTogaXNJbmxpbmUgPyAnaW5saW5lLWdyaWQnIDogJ2dyaWQnLFxuXHRcdFx0Z2FwOiBgY2FsYyggJHsgQ09ORklHLmdyaWRCYXNlIH0gKiAkeyBnYXAgfSApYCxcblx0XHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnM6IGdyaWRUZW1wbGF0ZUNvbHVtbnMgfHwgdW5kZWZpbmVkLFxuXHRcdFx0Z3JpZFRlbXBsYXRlUm93czogZ3JpZFRlbXBsYXRlUm93cyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkUm93R2FwOiByb3dHYXAsXG5cdFx0XHRncmlkQ29sdW1uR2FwOiBjb2x1bW5HYXAsXG5cdFx0XHRqdXN0aWZ5Q29udGVudDoganVzdGlmeSxcblx0XHRcdHZlcnRpY2FsQWxpZ246IGlzSW5saW5lID8gJ21pZGRsZScgOiB1bmRlZmluZWQsXG5cdFx0XHQuLi5hbGlnbm1lbnRQcm9wcyxcblx0XHR9ICk7XG5cblx0XHRyZXR1cm4gY3goIGdyaWRDbGFzc2VzLCBjbGFzc05hbWUgKTtcblx0fSwgW1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGN4LFxuXHRcdGdhcCxcblx0XHRncmlkVGVtcGxhdGVDb2x1bW5zLFxuXHRcdGdyaWRUZW1wbGF0ZVJvd3MsXG5cdFx0aXNJbmxpbmUsXG5cdFx0anVzdGlmeSxcblx0XHRyb3dHYXAsXG5cdF0gKTtcblxuXHRyZXR1cm4geyAuLi5vdGhlclByb3BzLCBjbGFzc05hbWU6IGNsYXNzZXMgfTtcbn1cbiJdfQ== */");
|
|
8673
9495
|
return cx(gridClasses, className);
|
|
8674
9496
|
}, [
|
|
8675
9497
|
align,
|
|
@@ -8690,7 +9512,7 @@ function useGrid(props) {
|
|
|
8690
9512
|
};
|
|
8691
9513
|
}
|
|
8692
9514
|
//#endregion
|
|
8693
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@
|
|
9515
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/component.mjs
|
|
8694
9516
|
function UnconnectedGrid(props, forwardedRef) {
|
|
8695
9517
|
const gridProps = useGrid(props);
|
|
8696
9518
|
return /* @__PURE__ */ jsx(component_default$2, {
|
|
@@ -8713,8 +9535,8 @@ const SUBPIXEL_TOLERANCE = .5;
|
|
|
8713
9535
|
* Counts how many leading rows fit inside the content container.
|
|
8714
9536
|
*
|
|
8715
9537
|
* Rows are read from the DOM rather than derived from constants: row height
|
|
8716
|
-
* depends on
|
|
8717
|
-
* as a label, so any restated number would drift.
|
|
9538
|
+
* depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
|
|
9539
|
+
* whatever a caller renders as a label, so any restated number would drift.
|
|
8718
9540
|
*
|
|
8719
9541
|
* Every row is a direct grid child marked with `data-row-index`; interactive rows
|
|
8720
9542
|
* use a button wrapper and non-interactive rows use a div wrapper.
|
|
@@ -8799,14 +9621,13 @@ function useFittedRowCount(enabled, rowCount, data) {
|
|
|
8799
9621
|
* @return Array of legend items for the leaderboard chart
|
|
8800
9622
|
*/
|
|
8801
9623
|
function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withComparison = false, withOverlayLabel = false, legendLabels }) {
|
|
8802
|
-
const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
|
|
8803
9624
|
const { getElementStyles } = useGlobalChartsContext();
|
|
8804
9625
|
return useMemo(() => {
|
|
8805
9626
|
if (!data || data.length === 0) return [];
|
|
8806
9627
|
const items = [];
|
|
8807
9628
|
const { color: resolvedPrimaryColor } = getElementStyles({
|
|
8808
9629
|
index: 0,
|
|
8809
|
-
overrideColor: primaryColor
|
|
9630
|
+
overrideColor: primaryColor
|
|
8810
9631
|
});
|
|
8811
9632
|
items.push({
|
|
8812
9633
|
label: legendLabels?.primary || __("Current period", "jetpack-charts"),
|
|
@@ -8815,7 +9636,7 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
|
|
|
8815
9636
|
if (withComparison && !withOverlayLabel) {
|
|
8816
9637
|
const { color: resolvedSecondaryColor } = getElementStyles({
|
|
8817
9638
|
index: 1,
|
|
8818
|
-
overrideColor: secondaryColor
|
|
9639
|
+
overrideColor: secondaryColor
|
|
8819
9640
|
});
|
|
8820
9641
|
items.push({
|
|
8821
9642
|
label: legendLabels?.comparison || __("Previous period", "jetpack-charts"),
|
|
@@ -8829,7 +9650,6 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
|
|
|
8829
9650
|
secondaryColor,
|
|
8830
9651
|
withComparison,
|
|
8831
9652
|
legendLabels,
|
|
8832
|
-
leaderboardChartSettings,
|
|
8833
9653
|
getElementStyles,
|
|
8834
9654
|
withOverlayLabel
|
|
8835
9655
|
]);
|
|
@@ -8843,6 +9663,9 @@ var leaderboard_chart_module_default = {
|
|
|
8843
9663
|
"chevron": "a8ccharts-GovfoW-chevron",
|
|
8844
9664
|
"deltaPlaceholder": "a8ccharts-GovfoW-deltaPlaceholder",
|
|
8845
9665
|
"deltaValue": "a8ccharts-GovfoW-deltaValue",
|
|
9666
|
+
"deltaValue--down": "a8ccharts-GovfoW-deltaValue--down",
|
|
9667
|
+
"deltaValue--neutral": "a8ccharts-GovfoW-deltaValue--neutral",
|
|
9668
|
+
"deltaValue--up": "a8ccharts-GovfoW-deltaValue--up",
|
|
8846
9669
|
"emptyState": "a8ccharts-GovfoW-emptyState",
|
|
8847
9670
|
"fitEmptyState": "a8ccharts-GovfoW-fitEmptyState",
|
|
8848
9671
|
"interactiveRow": "a8ccharts-GovfoW-interactiveRow",
|
|
@@ -8861,6 +9684,14 @@ var leaderboard_chart_module_default = {
|
|
|
8861
9684
|
};
|
|
8862
9685
|
//#endregion
|
|
8863
9686
|
//#region src/charts/leaderboard-chart/leaderboard-chart.tsx
|
|
9687
|
+
const ROW_GAP = "var(--a8c-charts-dimension-leaderboard-row-gap, 12px)";
|
|
9688
|
+
const COLUMN_GAP = "var(--a8c-charts-dimension-leaderboard-column-gap, 4px)";
|
|
9689
|
+
/** Indexed by `Math.sign( delta ) + 1`, so [negative, neutral, positive]. */
|
|
9690
|
+
const DELTA_TREND_CLASS = [
|
|
9691
|
+
"deltaValue--down",
|
|
9692
|
+
"deltaValue--neutral",
|
|
9693
|
+
"deltaValue--up"
|
|
9694
|
+
];
|
|
8864
9695
|
/**
|
|
8865
9696
|
* Default value formatter using formatMetricValue
|
|
8866
9697
|
*
|
|
@@ -8963,15 +9794,15 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
8963
9794
|
...legend.shapeStyles
|
|
8964
9795
|
};
|
|
8965
9796
|
const { legendChildren, nonLegendChildren } = useChartChildren(children, "LeaderboardChart");
|
|
8966
|
-
const { labelSpacing
|
|
9797
|
+
const { labelSpacing } = leaderboardChartSettings;
|
|
8967
9798
|
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
8968
9799
|
const { color: resolvedPrimaryColor } = getElementStyles({
|
|
8969
9800
|
index: 0,
|
|
8970
|
-
overrideColor: primaryColor
|
|
9801
|
+
overrideColor: primaryColor
|
|
8971
9802
|
});
|
|
8972
9803
|
const { color: resolvedSecondaryColor } = getElementStyles({
|
|
8973
9804
|
index: 1,
|
|
8974
|
-
overrideColor: secondaryColor
|
|
9805
|
+
overrideColor: secondaryColor
|
|
8975
9806
|
});
|
|
8976
9807
|
const legendItems = useLeaderboardLegendItems({
|
|
8977
9808
|
data: data || [],
|
|
@@ -9027,7 +9858,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
9027
9858
|
legendPosition,
|
|
9028
9859
|
legendElement: false,
|
|
9029
9860
|
legendChildren,
|
|
9030
|
-
className: clsx(leaderboard_chart_module_default.leaderboardChart, {
|
|
9861
|
+
className: clsx("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
|
|
9031
9862
|
[leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
|
|
9032
9863
|
[leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
|
|
9033
9864
|
}, className),
|
|
@@ -9062,7 +9893,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
9062
9893
|
legendPosition,
|
|
9063
9894
|
legendElement,
|
|
9064
9895
|
legendChildren,
|
|
9065
|
-
className: clsx(leaderboard_chart_module_default.leaderboardChart, {
|
|
9896
|
+
className: clsx("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
|
|
9066
9897
|
[leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
|
|
9067
9898
|
[leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
|
|
9068
9899
|
}, className),
|
|
@@ -9084,8 +9915,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
9084
9915
|
children: getAllHiddenMessage(legendInteractive, "series")
|
|
9085
9916
|
}) : /* @__PURE__ */ jsx(component_default, {
|
|
9086
9917
|
templateColumns: "minmax(0, 1fr) auto",
|
|
9087
|
-
rowGap,
|
|
9088
|
-
columnGap,
|
|
9918
|
+
rowGap: ROW_GAP,
|
|
9919
|
+
columnGap: COLUMN_GAP,
|
|
9089
9920
|
"data-leaderboard-grid": true,
|
|
9090
9921
|
children: data.map((entry, rowIndex) => {
|
|
9091
9922
|
const rowStyle = shouldFitRows && rowIndex >= fittedCount ? { visibility: "hidden" } : void 0;
|
|
@@ -9094,8 +9925,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
9094
9925
|
const hasDelta = hasDeltaValue(entry);
|
|
9095
9926
|
const showComparisonValue = showComparisonColumn && hasDelta;
|
|
9096
9927
|
const showComparisonPlaceholder = showComparisonColumn && !hasDelta;
|
|
9097
|
-
const
|
|
9098
|
-
const deltaColor = deltaColors[colorIndex];
|
|
9928
|
+
const deltaTrendClass = leaderboard_chart_module_default[DELTA_TREND_CLASS[showComparisonValue ? Math.sign(entry.delta) + 1 : 1]];
|
|
9099
9929
|
const rowCells = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Stack, {
|
|
9100
9930
|
direction: "column",
|
|
9101
9931
|
gap: labelSpacing,
|
|
@@ -9116,13 +9946,11 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
|
|
|
9116
9946
|
children: [
|
|
9117
9947
|
isPrimaryVisible && /* @__PURE__ */ jsx(Text$1, { children: valueFormatter(entry.currentValue) }),
|
|
9118
9948
|
showComparisonValue && /* @__PURE__ */ jsx(Text$1, {
|
|
9119
|
-
className: leaderboard_chart_module_default.deltaValue,
|
|
9120
|
-
style: { color: deltaColor },
|
|
9949
|
+
className: clsx(leaderboard_chart_module_default.deltaValue, deltaTrendClass),
|
|
9121
9950
|
children: deltaFormatter(entry.delta)
|
|
9122
9951
|
}),
|
|
9123
9952
|
showComparisonPlaceholder && /* @__PURE__ */ jsxs(Text$1, {
|
|
9124
|
-
className: clsx(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder),
|
|
9125
|
-
style: { color: deltaColor },
|
|
9953
|
+
className: clsx(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder, deltaTrendClass),
|
|
9126
9954
|
children: [/* @__PURE__ */ jsx("span", {
|
|
9127
9955
|
"aria-hidden": "true",
|
|
9128
9956
|
children: "—"
|
|
@@ -9206,7 +10034,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
|
|
|
9206
10034
|
//#endregion
|
|
9207
10035
|
//#region src/charts/pie-chart/pie-chart.module.scss
|
|
9208
10036
|
var pie_chart_module_default = {
|
|
9209
|
-
"pie-
|
|
10037
|
+
"pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
|
|
10038
|
+
"pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
|
|
10039
|
+
"pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
|
|
9210
10040
|
"pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
|
|
9211
10041
|
};
|
|
9212
10042
|
//#endregion
|
|
@@ -9261,11 +10091,8 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9261
10091
|
const providerTheme = useGlobalChartsTheme();
|
|
9262
10092
|
const chartId = useChartId(providedChartId);
|
|
9263
10093
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
|
|
9264
|
-
const
|
|
9265
|
-
|
|
9266
|
-
scroll: true,
|
|
9267
|
-
debounce: 0
|
|
9268
|
-
});
|
|
10094
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
10095
|
+
const containerRef = useRef(null);
|
|
9269
10096
|
const onMouseLeave = useCallback(() => {
|
|
9270
10097
|
if (!withTooltips) return;
|
|
9271
10098
|
hideTooltip();
|
|
@@ -9348,19 +10175,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9348
10175
|
width: propWidth || void 0,
|
|
9349
10176
|
height: propHeight || void 0
|
|
9350
10177
|
},
|
|
9351
|
-
trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
9352
|
-
withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(TooltipInPortal, {
|
|
9353
|
-
top: tooltipTop || 0,
|
|
9354
|
-
left: tooltipLeft || 0,
|
|
9355
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
9356
|
-
className: "a8c-charts-scope",
|
|
9357
|
-
role: "tooltip",
|
|
9358
|
-
children: renderTooltip({ tooltipData })
|
|
9359
|
-
})
|
|
9360
|
-
}),
|
|
9361
|
-
htmlChildren,
|
|
9362
|
-
otherChildren
|
|
9363
|
-
] }),
|
|
10178
|
+
trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [htmlChildren, otherChildren] }),
|
|
9364
10179
|
children: ({ contentWidth, contentHeight }) => {
|
|
9365
10180
|
const availableSize = Math.min(contentWidth > 0 ? contentWidth : 300, contentHeight > 0 ? contentHeight : 300);
|
|
9366
10181
|
const actualSize = size ? Math.min(size, availableSize) : availableSize;
|
|
@@ -9373,9 +10188,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9373
10188
|
const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
|
|
9374
10189
|
const maxCornerRadius = (outerRadius - innerRadius) / 2;
|
|
9375
10190
|
const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
|
|
9376
|
-
return /* @__PURE__ */
|
|
10191
|
+
return /* @__PURE__ */ jsxs(Center, {
|
|
9377
10192
|
ref: containerRef,
|
|
9378
|
-
|
|
10193
|
+
className: pie_chart_module_default["pie-chart__plot"],
|
|
10194
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
9379
10195
|
viewBox: `0 0 ${width} ${height}`,
|
|
9380
10196
|
preserveAspectRatio: "xMidYMid meet",
|
|
9381
10197
|
width,
|
|
@@ -9407,11 +10223,12 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9407
10223
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
|
|
9408
10224
|
const handleMouseMove = (event) => {
|
|
9409
10225
|
if (!withTooltips) return;
|
|
9410
|
-
|
|
10226
|
+
const bounds = containerRef.current?.getBoundingClientRect();
|
|
10227
|
+
if (!bounds) return;
|
|
9411
10228
|
showTooltip({
|
|
9412
10229
|
tooltipData: arc.data,
|
|
9413
|
-
tooltipLeft: event.clientX -
|
|
9414
|
-
tooltipTop: event.clientY -
|
|
10230
|
+
tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
|
|
10231
|
+
tooltipTop: event.clientY - bounds.top + tooltipOffsetY
|
|
9415
10232
|
});
|
|
9416
10233
|
};
|
|
9417
10234
|
const pathProps = {
|
|
@@ -9434,20 +10251,20 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9434
10251
|
const backgroundHeight = fontSize + 12;
|
|
9435
10252
|
return /* @__PURE__ */ jsxs("g", {
|
|
9436
10253
|
...groupProps,
|
|
9437
|
-
children: [/* @__PURE__ */ jsx("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ jsxs("g", { children: [
|
|
10254
|
+
children: [/* @__PURE__ */ jsx("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
|
|
10255
|
+
className: pie_chart_module_default["pie-chart__label-plate"],
|
|
9438
10256
|
x: centroidX - backgroundWidth / 2,
|
|
9439
10257
|
y: centroidY - backgroundHeight / 2,
|
|
9440
10258
|
width: backgroundWidth,
|
|
9441
10259
|
height: backgroundHeight,
|
|
9442
|
-
fill: providerTheme.labelBackgroundColor,
|
|
9443
10260
|
rx: 4,
|
|
9444
10261
|
ry: 4,
|
|
9445
10262
|
pointerEvents: "none"
|
|
9446
10263
|
}), /* @__PURE__ */ jsx("text", {
|
|
10264
|
+
className: pie_chart_module_default["pie-chart__label-text"],
|
|
9447
10265
|
x: centroidX,
|
|
9448
10266
|
y: centroidY,
|
|
9449
10267
|
dy: ".33em",
|
|
9450
|
-
fill: providerTheme.labelTextColor || "#333",
|
|
9451
10268
|
fontSize,
|
|
9452
10269
|
textAnchor: "middle",
|
|
9453
10270
|
pointerEvents: "none",
|
|
@@ -9458,7 +10275,15 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
9458
10275
|
}
|
|
9459
10276
|
}), !allSegmentsHidden && svgChildren]
|
|
9460
10277
|
})]
|
|
9461
|
-
})
|
|
10278
|
+
}), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
|
|
10279
|
+
top: tooltipTop || 0,
|
|
10280
|
+
left: tooltipLeft || 0,
|
|
10281
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10282
|
+
className: standaloneScopeClass,
|
|
10283
|
+
role: "tooltip",
|
|
10284
|
+
children: renderTooltip({ tooltipData })
|
|
10285
|
+
})
|
|
10286
|
+
})]
|
|
9462
10287
|
});
|
|
9463
10288
|
}
|
|
9464
10289
|
})
|
|
@@ -9485,6 +10310,7 @@ var pie_semi_circle_chart_module_default = {
|
|
|
9485
10310
|
"label": "a8ccharts-YtTOxW-label",
|
|
9486
10311
|
"note": "a8ccharts-YtTOxW-note",
|
|
9487
10312
|
"pie-semi-circle-chart": "a8ccharts-YtTOxW-pie-semi-circle-chart",
|
|
10313
|
+
"pie-semi-circle-chart__plot": "a8ccharts-YtTOxW-pie-semi-circle-chart__plot",
|
|
9488
10314
|
"pie-semi-circle-chart--responsive": "a8ccharts-YtTOxW-pie-semi-circle-chart--responsive"
|
|
9489
10315
|
};
|
|
9490
10316
|
//#endregion
|
|
@@ -9534,23 +10360,17 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9534
10360
|
const legendPosition = legend.position ?? "bottom";
|
|
9535
10361
|
const chartId = useChartId(providedChartId);
|
|
9536
10362
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
|
|
9537
|
-
const
|
|
9538
|
-
|
|
9539
|
-
scroll: true,
|
|
9540
|
-
debounce: 0
|
|
9541
|
-
});
|
|
10363
|
+
const standaloneScopeClass = useStandaloneScopeClass();
|
|
10364
|
+
const containerRef = useRef(null);
|
|
9542
10365
|
const handleMouseMove = useCallback((event, arc) => {
|
|
9543
|
-
|
|
10366
|
+
const bounds = containerRef.current?.getBoundingClientRect();
|
|
10367
|
+
if (!bounds) return;
|
|
9544
10368
|
showTooltip({
|
|
9545
10369
|
tooltipData: arc.data,
|
|
9546
|
-
tooltipLeft: event.clientX -
|
|
9547
|
-
tooltipTop: event.clientY -
|
|
10370
|
+
tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
|
|
10371
|
+
tooltipTop: event.clientY - bounds.top + tooltipOffsetY
|
|
9548
10372
|
});
|
|
9549
10373
|
}, [
|
|
9550
|
-
containerBounds.width,
|
|
9551
|
-
containerBounds.height,
|
|
9552
|
-
containerBounds.left,
|
|
9553
|
-
containerBounds.top,
|
|
9554
10374
|
showTooltip,
|
|
9555
10375
|
tooltipOffsetX,
|
|
9556
10376
|
tooltipOffsetY
|
|
@@ -9599,7 +10419,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9599
10419
|
const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
|
|
9600
10420
|
const errorHeight = errorWidth / 2;
|
|
9601
10421
|
return /* @__PURE__ */ jsx("div", {
|
|
9602
|
-
className: pie_semi_circle_chart_module_default["pie-semi-circle-chart"],
|
|
10422
|
+
className: clsx("pie-semi-circle-chart", pie_semi_circle_chart_module_default["pie-semi-circle-chart"], className),
|
|
9603
10423
|
children: /* @__PURE__ */ jsx("svg", {
|
|
9604
10424
|
width: errorWidth,
|
|
9605
10425
|
height: errorHeight,
|
|
@@ -9646,19 +10466,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9646
10466
|
width: propWidth || void 0,
|
|
9647
10467
|
height: propHeight || void 0
|
|
9648
10468
|
},
|
|
9649
|
-
trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
9650
|
-
withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(TooltipInPortal, {
|
|
9651
|
-
top: tooltipTop || 0,
|
|
9652
|
-
left: tooltipLeft || 0,
|
|
9653
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
9654
|
-
className: "a8c-charts-scope",
|
|
9655
|
-
role: "tooltip",
|
|
9656
|
-
children: renderTooltip({ tooltipData })
|
|
9657
|
-
})
|
|
9658
|
-
}),
|
|
9659
|
-
htmlChildren,
|
|
9660
|
-
otherChildren
|
|
9661
|
-
] }),
|
|
10469
|
+
trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [htmlChildren, otherChildren] }),
|
|
9662
10470
|
children: ({ contentWidth, contentHeight }) => {
|
|
9663
10471
|
const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
|
|
9664
10472
|
const availableHeight = contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
|
|
@@ -9666,9 +10474,10 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9666
10474
|
const height = width / 2;
|
|
9667
10475
|
const radius = height;
|
|
9668
10476
|
const innerRadius = radius * (1 - thickness);
|
|
9669
|
-
return /* @__PURE__ */
|
|
10477
|
+
return /* @__PURE__ */ jsxs(Center, {
|
|
9670
10478
|
ref: containerRef,
|
|
9671
|
-
|
|
10479
|
+
className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
|
|
10480
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
9672
10481
|
width,
|
|
9673
10482
|
height,
|
|
9674
10483
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -9726,7 +10535,15 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
9726
10535
|
!allSegmentsHidden && svgChildren
|
|
9727
10536
|
] })
|
|
9728
10537
|
})]
|
|
9729
|
-
})
|
|
10538
|
+
}), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
|
|
10539
|
+
top: tooltipTop || 0,
|
|
10540
|
+
left: tooltipLeft || 0,
|
|
10541
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10542
|
+
className: standaloneScopeClass,
|
|
10543
|
+
role: "tooltip",
|
|
10544
|
+
children: renderTooltip({ tooltipData })
|
|
10545
|
+
})
|
|
10546
|
+
})]
|
|
9730
10547
|
});
|
|
9731
10548
|
}
|
|
9732
10549
|
})
|
|
@@ -9951,6 +10768,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
|
|
|
9951
10768
|
});
|
|
9952
10769
|
}
|
|
9953
10770
|
//#endregion
|
|
9954
|
-
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, GoogleDataTableColumnRoleType, 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,
|
|
10771
|
+
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, GoogleDataTableColumnRoleType, 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, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useCalendarHeatmapData, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
|
|
9955
10772
|
|
|
9956
10773
|
//# sourceMappingURL=index.js.map
|