@automattic/charts 4.0.0 → 4.1.1
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 +27 -0
- package/SECURITY.md +1 -0
- package/TOKENS.md +7 -5
- package/dist/index.cjs +614 -259
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +30 -0
- package/dist/index.d.cts +88 -74
- package/dist/index.d.ts +88 -74
- package/dist/index.js +615 -261
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/charts/area-chart/area-chart.tsx +2 -1
- package/src/charts/area-chart/test/area-chart.test.tsx +31 -0
- package/src/charts/heatmap-chart/build-calendar-data.ts +189 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +31 -0
- package/src/charts/heatmap-chart/heatmap-chart.tsx +44 -9
- package/src/charts/heatmap-chart/index.ts +10 -3
- package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
- package/src/charts/heatmap-chart/private/index.ts +0 -2
- package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +4 -1
- package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +91 -0
- 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 +35 -0
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
- package/src/charts/leaderboard-chart/types.ts +2 -2
- package/src/charts/line-chart/line-chart.tsx +41 -7
- package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +178 -38
- package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
- package/src/charts/line-chart/types.ts +13 -2
- package/src/charts/pie-chart/test/pie-chart.test.tsx +3 -12
- package/src/components/tooltip/accessible-tooltip.tsx +40 -12
- package/src/components/tooltip/private/bounded-tooltip.tsx +71 -16
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +60 -1
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +102 -6
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +122 -3
- package/src/components/tooltip/xy-chart-tooltip.tsx +53 -14
- 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/use-chart-data-transform.ts +10 -3
- package/src/hooks/use-chart-margin.tsx +54 -9
- package/src/index.ts +4 -0
- package/src/styles/chart-scope.scss +11 -3
- package/src/styles/test/chart-scope.test.ts +35 -1
- package/src/test-utils/zoned-parsing-suite.ts +90 -0
- package/src/types.ts +2 -2
- package/src/utils/date-formatting.ts +1 -18
- package/src/utils/date-parsing.ts +103 -52
- package/src/utils/get-edge-tick-widths.ts +33 -0
- package/src/utils/index.ts +1 -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/warn-once.ts +23 -0
- package/src/visx/types.ts +27 -3
- package/src/charts/heatmap-chart/private/build-calendar-data.ts +0 -179
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ let react = require("react");
|
|
|
31
31
|
react = __toESM(react);
|
|
32
32
|
let _visx_vendor_d3_color = require("@visx/vendor/d3-color");
|
|
33
33
|
let date_fns = require("date-fns");
|
|
34
|
+
let _date_fns_tz = require("@date-fns/tz");
|
|
34
35
|
let _visx_text = require("@visx/text");
|
|
35
36
|
let deepmerge = require("deepmerge");
|
|
36
37
|
deepmerge = __toESM(deepmerge, 1);
|
|
@@ -86,46 +87,35 @@ function attachSubComponents(Chart, subComponents) {
|
|
|
86
87
|
return Object.assign(Chart, subComponents);
|
|
87
88
|
}
|
|
88
89
|
//#endregion
|
|
89
|
-
//#region src/utils/
|
|
90
|
+
//#region src/utils/warn-once.ts
|
|
91
|
+
const warned = /* @__PURE__ */ new Set();
|
|
90
92
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* This module provides utilities for parsing various date string formats and converting
|
|
94
|
-
* them to local timezone dates using the battle-tested date-fns library. For formats
|
|
95
|
-
* without timezone info, they're treated as local. For formats with timezone info,
|
|
96
|
-
* they're converted to the equivalent local time.
|
|
93
|
+
* Warns about a host-supplied value once per process, and never in production.
|
|
97
94
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
* @param key - Identifies the value, so a bad prop on every point warns once.
|
|
96
|
+
* @param message - What the host got wrong and what happens instead.
|
|
97
|
+
*/
|
|
98
|
+
const warnOnce = (key, message) => {
|
|
99
|
+
if (warned.has(key) || process.env.NODE_ENV === "production") return;
|
|
100
|
+
warned.add(key);
|
|
101
|
+
console.warn(`[Charts] ${message}`);
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/utils/date-parsing.ts
|
|
105
|
+
/**
|
|
106
|
+
* @file Date parsing: a naive string is dated in a supplied IANA zone, or the runtime's own
|
|
106
107
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* - YYYY-MM-DD HH:mm (treated as local)
|
|
111
|
-
* - YYYY-MM-DDTHH:mm:ss (treated as local)
|
|
112
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
|
|
113
|
-
* - YYYY-MM-DDTHH:mm (treated as local)
|
|
114
|
-
* - YYYY-MM-DDTHH:mm:ssZ (converted to local)
|
|
115
|
-
* - YYYY-MM-DDTHH:mm:ss±HH:mm (converted to local)
|
|
108
|
+
* A string carrying an offset is already an instant and parses the same everywhere. A string
|
|
109
|
+
* without one is only a wall-clock reading, so it means nothing until a zone is named. See
|
|
110
|
+
* `parseAsLocalDate` for the supported formats.
|
|
116
111
|
*
|
|
117
|
-
*
|
|
118
|
-
* ```typescript
|
|
119
|
-
* parseAsLocalDate("2025-01-01"); // Local timezone
|
|
120
|
-
* parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
|
|
121
|
-
* parseAsLocalDate("2025-01-01 14:30"); // Local timezone
|
|
122
|
-
* parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
|
|
123
|
-
* parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
|
|
124
|
-
* parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
|
|
125
|
-
* ```
|
|
112
|
+
* Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
|
|
126
113
|
*/
|
|
127
114
|
/**
|
|
128
115
|
* Checks if a date string contains timezone information
|
|
116
|
+
*
|
|
117
|
+
* The minutes are optional because ISO 8601 allows a bare `±hh`, which `parseISO` reads.
|
|
118
|
+
*
|
|
129
119
|
* @param {string} dateString - The date string to check for timezone information
|
|
130
120
|
* @return {boolean} True if the date string contains timezone information, false otherwise
|
|
131
121
|
*/
|
|
@@ -133,28 +123,57 @@ const hasTimezone = (dateString) => {
|
|
|
133
123
|
const tIndex = dateString.indexOf("T");
|
|
134
124
|
if (tIndex === -1) return false;
|
|
135
125
|
if (dateString.endsWith("Z")) return true;
|
|
136
|
-
return /[+-]\d{2}
|
|
126
|
+
return /[+-]\d{2}(?::?\d{2})?$/.test(dateString.slice(tIndex + 1));
|
|
127
|
+
};
|
|
128
|
+
const NAIVE = /^(\d{1,4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?$/;
|
|
129
|
+
const asUtcMs = ([, ...fields]) => {
|
|
130
|
+
const [year, month, day, hour, minute, second, ms] = fields.map((field) => Number(field ?? 0));
|
|
131
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
132
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
133
|
+
date.setUTCHours(hour, minute, second, ms);
|
|
134
|
+
return date.getTime();
|
|
137
135
|
};
|
|
138
136
|
/**
|
|
139
|
-
*
|
|
137
|
+
* The instant a wall-clock reading names in a given zone.
|
|
140
138
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* local timezone equivalent.
|
|
139
|
+
* A reading a spring-forward gap deleted moves forward past the gap, and one a fall-back
|
|
140
|
+
* repeated resolves to a single instant. Neither is configurable: see CHARTS-268.
|
|
144
141
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* - YYYY-MM-DD HH:mm (local)
|
|
149
|
-
* - YYYY-MM-DDTHH:mm:ss (local)
|
|
150
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (local)
|
|
151
|
-
* - YYYY-MM-DDTHH:mm (local)
|
|
152
|
-
* - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
|
|
153
|
-
* - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
|
|
154
|
-
* @param {string} dateString - The date string to parse into a local timezone date
|
|
155
|
-
* @return {Date} A Date object representing the parsed date in local timezone, or an invalid Date if parsing fails
|
|
142
|
+
* @param wallClock - The reading, as the milliseconds it would be if it were UTC.
|
|
143
|
+
* @param timeZone - IANA zone the reading is dated in.
|
|
144
|
+
* @return The instant, or `null` where `timeZone` is not a zone Intl accepts.
|
|
156
145
|
*/
|
|
157
|
-
const
|
|
146
|
+
const wallClockToInstant = (wallClock, timeZone) => {
|
|
147
|
+
const first = (0, _date_fns_tz.tzOffset)(timeZone, new Date(wallClock));
|
|
148
|
+
if (Number.isNaN(first)) {
|
|
149
|
+
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".`);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const guess = wallClock - first * 6e4;
|
|
153
|
+
const second = (0, _date_fns_tz.tzOffset)(timeZone, new Date(guess));
|
|
154
|
+
if (first === second) return new Date(guess);
|
|
155
|
+
const corrected = wallClock - second * 6e4;
|
|
156
|
+
const third = (0, _date_fns_tz.tzOffset)(timeZone, new Date(corrected));
|
|
157
|
+
if (second === third) return new Date(corrected);
|
|
158
|
+
return /* @__PURE__ */ new Date(wallClock - Math.min(second, third) * 6e4);
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Parses any supported date string format into an instant, dated in `timeZone` or the runtime's own
|
|
162
|
+
*
|
|
163
|
+
* Uses date-fns for robust parsing and validation. A string carrying timezone info
|
|
164
|
+
* is already an instant and is returned as one, whatever `timeZone` says. A string
|
|
165
|
+
* without it is a wall-clock reading, dated in `timeZone` when one is supplied and
|
|
166
|
+
* in the runtime's own zone when none is.
|
|
167
|
+
*
|
|
168
|
+
* A wall clock a DST gap deleted is moved forward past the gap; one a fall-back repeated
|
|
169
|
+
* resolves to a single instant. Neither policy is selectable. A zone `Intl` rejects falls
|
|
170
|
+
* back to the runtime's own, and warns once outside production.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} dateString - The date string to parse into a date
|
|
173
|
+
* @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
|
|
174
|
+
* @return {Date} A Date object representing the parsed instant, or an invalid Date if parsing fails
|
|
175
|
+
*/
|
|
176
|
+
const parseAsLocalDate = (dateString, timeZone) => {
|
|
158
177
|
const trimmedString = dateString.trim();
|
|
159
178
|
if (hasTimezone(trimmedString)) {
|
|
160
179
|
const isoDate = (0, date_fns.parseISO)(trimmedString);
|
|
@@ -170,7 +189,10 @@ const parseAsLocalDate = (dateString) => {
|
|
|
170
189
|
"yyyy-MM-dd'T'HH:mm"
|
|
171
190
|
]) {
|
|
172
191
|
const result = (0, date_fns.parse)(trimmedString, format, /* @__PURE__ */ new Date());
|
|
173
|
-
if ((0, date_fns.isValid)(result))
|
|
192
|
+
if (!(0, date_fns.isValid)(result)) continue;
|
|
193
|
+
if (!timeZone) return result;
|
|
194
|
+
const fields = NAIVE.exec(trimmedString);
|
|
195
|
+
return (fields && wallClockToInstant(asUtcMs(fields), timeZone)) ?? result;
|
|
174
196
|
}
|
|
175
197
|
return /* @__PURE__ */ new Date(NaN);
|
|
176
198
|
};
|
|
@@ -275,6 +297,31 @@ const formatPercentage = (value) => {
|
|
|
275
297
|
} });
|
|
276
298
|
};
|
|
277
299
|
//#endregion
|
|
300
|
+
//#region src/utils/get-edge-tick-widths.ts
|
|
301
|
+
/**
|
|
302
|
+
* Rendered widths of the first and last tick labels on an axis.
|
|
303
|
+
*
|
|
304
|
+
* An unmeasurable label reserves nothing, which is what a width of 0 already
|
|
305
|
+
* means to every caller, so it is reported as 0 rather than as its own case.
|
|
306
|
+
*
|
|
307
|
+
* @param ticks - Tick values, in axis order.
|
|
308
|
+
* @param formatTick - Function to format a tick.
|
|
309
|
+
* @param {object} labelStyle - Style object for the label.
|
|
310
|
+
* @return {object} - Widths in pixels.
|
|
311
|
+
*/
|
|
312
|
+
const getEdgeTickWidths = (ticks, formatTick, labelStyle) => {
|
|
313
|
+
if (!ticks.length) return {
|
|
314
|
+
first: 0,
|
|
315
|
+
last: 0
|
|
316
|
+
};
|
|
317
|
+
const lastIndex = ticks.length - 1;
|
|
318
|
+
const label = (tick, index) => String(formatTick ? formatTick(tick, index, []) ?? "" : tick);
|
|
319
|
+
return {
|
|
320
|
+
first: (0, _visx_text.getStringWidth)(label(ticks[0], 0), labelStyle) ?? 0,
|
|
321
|
+
last: (0, _visx_text.getStringWidth)(label(ticks[lastIndex], lastIndex), labelStyle) ?? 0
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
//#endregion
|
|
278
325
|
//#region src/utils/get-longest-tick-width.ts
|
|
279
326
|
/**
|
|
280
327
|
* Returns the width of the longest tick.
|
|
@@ -661,12 +708,6 @@ const getFormatter$1 = (options, { locale, timeZone }) => {
|
|
|
661
708
|
}
|
|
662
709
|
return formatter;
|
|
663
710
|
};
|
|
664
|
-
const warned = /* @__PURE__ */ new Set();
|
|
665
|
-
const warnOnce = (key, message) => {
|
|
666
|
-
if (warned.has(key) || process.env.NODE_ENV === "production") return;
|
|
667
|
-
warned.add(key);
|
|
668
|
-
console.warn(`[Charts] ${message}`);
|
|
669
|
-
};
|
|
670
711
|
const isUsable = (formatting) => {
|
|
671
712
|
try {
|
|
672
713
|
Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
|
|
@@ -1292,13 +1333,28 @@ const useXYChartTheme = (data) => {
|
|
|
1292
1333
|
]);
|
|
1293
1334
|
};
|
|
1294
1335
|
//#endregion
|
|
1336
|
+
//#region src/providers/chart-context/hooks/use-chart-formatting.ts
|
|
1337
|
+
const RUNTIME_FORMATTING = {};
|
|
1338
|
+
/**
|
|
1339
|
+
* The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
|
|
1340
|
+
*
|
|
1341
|
+
* Falls back to the runtime's own rather than throwing: charts render outside a
|
|
1342
|
+
* provider, and a host that sets neither gets exactly the browser-default
|
|
1343
|
+
* behavior this package had before the context existed.
|
|
1344
|
+
*
|
|
1345
|
+
* @return The host's formatting context.
|
|
1346
|
+
*/
|
|
1347
|
+
const useChartFormatting = () => {
|
|
1348
|
+
return (0, react.useContext)(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
|
|
1349
|
+
};
|
|
1350
|
+
//#endregion
|
|
1295
1351
|
//#region src/hooks/use-chart-data-transform.ts
|
|
1296
1352
|
/**
|
|
1297
1353
|
* Hook that transforms and sorts chart data, handling date parsing and sorting
|
|
1298
1354
|
*
|
|
1299
1355
|
* This hook extracts the common data transformation logic used in both line-chart
|
|
1300
1356
|
* and bar-chart components. It:
|
|
1301
|
-
* 1. Parses date strings into Date objects
|
|
1357
|
+
* 1. Parses date strings into Date objects, dated in the provider's time zone
|
|
1302
1358
|
* 2. Sorts data points by date when date properties are present
|
|
1303
1359
|
* 3. Returns the original data unchanged when no date properties are found
|
|
1304
1360
|
*
|
|
@@ -1306,6 +1362,7 @@ const useXYChartTheme = (data) => {
|
|
|
1306
1362
|
* @return {SeriesData[]} The transformed and sorted data
|
|
1307
1363
|
*/
|
|
1308
1364
|
const useChartDataTransform = (data) => {
|
|
1365
|
+
const { timeZone } = useChartFormatting();
|
|
1309
1366
|
return (0, react.useMemo)(() => {
|
|
1310
1367
|
const firstPoint = data?.[0]?.data?.[0];
|
|
1311
1368
|
if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
|
|
@@ -1314,7 +1371,7 @@ const useChartDataTransform = (data) => {
|
|
|
1314
1371
|
data: series.data.map((point) => {
|
|
1315
1372
|
let date;
|
|
1316
1373
|
if ("date" in point && point.date) date = point.date;
|
|
1317
|
-
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
|
|
1374
|
+
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
|
|
1318
1375
|
return {
|
|
1319
1376
|
...point,
|
|
1320
1377
|
date
|
|
@@ -1324,7 +1381,7 @@ const useChartDataTransform = (data) => {
|
|
|
1324
1381
|
return a.date.getTime() - b.date.getTime();
|
|
1325
1382
|
})
|
|
1326
1383
|
}));
|
|
1327
|
-
}, [data]);
|
|
1384
|
+
}, [data, timeZone]);
|
|
1328
1385
|
};
|
|
1329
1386
|
//#endregion
|
|
1330
1387
|
//#region src/hooks/use-chart-margin.tsx
|
|
@@ -1366,11 +1423,32 @@ const DEFAULT_TICK_LENGTH = 8;
|
|
|
1366
1423
|
* measure them via getLongestTickWidth.
|
|
1367
1424
|
*/
|
|
1368
1425
|
const DEFAULT_Y_TICK_WIDTH = 40;
|
|
1426
|
+
/**
|
|
1427
|
+
* Copy a label style with its lengths spelled out in px.
|
|
1428
|
+
*
|
|
1429
|
+
* `getStringWidth` applies the style through CSSOM. Blink resolves a bare number
|
|
1430
|
+
* on an SVG `<text>`, but that is its own leniency rather than the CSS rule, and
|
|
1431
|
+
* `buildChartTheme` hands us bare numbers.
|
|
1432
|
+
*
|
|
1433
|
+
* @param style - Raw label style from the theme.
|
|
1434
|
+
* @return The same style with px-qualified lengths.
|
|
1435
|
+
*/
|
|
1436
|
+
const toMeasurableStyle = (style) => {
|
|
1437
|
+
if (!style) return style;
|
|
1438
|
+
const fontSize = resolveFontSize(style.fontSize);
|
|
1439
|
+
const { letterSpacing } = style;
|
|
1440
|
+
return {
|
|
1441
|
+
...style,
|
|
1442
|
+
...fontSize === void 0 ? {} : { fontSize: `${fontSize}px` },
|
|
1443
|
+
...typeof letterSpacing === "number" ? { letterSpacing: `${letterSpacing}px` } : {}
|
|
1444
|
+
};
|
|
1445
|
+
};
|
|
1369
1446
|
const getXAxisLabelMetrics = (theme, orientation) => {
|
|
1370
1447
|
const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
|
|
1371
1448
|
return {
|
|
1372
1449
|
fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
|
|
1373
|
-
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
|
|
1450
|
+
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
|
|
1451
|
+
tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
|
|
1374
1452
|
};
|
|
1375
1453
|
};
|
|
1376
1454
|
const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
@@ -1382,7 +1460,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1382
1460
|
const maxY = Math.max(...allDataPoints.map((d) => d.value));
|
|
1383
1461
|
const yScale = (0, _visx_scale.createScale)({
|
|
1384
1462
|
...options.yScale,
|
|
1385
|
-
domain: [minY, maxY],
|
|
1463
|
+
domain: options.yScale?.domain ?? [minY, maxY],
|
|
1386
1464
|
range: [height, 0]
|
|
1387
1465
|
});
|
|
1388
1466
|
return (0, _visx_scale.getTicks)(yScale, options.axis?.y?.numTicks);
|
|
@@ -1401,18 +1479,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1401
1479
|
};
|
|
1402
1480
|
const yAxisOrientation = options.axis?.y?.orientation;
|
|
1403
1481
|
const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
|
|
1404
|
-
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
|
|
1482
|
+
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
|
|
1405
1483
|
const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
|
|
1406
1484
|
const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1485
|
+
if (options.axis?.y?.display !== false) {
|
|
1486
|
+
if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
|
|
1487
|
+
else defaultMargin.left = yMarginValue;
|
|
1488
|
+
}
|
|
1409
1489
|
const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
|
|
1410
|
-
const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1490
|
+
const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1411
1491
|
const computedXMargin = fontSize + tickLength;
|
|
1412
1492
|
if (xOrientation === "top") {
|
|
1413
1493
|
defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
|
|
1414
1494
|
defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
|
|
1415
1495
|
} else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
|
|
1496
|
+
if (options.axis?.x?.display !== false) {
|
|
1497
|
+
const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
|
|
1498
|
+
defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
|
|
1499
|
+
defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
|
|
1500
|
+
}
|
|
1416
1501
|
return defaultMargin;
|
|
1417
1502
|
}, [
|
|
1418
1503
|
options,
|
|
@@ -1688,21 +1773,6 @@ const useGlobalChartsTheme = () => {
|
|
|
1688
1773
|
return (0, react.useContext)(GlobalChartsContext)?.theme ?? defaultTheme;
|
|
1689
1774
|
};
|
|
1690
1775
|
//#endregion
|
|
1691
|
-
//#region src/providers/chart-context/hooks/use-chart-formatting.ts
|
|
1692
|
-
const RUNTIME_FORMATTING = {};
|
|
1693
|
-
/**
|
|
1694
|
-
* The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
|
|
1695
|
-
*
|
|
1696
|
-
* Falls back to the runtime's own rather than throwing: charts render outside a
|
|
1697
|
-
* provider, and a host that sets neither gets exactly the browser-default
|
|
1698
|
-
* behavior this package had before the context existed.
|
|
1699
|
-
*
|
|
1700
|
-
* @return The host's formatting context.
|
|
1701
|
-
*/
|
|
1702
|
-
const useChartFormatting = () => {
|
|
1703
|
-
return (0, react.useContext)(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
|
|
1704
|
-
};
|
|
1705
|
-
//#endregion
|
|
1706
1776
|
//#region src/components/legend/utils/value-or-identity.ts
|
|
1707
1777
|
/**
|
|
1708
1778
|
* Returns an object's value if defined, or the object.
|
|
@@ -2142,6 +2212,8 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
|
|
|
2142
2212
|
//#endregion
|
|
2143
2213
|
//#region src/components/tooltip/private/bounded-tooltip.tsx
|
|
2144
2214
|
const DEFAULT_OFFSET = 10;
|
|
2215
|
+
const POINTER_HEIGHT = 6;
|
|
2216
|
+
const clamp = (position, min, max, size) => Math.min(Math.max(position, min), Math.max(min, max - size));
|
|
2145
2217
|
const isClipping = (element) => {
|
|
2146
2218
|
const { overflow, overflowX, overflowY } = getComputedStyle(element);
|
|
2147
2219
|
return [
|
|
@@ -2159,33 +2231,49 @@ const findClippingAncestor = (wrapper) => {
|
|
|
2159
2231
|
return null;
|
|
2160
2232
|
};
|
|
2161
2233
|
/**
|
|
2162
|
-
* Where the box goes, in wrapper coordinates.
|
|
2163
|
-
*
|
|
2164
|
-
*
|
|
2165
|
-
*
|
|
2234
|
+
* Where the box goes, in wrapper coordinates. Automatic placement flips to
|
|
2235
|
+
* the side that clips less, then clamps inside `bounds`; below-axis placement
|
|
2236
|
+
* is centered on the anchor and clamped horizontally.
|
|
2237
|
+
*
|
|
2238
|
+
* The flip measures against the visible part of the wrapper, its intersection
|
|
2239
|
+
* with `bounds`. Only the clamp is bound by the page: a box that fits inside the
|
|
2240
|
+
* chart on one side stays there, so it does not reach past the chart into
|
|
2241
|
+
* siblings that paint over it.
|
|
2166
2242
|
*
|
|
2167
|
-
* @param params - Anchor, offsets, the box size and the
|
|
2243
|
+
* @param params - Anchor, offsets, the box size and the rects to keep inside.
|
|
2168
2244
|
* @param params.left - Anchor x, in wrapper coordinates.
|
|
2169
2245
|
* @param params.top - Anchor y, in wrapper coordinates.
|
|
2170
2246
|
* @param params.offsetLeft - Gap between the anchor and the box, horizontally.
|
|
2171
2247
|
* @param params.offsetTop - Gap between the anchor and the box, vertically.
|
|
2172
2248
|
* @param params.box - Rendered size of the box.
|
|
2249
|
+
* @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
|
|
2250
|
+
* @param params.placement - Fixed below-axis placement or automatic flipping.
|
|
2173
2251
|
* @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
|
|
2174
|
-
* @return The box's top-left corner
|
|
2252
|
+
* @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
|
|
2175
2253
|
*/
|
|
2176
|
-
const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
|
|
2254
|
+
const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bounds, placement = "auto" }) => {
|
|
2255
|
+
if (placement === "below-axis") return {
|
|
2256
|
+
x: Math.round(clamp(left - box.width / 2, bounds.left, bounds.right, box.width)),
|
|
2257
|
+
y: top + POINTER_HEIGHT
|
|
2258
|
+
};
|
|
2259
|
+
const fit = {
|
|
2260
|
+
left: Math.max(wrapper.left, bounds.left),
|
|
2261
|
+
top: Math.max(wrapper.top, bounds.top),
|
|
2262
|
+
right: Math.min(wrapper.right, bounds.right),
|
|
2263
|
+
bottom: Math.min(wrapper.bottom, bounds.bottom)
|
|
2264
|
+
};
|
|
2177
2265
|
const rightX = left + offsetLeft;
|
|
2178
2266
|
const leftX = left - offsetLeft - box.width;
|
|
2179
|
-
const rightOverflow = rightX + box.width -
|
|
2180
|
-
const leftOverflow =
|
|
2267
|
+
const rightOverflow = rightX + box.width - fit.right;
|
|
2268
|
+
const leftOverflow = fit.left - leftX;
|
|
2181
2269
|
let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
|
|
2182
2270
|
const downY = top + offsetTop;
|
|
2183
2271
|
const upY = top - offsetTop - box.height;
|
|
2184
|
-
const downOverflow = downY + box.height -
|
|
2185
|
-
const upOverflow =
|
|
2272
|
+
const downOverflow = downY + box.height - fit.bottom;
|
|
2273
|
+
const upOverflow = fit.top - upY;
|
|
2186
2274
|
let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
|
|
2187
|
-
x =
|
|
2188
|
-
y =
|
|
2275
|
+
x = clamp(x, bounds.left, bounds.right, box.width);
|
|
2276
|
+
y = clamp(y, bounds.top, bounds.bottom, box.height);
|
|
2189
2277
|
return {
|
|
2190
2278
|
x: Math.round(x),
|
|
2191
2279
|
y: Math.round(y)
|
|
@@ -2199,7 +2287,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
|
|
|
2199
2287
|
* alone would let the box spill into an `overflow: hidden` card and be cut off.
|
|
2200
2288
|
*
|
|
2201
2289
|
* Re-measures on every render, so a box whose content changes width between
|
|
2202
|
-
* two hovers is placed for its current size.
|
|
2290
|
+
* two hovers is placed for its current size. See `getBoundedPosition` for the
|
|
2291
|
+
* below-axis exception.
|
|
2203
2292
|
*
|
|
2204
2293
|
* @param props - visx `Tooltip` props.
|
|
2205
2294
|
* @param props.left - Anchor x, in wrapper coordinates.
|
|
@@ -2209,9 +2298,10 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
|
|
|
2209
2298
|
* @param props.style - Box styles; visx's defaults unless `unstyled`.
|
|
2210
2299
|
* @param props.unstyled - Skip `style` and leave the box bare.
|
|
2211
2300
|
* @param props.children - Box content.
|
|
2301
|
+
* @param props.placement - Fixed below-axis placement or automatic flipping.
|
|
2212
2302
|
* @return The tooltip box.
|
|
2213
2303
|
*/
|
|
2214
|
-
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, ...rest }) => {
|
|
2304
|
+
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
|
|
2215
2305
|
const nodeRef = (0, react.useRef)(null);
|
|
2216
2306
|
const clipRef = (0, react.useRef)(null);
|
|
2217
2307
|
const [position, setPosition] = (0, react.useState)(null);
|
|
@@ -2236,10 +2326,17 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2236
2326
|
top,
|
|
2237
2327
|
offsetLeft,
|
|
2238
2328
|
offsetTop,
|
|
2329
|
+
placement,
|
|
2239
2330
|
box: {
|
|
2240
2331
|
width: own.width,
|
|
2241
2332
|
height: own.height
|
|
2242
2333
|
},
|
|
2334
|
+
wrapper: {
|
|
2335
|
+
left: 0,
|
|
2336
|
+
top: 0,
|
|
2337
|
+
right: wrapperRect.width,
|
|
2338
|
+
bottom: wrapperRect.height
|
|
2339
|
+
},
|
|
2243
2340
|
bounds: {
|
|
2244
2341
|
left: clipRect.left - wrapperRect.left,
|
|
2245
2342
|
top: clipRect.top - wrapperRect.top,
|
|
@@ -2253,11 +2350,12 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2253
2350
|
top,
|
|
2254
2351
|
offsetLeft,
|
|
2255
2352
|
offsetTop,
|
|
2256
|
-
children
|
|
2353
|
+
children,
|
|
2354
|
+
placement
|
|
2257
2355
|
]);
|
|
2258
2356
|
const x = position?.x ?? left + offsetLeft;
|
|
2259
|
-
const y = position?.y ?? top + offsetTop;
|
|
2260
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2357
|
+
const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
|
|
2358
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
|
|
2261
2359
|
ref: nodeRef,
|
|
2262
2360
|
style: {
|
|
2263
2361
|
position: "absolute",
|
|
@@ -2267,12 +2365,40 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2267
2365
|
...!unstyled && style
|
|
2268
2366
|
},
|
|
2269
2367
|
...rest,
|
|
2270
|
-
children
|
|
2368
|
+
children: [placement === "below-axis" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2369
|
+
"aria-hidden": "true",
|
|
2370
|
+
style: {
|
|
2371
|
+
position: "absolute",
|
|
2372
|
+
left: left - x - POINTER_HEIGHT,
|
|
2373
|
+
top: -6,
|
|
2374
|
+
width: 12,
|
|
2375
|
+
height: POINTER_HEIGHT,
|
|
2376
|
+
background: "inherit",
|
|
2377
|
+
clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
|
|
2378
|
+
pointerEvents: "none"
|
|
2379
|
+
}
|
|
2380
|
+
}), children]
|
|
2271
2381
|
});
|
|
2272
2382
|
};
|
|
2273
2383
|
//#endregion
|
|
2274
2384
|
//#region src/components/tooltip/xy-chart-tooltip.tsx
|
|
2275
2385
|
const CROSSHAIR_STROKE_WIDTH = 1.5;
|
|
2386
|
+
const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2387
|
+
"stroke",
|
|
2388
|
+
"strokeWidth",
|
|
2389
|
+
"strokeOpacity",
|
|
2390
|
+
"strokeDasharray",
|
|
2391
|
+
"strokeLinecap",
|
|
2392
|
+
"opacity"
|
|
2393
|
+
]);
|
|
2394
|
+
const crosshairPaintProps = (props = {}) => {
|
|
2395
|
+
const pickPaint = (value) => Object.fromEntries(Object.entries(value).filter(([key]) => CROSSHAIR_PAINT_PROPERTIES.has(key)));
|
|
2396
|
+
return {
|
|
2397
|
+
...pickPaint(props),
|
|
2398
|
+
...props.className === void 0 ? {} : { className: props.className },
|
|
2399
|
+
...props.style === void 0 ? {} : { style: pickPaint(props.style) }
|
|
2400
|
+
};
|
|
2401
|
+
};
|
|
2276
2402
|
const DEFAULT_GLYPH_RADIUS = 4;
|
|
2277
2403
|
const FALLBACK_COLOR = "#222";
|
|
2278
2404
|
const DEFAULT_TOOLTIP_Z_INDEX = 3;
|
|
@@ -2303,7 +2429,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_js
|
|
|
2303
2429
|
seriesKey: key,
|
|
2304
2430
|
...props
|
|
2305
2431
|
}, key);
|
|
2306
|
-
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, zIndex = DEFAULT_TOOLTIP_Z_INDEX, ...rest }) => {
|
|
2432
|
+
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 }) => {
|
|
2307
2433
|
const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
|
|
2308
2434
|
const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
|
|
2309
2435
|
const tooltipContent = renderTooltip ? renderTooltip({
|
|
@@ -2322,9 +2448,9 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2322
2448
|
};
|
|
2323
2449
|
const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
|
|
2324
2450
|
let { tooltipLeft, tooltipTop } = tooltipContext;
|
|
2325
|
-
if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
|
|
2451
|
+
if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
|
|
2326
2452
|
const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
|
|
2327
|
-
if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
|
|
2453
|
+
if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
|
|
2328
2454
|
if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
|
|
2329
2455
|
}
|
|
2330
2456
|
const glyphs = [];
|
|
@@ -2358,13 +2484,14 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2358
2484
|
const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
2359
2485
|
const marginTop = margin?.top ?? 0;
|
|
2360
2486
|
const marginLeft = margin?.left ?? 0;
|
|
2361
|
-
const TooltipComponent = detectBounds ? BoundedTooltip : _visx_tooltip.Tooltip;
|
|
2487
|
+
const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : _visx_tooltip.Tooltip;
|
|
2362
2488
|
const boxStyle = {
|
|
2363
2489
|
..._visx_tooltip.defaultStyles,
|
|
2364
2490
|
zIndex,
|
|
2365
|
-
|
|
2366
|
-
boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
|
|
2367
|
-
...theme?.htmlLabel
|
|
2491
|
+
backgroundColor: theme?.backgroundColor ?? "white",
|
|
2492
|
+
boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
|
|
2493
|
+
...theme?.htmlLabel,
|
|
2494
|
+
...style
|
|
2368
2495
|
};
|
|
2369
2496
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
2370
2497
|
className: "visx-tooltip-overlay",
|
|
@@ -2372,32 +2499,33 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2372
2499
|
children: [
|
|
2373
2500
|
showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
2374
2501
|
className: "visx-crosshair visx-crosshair-vertical",
|
|
2502
|
+
stroke: crosshairStroke,
|
|
2503
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2504
|
+
...crosshairPaintProps(verticalCrosshairStyle),
|
|
2375
2505
|
x1: tooltipLeft,
|
|
2376
2506
|
x2: tooltipLeft,
|
|
2377
2507
|
y1: marginTop,
|
|
2378
|
-
y2: marginTop + innerHeight
|
|
2379
|
-
stroke: crosshairStroke,
|
|
2380
|
-
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2381
|
-
...verticalCrosshairStyle
|
|
2508
|
+
y2: marginTop + innerHeight
|
|
2382
2509
|
}),
|
|
2383
2510
|
showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
2384
2511
|
className: "visx-crosshair visx-crosshair-horizontal",
|
|
2512
|
+
stroke: crosshairStroke,
|
|
2513
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2514
|
+
...crosshairPaintProps(horizontalCrosshairStyle),
|
|
2385
2515
|
x1: marginLeft,
|
|
2386
2516
|
x2: marginLeft + innerWidth,
|
|
2387
2517
|
y1: tooltipTop,
|
|
2388
|
-
y2: tooltipTop
|
|
2389
|
-
stroke: crosshairStroke,
|
|
2390
|
-
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2391
|
-
...horizontalCrosshairStyle
|
|
2518
|
+
y2: tooltipTop
|
|
2392
2519
|
}),
|
|
2393
2520
|
glyphs
|
|
2394
2521
|
]
|
|
2395
2522
|
}), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
|
|
2396
2523
|
left: tooltipLeft,
|
|
2397
|
-
top: tooltipTop,
|
|
2524
|
+
top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
|
|
2398
2525
|
style: boxStyle,
|
|
2399
2526
|
applyPositionStyle: true,
|
|
2400
2527
|
...tooltipProps,
|
|
2528
|
+
...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
|
|
2401
2529
|
children: tooltipContent
|
|
2402
2530
|
}), container)] });
|
|
2403
2531
|
};
|
|
@@ -2418,7 +2546,8 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2418
2546
|
* isolation keeps the box's `zIndex` from competing with page chrome. The box
|
|
2419
2547
|
* flips and clamps to stay inside the nearest ancestor that clips its overflow
|
|
2420
2548
|
* (or the viewport), so it may extend past the chart wrapper but is never cut
|
|
2421
|
-
* off unless that ancestor is smaller than the box itself.
|
|
2549
|
+
* off unless that ancestor is smaller than the box itself. Below-axis placement
|
|
2550
|
+
* pins it beneath the x-axis and applies horizontal bounds only.
|
|
2422
2551
|
*
|
|
2423
2552
|
* @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
|
|
2424
2553
|
* @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
|
|
@@ -2535,12 +2664,16 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2535
2664
|
renderTooltip: focusableRenderTooltip
|
|
2536
2665
|
});
|
|
2537
2666
|
};
|
|
2538
|
-
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
|
|
2667
|
+
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
|
|
2539
2668
|
return {
|
|
2540
2669
|
tooltipRef: (0, react.useCallback)((element) => {
|
|
2541
|
-
if (element && selectedIndex !== void 0)
|
|
2542
|
-
|
|
2543
|
-
|
|
2670
|
+
if (element && selectedIndex !== void 0) {
|
|
2671
|
+
if (preventTooltipScroll) element.focus({ preventScroll: true });
|
|
2672
|
+
else element.focus();
|
|
2673
|
+
}
|
|
2674
|
+
}, [preventTooltipScroll, selectedIndex]),
|
|
2675
|
+
onChartFocus: (0, react.useCallback)((event) => {
|
|
2676
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
2544
2677
|
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2545
2678
|
}, [
|
|
2546
2679
|
isNavigating,
|
|
@@ -2552,15 +2685,19 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2552
2685
|
}, [setIsNavigating]),
|
|
2553
2686
|
onChartKeyDown: (0, react.useCallback)((event) => {
|
|
2554
2687
|
if (totalPoints === 0) return;
|
|
2688
|
+
const focusChart = () => {
|
|
2689
|
+
if (preventTooltipScroll) chartRef.current?.focus({ preventScroll: true });
|
|
2690
|
+
else chartRef.current?.focus();
|
|
2691
|
+
};
|
|
2555
2692
|
if (event.key === "Tab") {
|
|
2556
|
-
|
|
2693
|
+
focusChart();
|
|
2557
2694
|
setSelectedIndex(void 0);
|
|
2558
2695
|
setIsNavigating(false);
|
|
2559
2696
|
return;
|
|
2560
2697
|
}
|
|
2561
2698
|
const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
|
|
2562
2699
|
if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
|
|
2563
|
-
|
|
2700
|
+
focusChart();
|
|
2564
2701
|
setSelectedIndex(void 0);
|
|
2565
2702
|
setIsNavigating(false);
|
|
2566
2703
|
return;
|
|
@@ -2575,7 +2712,7 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2575
2712
|
} else if (event.key === "Escape") {
|
|
2576
2713
|
setSelectedIndex(void 0);
|
|
2577
2714
|
setIsNavigating(false);
|
|
2578
|
-
|
|
2715
|
+
focusChart();
|
|
2579
2716
|
} else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
|
|
2580
2717
|
}, [
|
|
2581
2718
|
totalPoints,
|
|
@@ -2583,7 +2720,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2583
2720
|
setSelectedIndex,
|
|
2584
2721
|
setIsNavigating,
|
|
2585
2722
|
chartRef,
|
|
2586
|
-
onActivate
|
|
2723
|
+
onActivate,
|
|
2724
|
+
preventTooltipScroll
|
|
2587
2725
|
])
|
|
2588
2726
|
};
|
|
2589
2727
|
};
|
|
@@ -3871,10 +4009,11 @@ const TooltipDate = ({ date, displayResolution }) => {
|
|
|
3871
4009
|
* one row per visible series (label + formatted value), sorted descending by
|
|
3872
4010
|
* value. Reused by AreaChart, which has the same multi-series shape.
|
|
3873
4011
|
*
|
|
3874
|
-
* @param params
|
|
4012
|
+
* @param params - visx tooltip data and the chart's optional `bucketInfo`.
|
|
4013
|
+
* @param contentStyle - Explicit tooltip content color overrides.
|
|
3875
4014
|
* @return Tooltip JSX, or `null` when no datum is hovered.
|
|
3876
4015
|
*/
|
|
3877
|
-
const renderDefaultTooltip = (params) => {
|
|
4016
|
+
const renderDefaultTooltip = (params, contentStyle) => {
|
|
3878
4017
|
const { tooltipData, bucketInfo } = params;
|
|
3879
4018
|
const nearestDatum = tooltipData?.nearestDatum?.datum;
|
|
3880
4019
|
if (!nearestDatum) return null;
|
|
@@ -3884,6 +4023,7 @@ const renderDefaultTooltip = (params) => {
|
|
|
3884
4023
|
})).sort((a, b) => b.value - a.value);
|
|
3885
4024
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3886
4025
|
className: line_chart_module_default["line-chart__tooltip"],
|
|
4026
|
+
style: contentStyle,
|
|
3887
4027
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3888
4028
|
className: line_chart_module_default["line-chart__tooltip-date"],
|
|
3889
4029
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
|
|
@@ -3933,7 +4073,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
|
|
|
3933
4073
|
]);
|
|
3934
4074
|
return null;
|
|
3935
4075
|
};
|
|
3936
|
-
const LineChartInternal = (0, react.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) => {
|
|
4076
|
+
const LineChartInternal = (0, react.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) => {
|
|
3937
4077
|
const legendInteractive = legend.interactive ?? false;
|
|
3938
4078
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
3939
4079
|
const legendShape = legend.shape ?? "line";
|
|
@@ -4011,7 +4151,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4011
4151
|
setIsNavigating,
|
|
4012
4152
|
chartRef,
|
|
4013
4153
|
totalPoints: dataSorted[0]?.data.length || 0,
|
|
4014
|
-
onActivate: activateSelectedPoint
|
|
4154
|
+
onActivate: activateSelectedPoint,
|
|
4155
|
+
preventTooltipScroll: tooltipPlacement === "below-axis"
|
|
4015
4156
|
});
|
|
4016
4157
|
const chartOptions = (0, react.useMemo)(() => {
|
|
4017
4158
|
return {
|
|
@@ -4120,10 +4261,29 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4120
4261
|
xAccessor: (d) => d?.date,
|
|
4121
4262
|
yAccessor: (d) => d?.value
|
|
4122
4263
|
};
|
|
4123
|
-
const
|
|
4264
|
+
const resolvedTooltipStyle = (0, react.useMemo)(() => {
|
|
4265
|
+
if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
|
|
4266
|
+
if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
|
|
4267
|
+
return {
|
|
4268
|
+
backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
|
|
4269
|
+
...tooltipStyle
|
|
4270
|
+
};
|
|
4271
|
+
}, [renderTooltip, tooltipStyle]);
|
|
4272
|
+
const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
|
|
4124
4273
|
...params,
|
|
4125
4274
|
bucketInfo
|
|
4126
|
-
}
|
|
4275
|
+
}, {
|
|
4276
|
+
color: tooltipStyle?.color,
|
|
4277
|
+
background: tooltipStyle?.background,
|
|
4278
|
+
backgroundColor: tooltipStyle?.backgroundColor
|
|
4279
|
+
}) : renderTooltip({
|
|
4280
|
+
...params,
|
|
4281
|
+
bucketInfo
|
|
4282
|
+
}), [
|
|
4283
|
+
renderTooltip,
|
|
4284
|
+
bucketInfo,
|
|
4285
|
+
tooltipStyle
|
|
4286
|
+
]);
|
|
4127
4287
|
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4128
4288
|
className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
|
|
4129
4289
|
children: error
|
|
@@ -4164,6 +4324,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4164
4324
|
children: ({ contentHeight }) => {
|
|
4165
4325
|
const chartHeight = contentHeight > 0 ? contentHeight : height;
|
|
4166
4326
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4327
|
+
ref: chartRef,
|
|
4167
4328
|
role: "grid",
|
|
4168
4329
|
"aria-label": (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
|
|
4169
4330
|
tabIndex: 0,
|
|
@@ -4171,7 +4332,6 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4171
4332
|
onFocus: onChartFocus,
|
|
4172
4333
|
onBlur: onChartBlur,
|
|
4173
4334
|
children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4174
|
-
ref: chartRef,
|
|
4175
4335
|
className: xy_plot_module_default["xy-plot"],
|
|
4176
4336
|
children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
|
|
4177
4337
|
theme,
|
|
@@ -4262,6 +4422,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4262
4422
|
withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
|
|
4263
4423
|
detectBounds: true,
|
|
4264
4424
|
snapTooltipToDatumX: true,
|
|
4425
|
+
tooltipPlacement,
|
|
4426
|
+
style: resolvedTooltipStyle,
|
|
4265
4427
|
snapTooltipToDatumY: true,
|
|
4266
4428
|
showSeriesGlyphs: true,
|
|
4267
4429
|
renderTooltip: tooltipRenderer,
|
|
@@ -4269,6 +4431,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4269
4431
|
glyphStyle,
|
|
4270
4432
|
showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
|
|
4271
4433
|
showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
|
|
4434
|
+
verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
|
|
4435
|
+
horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
|
|
4272
4436
|
selectedIndex,
|
|
4273
4437
|
tooltipRef,
|
|
4274
4438
|
keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
|
|
@@ -4665,6 +4829,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4665
4829
|
children: ({ contentHeight }) => {
|
|
4666
4830
|
const chartHeight = contentHeight > 0 ? contentHeight : height;
|
|
4667
4831
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4832
|
+
ref: chartRef,
|
|
4668
4833
|
role: "grid",
|
|
4669
4834
|
"aria-label": (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
|
|
4670
4835
|
tabIndex: 0,
|
|
@@ -4672,7 +4837,6 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4672
4837
|
onFocus: onChartFocus,
|
|
4673
4838
|
onBlur: onChartBlur,
|
|
4674
4839
|
children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4675
|
-
ref: chartRef,
|
|
4676
4840
|
className: xy_plot_module_default["xy-plot"],
|
|
4677
4841
|
children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
|
|
4678
4842
|
theme,
|
|
@@ -6421,9 +6585,13 @@ var heatmap_chart_module_default = {
|
|
|
6421
6585
|
"heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
|
|
6422
6586
|
"heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
|
|
6423
6587
|
"heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
|
|
6588
|
+
"heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
|
|
6424
6589
|
"heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
|
|
6425
6590
|
"heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
|
|
6591
|
+
"heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
|
|
6426
6592
|
"heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
|
|
6593
|
+
"heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
|
|
6594
|
+
"heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
|
|
6427
6595
|
"heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
|
|
6428
6596
|
"heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
|
|
6429
6597
|
"heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
|
|
@@ -6437,17 +6605,21 @@ var heatmap_chart_module_default = {
|
|
|
6437
6605
|
//#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
|
|
6438
6606
|
const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
|
|
6439
6607
|
/**
|
|
6440
|
-
* Get the min and max values from heatmap data, ignoring null/NaN.
|
|
6608
|
+
* Get the min and max values from heatmap data, ignoring null/NaN. Summary
|
|
6609
|
+
* columns stay out: a roll-up on the scale would flatten every real cell.
|
|
6441
6610
|
* @param data - The heatmap columns
|
|
6442
6611
|
* @return Tuple of [min, max] values
|
|
6443
6612
|
*/
|
|
6444
6613
|
const getValueExtent = (data) => {
|
|
6445
6614
|
let min = Infinity;
|
|
6446
6615
|
let max = -Infinity;
|
|
6447
|
-
for (const column of data)
|
|
6448
|
-
if (
|
|
6449
|
-
|
|
6450
|
-
|
|
6616
|
+
for (const column of data) {
|
|
6617
|
+
if (column.summary) continue;
|
|
6618
|
+
for (const cell of column.data) {
|
|
6619
|
+
if (!isPresent(cell.value)) continue;
|
|
6620
|
+
if (cell.value < min) min = cell.value;
|
|
6621
|
+
if (cell.value > max) max = cell.value;
|
|
6622
|
+
}
|
|
6451
6623
|
}
|
|
6452
6624
|
if (min === Infinity) return [0, 0];
|
|
6453
6625
|
return [min, max];
|
|
@@ -6468,101 +6640,6 @@ const getNormalizedValue = (value, extent) => {
|
|
|
6468
6640
|
return Math.min(1, Math.max(0, (value - min) / (max - min)));
|
|
6469
6641
|
};
|
|
6470
6642
|
//#endregion
|
|
6471
|
-
//#region src/charts/heatmap-chart/private/build-calendar-data.ts
|
|
6472
|
-
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
6473
|
-
const LABELLED_ROWS = [
|
|
6474
|
-
0,
|
|
6475
|
-
2,
|
|
6476
|
-
4
|
|
6477
|
-
];
|
|
6478
|
-
/**
|
|
6479
|
-
* Resolve one grid bound: an unparseable or narrowing value falls back to the
|
|
6480
|
-
* series' own bound, so the grid can only ever be widened.
|
|
6481
|
-
*
|
|
6482
|
-
* @param bound - Requested bound as `yyyy-MM-dd`, if any.
|
|
6483
|
-
* @param fallback - The series' own bound on this side.
|
|
6484
|
-
* @param direction - Which way the bound is allowed to move.
|
|
6485
|
-
* @return The bound to draw to.
|
|
6486
|
-
*/
|
|
6487
|
-
const widenTo = (bound, fallback, direction) => {
|
|
6488
|
-
if (!bound) return fallback;
|
|
6489
|
-
const parsed = (0, date_fns.parseISO)(bound);
|
|
6490
|
-
if (isNaN(parsed.getTime())) return fallback;
|
|
6491
|
-
if (direction === "earlier") return parsed < fallback ? parsed : fallback;
|
|
6492
|
-
return parsed > fallback ? parsed : fallback;
|
|
6493
|
-
};
|
|
6494
|
-
const toDate = (point) => {
|
|
6495
|
-
if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
|
|
6496
|
-
if (point.dateString) {
|
|
6497
|
-
const parsed = (0, date_fns.parseISO)(point.dateString);
|
|
6498
|
-
if (!isNaN(parsed.getTime())) return parsed;
|
|
6499
|
-
}
|
|
6500
|
-
return null;
|
|
6501
|
-
};
|
|
6502
|
-
const buildCalendarHeatmapData = (series, options = {}) => {
|
|
6503
|
-
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
6504
|
-
const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
|
|
6505
|
-
const entries = series.map((point) => ({
|
|
6506
|
-
date: toDate(point),
|
|
6507
|
-
value: point.value
|
|
6508
|
-
})).filter((entry) => entry.date !== null);
|
|
6509
|
-
if (!entries.length) return {
|
|
6510
|
-
data: [],
|
|
6511
|
-
rowLabels: []
|
|
6512
|
-
};
|
|
6513
|
-
const valueByDay = /* @__PURE__ */ new Map();
|
|
6514
|
-
let minDate = entries[0].date;
|
|
6515
|
-
let maxDate = entries[0].date;
|
|
6516
|
-
for (const { date, value } of entries) {
|
|
6517
|
-
valueByDay.set((0, date_fns.format)(date, "yyyy-MM-dd"), value);
|
|
6518
|
-
if (date < minDate) minDate = date;
|
|
6519
|
-
if (date > maxDate) maxDate = date;
|
|
6520
|
-
}
|
|
6521
|
-
const minDayKey = (0, date_fns.format)(minDate, "yyyy-MM-dd");
|
|
6522
|
-
const maxDayKey = (0, date_fns.format)(maxDate, "yyyy-MM-dd");
|
|
6523
|
-
const gridMinDate = widenTo(options.gridSpan?.start, minDate, "earlier");
|
|
6524
|
-
const gridMaxDate = widenTo(options.gridSpan?.end, maxDate, "later");
|
|
6525
|
-
const gridMaxDayKey = (0, date_fns.format)(gridMaxDate, "yyyy-MM-dd");
|
|
6526
|
-
const gridStart = (0, date_fns.startOfWeek)(gridMinDate, { weekStartsOn });
|
|
6527
|
-
const gridMinDayKey = (0, date_fns.format)(gridMinDate < minDate ? gridStart : gridMinDate, "yyyy-MM-dd");
|
|
6528
|
-
const weekCount = (0, date_fns.differenceInCalendarWeeks)(gridMaxDate, gridStart, { weekStartsOn }) + 1;
|
|
6529
|
-
const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? (0, date_fns.format)((0, date_fns.addDays)(gridStart, row), "EEE") : "");
|
|
6530
|
-
const MIN_FIRST_MONTH_WEEKS = 2;
|
|
6531
|
-
const firstMonth = gridStart.getMonth();
|
|
6532
|
-
let firstMonthWeeks = 0;
|
|
6533
|
-
while (firstMonthWeeks < weekCount && (0, date_fns.addDays)(gridStart, firstMonthWeeks * 7).getMonth() === firstMonth) firstMonthWeeks++;
|
|
6534
|
-
const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
|
|
6535
|
-
const data = [];
|
|
6536
|
-
let previousMonth = -1;
|
|
6537
|
-
for (let week = 0; week < weekCount; week++) {
|
|
6538
|
-
const columnStart = (0, date_fns.addDays)(gridStart, week * 7);
|
|
6539
|
-
const month = columnStart.getMonth();
|
|
6540
|
-
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? (0, date_fns.format)(columnStart, "MMM") : "";
|
|
6541
|
-
previousMonth = month;
|
|
6542
|
-
const cells = [];
|
|
6543
|
-
for (let row = 0; row < 7; row++) {
|
|
6544
|
-
const day = (0, date_fns.addDays)(gridStart, week * 7 + row);
|
|
6545
|
-
const key = (0, date_fns.format)(day, "yyyy-MM-dd");
|
|
6546
|
-
const cell = {
|
|
6547
|
-
label: (0, date_fns.format)(day, "EEE, MMM d, yyyy"),
|
|
6548
|
-
value: valueByDay.has(key) ? valueByDay.get(key) : null
|
|
6549
|
-
};
|
|
6550
|
-
if (key < gridMinDayKey || key > gridMaxDayKey) {
|
|
6551
|
-
if (hideOutOfRangeDays) cell.hidden = true;
|
|
6552
|
-
} else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
|
|
6553
|
-
cells.push(cell);
|
|
6554
|
-
}
|
|
6555
|
-
data.push({
|
|
6556
|
-
label,
|
|
6557
|
-
data: cells
|
|
6558
|
-
});
|
|
6559
|
-
}
|
|
6560
|
-
return {
|
|
6561
|
-
data,
|
|
6562
|
-
rowLabels
|
|
6563
|
-
};
|
|
6564
|
-
};
|
|
6565
|
-
//#endregion
|
|
6566
6643
|
//#region src/charts/heatmap-chart/private/heatmap-context.ts
|
|
6567
6644
|
/** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
|
|
6568
6645
|
const HeatmapContext = (0, react.createContext)(null);
|
|
@@ -6612,7 +6689,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6612
6689
|
//#endregion
|
|
6613
6690
|
//#region src/charts/heatmap-chart/heatmap-chart.tsx
|
|
6614
6691
|
const CELL_MIX_FLOOR = .15;
|
|
6615
|
-
const
|
|
6692
|
+
const NO_ROW_LABELS = [];
|
|
6693
|
+
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 }) => {
|
|
6616
6694
|
const chartId = useChartId(providedChartId);
|
|
6617
6695
|
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
6618
6696
|
const scopeElement = useChartScopeElement();
|
|
@@ -6763,13 +6841,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6763
6841
|
const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
|
|
6764
6842
|
const gridStyle = {
|
|
6765
6843
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
6766
|
-
gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
|
|
6844
|
+
gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
|
|
6767
6845
|
gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
|
|
6768
6846
|
};
|
|
6769
6847
|
if (compact) {
|
|
6770
6848
|
gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
|
|
6771
6849
|
gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
|
|
6772
6850
|
}
|
|
6851
|
+
const summaryGaps = (columnIndex) => {
|
|
6852
|
+
if (!data[columnIndex]?.summary) return {};
|
|
6853
|
+
return {
|
|
6854
|
+
[heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
|
|
6855
|
+
[heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
|
|
6856
|
+
};
|
|
6857
|
+
};
|
|
6773
6858
|
const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
|
|
6774
6859
|
const heightCapped = !compact && Boolean(maxCellHeight);
|
|
6775
6860
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
|
|
@@ -6807,7 +6892,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6807
6892
|
"aria-hidden": "true",
|
|
6808
6893
|
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
6809
6894
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6810
|
-
className: heatmap_chart_module_default["heatmap-chart__col-label"],
|
|
6895
|
+
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
|
|
6896
|
+
[heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
|
|
6897
|
+
...summaryGaps(columnIndex)
|
|
6898
|
+
}),
|
|
6811
6899
|
children: column.label
|
|
6812
6900
|
}, `col-${columnIndex}`))]
|
|
6813
6901
|
}), Array.from({ length: rows }).map((_row, rowIndex) => {
|
|
@@ -6832,7 +6920,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6832
6920
|
}, `cell-${columnIndex}-${rowIndex}`);
|
|
6833
6921
|
const value = cell?.value ?? null;
|
|
6834
6922
|
const present = isPresent(value);
|
|
6835
|
-
const
|
|
6923
|
+
const filled = present && !column.summary;
|
|
6924
|
+
const normalized = filled ? getNormalizedValue(value, extent) : 0;
|
|
6836
6925
|
const flatIndex = columnIndex * rows + rowIndex;
|
|
6837
6926
|
const info = buildTooltipData(columnIndex, rowIndex);
|
|
6838
6927
|
const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
|
|
@@ -6845,14 +6934,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6845
6934
|
"data-column": columnIndex,
|
|
6846
6935
|
"data-row": rowIndex,
|
|
6847
6936
|
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
|
|
6848
|
-
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]:
|
|
6849
|
-
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]:
|
|
6937
|
+
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
|
|
6938
|
+
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
|
|
6939
|
+
[heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
|
|
6940
|
+
...summaryGaps(columnIndex),
|
|
6850
6941
|
[heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
|
|
6851
6942
|
}),
|
|
6852
|
-
style:
|
|
6943
|
+
style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
|
|
6853
6944
|
onMouseMove: handleCellMouseMove,
|
|
6854
6945
|
onMouseLeave: handleCellMouseLeave,
|
|
6855
|
-
children: drawValues && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6946
|
+
children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6856
6947
|
className: heatmap_chart_module_default["heatmap-chart__cell-value"],
|
|
6857
6948
|
children: (0, _automattic_number_formatters.formatNumberCompact)(value)
|
|
6858
6949
|
})
|
|
@@ -6888,6 +6979,269 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ (0, react_jsx_run
|
|
|
6888
6979
|
HeatmapChartResponsiveInner.displayName = "HeatmapChart";
|
|
6889
6980
|
const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
|
|
6890
6981
|
//#endregion
|
|
6982
|
+
//#region src/charts/heatmap-chart/private/civil-day.ts
|
|
6983
|
+
const DAY_MS = 864e5;
|
|
6984
|
+
const KEY_OPTIONS = {
|
|
6985
|
+
calendar: "gregory",
|
|
6986
|
+
numberingSystem: "latn",
|
|
6987
|
+
year: "numeric",
|
|
6988
|
+
month: "2-digit",
|
|
6989
|
+
day: "2-digit"
|
|
6990
|
+
};
|
|
6991
|
+
const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
6992
|
+
const pad = (value, length) => String(value).padStart(length, "0");
|
|
6993
|
+
/**
|
|
6994
|
+
* The day a UTC proxy stands for.
|
|
6995
|
+
*
|
|
6996
|
+
* @param date - UTC proxy.
|
|
6997
|
+
* @return Its day key.
|
|
6998
|
+
*/
|
|
6999
|
+
const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
|
|
7000
|
+
/**
|
|
7001
|
+
* A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
|
|
7002
|
+
* `DAY_MS`.
|
|
7003
|
+
*
|
|
7004
|
+
* @param key - Day key.
|
|
7005
|
+
* @return The proxy, or null when the key names no real day.
|
|
7006
|
+
*/
|
|
7007
|
+
const civilDate = (key) => {
|
|
7008
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
|
7009
|
+
if (!match) return null;
|
|
7010
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
7011
|
+
date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
7012
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
7013
|
+
return civilKey(date) === key ? date : null;
|
|
7014
|
+
};
|
|
7015
|
+
/**
|
|
7016
|
+
* The day a written date names, ignoring any time and zone it carries.
|
|
7017
|
+
*
|
|
7018
|
+
* @param text - `yyyy-MM-dd`, optionally followed by a time.
|
|
7019
|
+
* @return Its day key, or null.
|
|
7020
|
+
*/
|
|
7021
|
+
const writtenDayKey = (text) => {
|
|
7022
|
+
const match = DATE_PREFIX.exec(text);
|
|
7023
|
+
if (!match) return null;
|
|
7024
|
+
const key = `${match[1]}-${match[2]}-${match[3]}`;
|
|
7025
|
+
return civilDate(key) ? key : null;
|
|
7026
|
+
};
|
|
7027
|
+
/**
|
|
7028
|
+
* Reads the day an instant falls on in a zone.
|
|
7029
|
+
*
|
|
7030
|
+
* @param timeZone - IANA zone name; absent leaves the runtime's own.
|
|
7031
|
+
* @return Reader taking a `Date`, null for a day no key can hold.
|
|
7032
|
+
*/
|
|
7033
|
+
const instantDayReader = (timeZone) => {
|
|
7034
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
7035
|
+
...KEY_OPTIONS,
|
|
7036
|
+
timeZone
|
|
7037
|
+
});
|
|
7038
|
+
return (date) => {
|
|
7039
|
+
const parts = formatter.formatToParts(date);
|
|
7040
|
+
const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
7041
|
+
const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
|
|
7042
|
+
return civilDate(key) ? key : null;
|
|
7043
|
+
};
|
|
7044
|
+
};
|
|
7045
|
+
/**
|
|
7046
|
+
* The day a series point belongs to.
|
|
7047
|
+
*
|
|
7048
|
+
* A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
|
|
7049
|
+
* zone. Anything else is a wall clock the host wrote, so its date part is the day.
|
|
7050
|
+
*
|
|
7051
|
+
* @param point - Series point.
|
|
7052
|
+
* @param readInstant - Reader from `instantDayReader`.
|
|
7053
|
+
* @return Its day key, or null when the point carries no usable date.
|
|
7054
|
+
*/
|
|
7055
|
+
const pointDayKey = (point, readInstant) => {
|
|
7056
|
+
if (point.date instanceof Date && !isNaN(point.date.getTime())) {
|
|
7057
|
+
const key = readInstant(point.date);
|
|
7058
|
+
if (key) return key;
|
|
7059
|
+
}
|
|
7060
|
+
const text = point.dateString;
|
|
7061
|
+
if (!text) return null;
|
|
7062
|
+
if (!hasTimezone(text)) return writtenDayKey(text);
|
|
7063
|
+
const parsed = (0, date_fns.parseISO)(text);
|
|
7064
|
+
return isNaN(parsed.getTime()) ? null : readInstant(parsed);
|
|
7065
|
+
};
|
|
7066
|
+
/**
|
|
7067
|
+
* Steps a proxy by whole days.
|
|
7068
|
+
*
|
|
7069
|
+
* @param date - UTC proxy.
|
|
7070
|
+
* @param days - Days to add; may be negative.
|
|
7071
|
+
* @return The shifted proxy.
|
|
7072
|
+
*/
|
|
7073
|
+
const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
|
|
7074
|
+
/**
|
|
7075
|
+
* Rounds a proxy down to its week.
|
|
7076
|
+
*
|
|
7077
|
+
* @param date - UTC proxy.
|
|
7078
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
7079
|
+
* @return The proxy for that week's first day.
|
|
7080
|
+
*/
|
|
7081
|
+
const startOfCivilWeek = (date, weekStartsOn) => {
|
|
7082
|
+
const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
|
|
7083
|
+
return addCivilDays(date, -daysSinceWeekStart);
|
|
7084
|
+
};
|
|
7085
|
+
/**
|
|
7086
|
+
* Counts the columns a span needs.
|
|
7087
|
+
*
|
|
7088
|
+
* @param from - Earlier UTC proxy.
|
|
7089
|
+
* @param to - Later UTC proxy.
|
|
7090
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
7091
|
+
* @return Columns needed to cover the span, inclusive.
|
|
7092
|
+
*/
|
|
7093
|
+
const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
|
|
7094
|
+
//#endregion
|
|
7095
|
+
//#region src/charts/heatmap-chart/build-calendar-data.ts
|
|
7096
|
+
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
7097
|
+
const LABELLED_ROWS = [
|
|
7098
|
+
0,
|
|
7099
|
+
2,
|
|
7100
|
+
4
|
|
7101
|
+
];
|
|
7102
|
+
/**
|
|
7103
|
+
* Resolve one grid bound: an unparseable or narrowing value falls back to the
|
|
7104
|
+
* series' own bound, so the grid can only ever be widened.
|
|
7105
|
+
*
|
|
7106
|
+
* @param bound - Requested bound as `yyyy-MM-dd`, if any.
|
|
7107
|
+
* @param fallback - The series' own bound on this side.
|
|
7108
|
+
* @param direction - Which way the bound is allowed to move.
|
|
7109
|
+
* @return The day key to draw to.
|
|
7110
|
+
*/
|
|
7111
|
+
const widenTo = (bound, fallback, direction) => {
|
|
7112
|
+
const key = bound ? writtenDayKey(bound) : null;
|
|
7113
|
+
if (!key) {
|
|
7114
|
+
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.`);
|
|
7115
|
+
return fallback;
|
|
7116
|
+
}
|
|
7117
|
+
if (direction === "earlier") return key < fallback ? key : fallback;
|
|
7118
|
+
return key > fallback ? key : fallback;
|
|
7119
|
+
};
|
|
7120
|
+
/**
|
|
7121
|
+
* Lay a day-bucketed series out as calendar columns.
|
|
7122
|
+
*
|
|
7123
|
+
* @param series - Points to bucket.
|
|
7124
|
+
* @param options - Grid shape, plus the locale and zone to read days in.
|
|
7125
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
7126
|
+
*/
|
|
7127
|
+
const buildCalendarHeatmapData = (series, options = {}) => {
|
|
7128
|
+
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
7129
|
+
const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
|
|
7130
|
+
const { locale, timeZone } = sanitizeFormatting({
|
|
7131
|
+
locale: options.locale,
|
|
7132
|
+
timeZone: options.timeZone
|
|
7133
|
+
});
|
|
7134
|
+
const readInstant = instantDayReader(timeZone);
|
|
7135
|
+
const valueByDay = /* @__PURE__ */ new Map();
|
|
7136
|
+
let minDayKey;
|
|
7137
|
+
let maxDayKey;
|
|
7138
|
+
for (const point of series) {
|
|
7139
|
+
const key = pointDayKey(point, readInstant);
|
|
7140
|
+
if (!key) {
|
|
7141
|
+
const offending = point.dateString ?? (point.date && String(point.date));
|
|
7142
|
+
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.");
|
|
7143
|
+
continue;
|
|
7144
|
+
}
|
|
7145
|
+
valueByDay.set(key, point.value);
|
|
7146
|
+
if (!minDayKey || key < minDayKey) minDayKey = key;
|
|
7147
|
+
if (!maxDayKey || key > maxDayKey) maxDayKey = key;
|
|
7148
|
+
}
|
|
7149
|
+
if (!minDayKey || !maxDayKey) return {
|
|
7150
|
+
data: [],
|
|
7151
|
+
rowLabels: []
|
|
7152
|
+
};
|
|
7153
|
+
const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
|
|
7154
|
+
const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
|
|
7155
|
+
const requestedMinDate = civilDate(requestedMinDayKey);
|
|
7156
|
+
const gridMaxDate = civilDate(gridMaxDayKey);
|
|
7157
|
+
const labelFormatting = {
|
|
7158
|
+
locale,
|
|
7159
|
+
timeZone: "UTC"
|
|
7160
|
+
};
|
|
7161
|
+
const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
|
|
7162
|
+
const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
|
|
7163
|
+
const formatDay = createDateFormatter({
|
|
7164
|
+
weekday: "short",
|
|
7165
|
+
month: "short",
|
|
7166
|
+
day: "numeric",
|
|
7167
|
+
year: "numeric"
|
|
7168
|
+
}, labelFormatting);
|
|
7169
|
+
const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
|
|
7170
|
+
const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
|
|
7171
|
+
const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
|
|
7172
|
+
const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
|
|
7173
|
+
const MIN_FIRST_MONTH_WEEKS = 2;
|
|
7174
|
+
const firstMonth = gridStart.getUTCMonth();
|
|
7175
|
+
let firstMonthWeeks = 0;
|
|
7176
|
+
while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
|
|
7177
|
+
const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
|
|
7178
|
+
const data = [];
|
|
7179
|
+
let previousMonth = -1;
|
|
7180
|
+
for (let week = 0; week < weekCount; week++) {
|
|
7181
|
+
const columnStart = addCivilDays(gridStart, week * 7);
|
|
7182
|
+
const month = columnStart.getUTCMonth();
|
|
7183
|
+
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
|
|
7184
|
+
previousMonth = month;
|
|
7185
|
+
const cells = [];
|
|
7186
|
+
for (let row = 0; row < 7; row++) {
|
|
7187
|
+
const day = addCivilDays(gridStart, week * 7 + row);
|
|
7188
|
+
const key = civilKey(day);
|
|
7189
|
+
const cell = {
|
|
7190
|
+
label: formatDay(day),
|
|
7191
|
+
value: valueByDay.has(key) ? valueByDay.get(key) : null
|
|
7192
|
+
};
|
|
7193
|
+
if (key < gridMinDayKey || key > gridMaxDayKey) {
|
|
7194
|
+
if (hideOutOfRangeDays) cell.hidden = true;
|
|
7195
|
+
} else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
|
|
7196
|
+
cells.push(cell);
|
|
7197
|
+
}
|
|
7198
|
+
data.push({
|
|
7199
|
+
label,
|
|
7200
|
+
data: cells
|
|
7201
|
+
});
|
|
7202
|
+
}
|
|
7203
|
+
return {
|
|
7204
|
+
data,
|
|
7205
|
+
rowLabels
|
|
7206
|
+
};
|
|
7207
|
+
};
|
|
7208
|
+
//#endregion
|
|
7209
|
+
//#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
|
|
7210
|
+
/**
|
|
7211
|
+
* `buildCalendarHeatmapData` with the locale and zone taken from
|
|
7212
|
+
* `GlobalChartsProvider` where the caller names neither.
|
|
7213
|
+
*
|
|
7214
|
+
* @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
|
|
7215
|
+
* @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
|
|
7216
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
7217
|
+
*/
|
|
7218
|
+
const useCalendarHeatmapData = (series, options = {}) => {
|
|
7219
|
+
const formatting = useChartFormatting();
|
|
7220
|
+
const locale = options.locale ?? formatting.locale;
|
|
7221
|
+
const timeZone = options.timeZone ?? formatting.timeZone;
|
|
7222
|
+
const { weekStartsOn, hideOutOfRangeDays } = options;
|
|
7223
|
+
const gridStart = options.gridSpan?.start;
|
|
7224
|
+
const gridEnd = options.gridSpan?.end;
|
|
7225
|
+
return (0, react.useMemo)(() => buildCalendarHeatmapData(series, {
|
|
7226
|
+
locale,
|
|
7227
|
+
timeZone,
|
|
7228
|
+
weekStartsOn,
|
|
7229
|
+
hideOutOfRangeDays,
|
|
7230
|
+
gridSpan: {
|
|
7231
|
+
start: gridStart,
|
|
7232
|
+
end: gridEnd
|
|
7233
|
+
}
|
|
7234
|
+
}), [
|
|
7235
|
+
series,
|
|
7236
|
+
locale,
|
|
7237
|
+
timeZone,
|
|
7238
|
+
weekStartsOn,
|
|
7239
|
+
hideOutOfRangeDays,
|
|
7240
|
+
gridStart,
|
|
7241
|
+
gridEnd
|
|
7242
|
+
]);
|
|
7243
|
+
};
|
|
7244
|
+
//#endregion
|
|
6891
7245
|
//#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
|
|
6892
7246
|
/*!
|
|
6893
7247
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
@@ -7251,7 +7605,7 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7251
7605
|
tslib_1.__exportStar(require_dist$1(), exports);
|
|
7252
7606
|
}));
|
|
7253
7607
|
//#endregion
|
|
7254
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
7608
|
+
//#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
|
|
7255
7609
|
function useUpdateEffect(effect, deps) {
|
|
7256
7610
|
const mountedRef = (0, react.useRef)(false);
|
|
7257
7611
|
(0, react.useEffect)(() => {
|
|
@@ -8889,7 +9243,7 @@ _createEmotion.css;
|
|
|
8889
9243
|
_createEmotion.sheet;
|
|
8890
9244
|
_createEmotion.cache;
|
|
8891
9245
|
//#endregion
|
|
8892
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9246
|
+
//#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
|
|
8893
9247
|
var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
|
|
8894
9248
|
var useCx = () => {
|
|
8895
9249
|
const cache = __unsafe_useEmotionCache();
|
|
@@ -8996,7 +9350,7 @@ function memize(fn, options) {
|
|
|
8996
9350
|
return memoized;
|
|
8997
9351
|
}
|
|
8998
9352
|
//#endregion
|
|
8999
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9353
|
+
//#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
|
|
9000
9354
|
var white = "#fff";
|
|
9001
9355
|
var GRAY = {
|
|
9002
9356
|
900: "#1e1e1e",
|
|
@@ -9075,7 +9429,7 @@ var COLORS = Object.freeze({
|
|
|
9075
9429
|
ui: UI
|
|
9076
9430
|
});
|
|
9077
9431
|
//#endregion
|
|
9078
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9432
|
+
//#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
|
|
9079
9433
|
var CONTROL_HEIGHT = "36px";
|
|
9080
9434
|
var CONTROL_PROPS = {
|
|
9081
9435
|
controlPaddingX: 12,
|
|
@@ -9193,7 +9547,7 @@ function warning(message) {
|
|
|
9193
9547
|
logged.add(message);
|
|
9194
9548
|
}
|
|
9195
9549
|
//#endregion
|
|
9196
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9550
|
+
//#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
|
|
9197
9551
|
var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
|
|
9198
9552
|
var ComponentsContext = (0, react.createContext)(
|
|
9199
9553
|
/** @type {Record<string, any>} */
|
|
@@ -9220,19 +9574,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
|
|
|
9220
9574
|
};
|
|
9221
9575
|
(0, react.memo)(BaseContextSystemProvider);
|
|
9222
9576
|
//#endregion
|
|
9223
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9577
|
+
//#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
|
|
9224
9578
|
var COMPONENT_NAMESPACE = "data-wp-component";
|
|
9225
9579
|
var CONNECTED_NAMESPACE = "data-wp-c16t";
|
|
9226
9580
|
var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
|
|
9227
9581
|
//#endregion
|
|
9228
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9582
|
+
//#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
|
|
9229
9583
|
var import_dist = require_dist();
|
|
9230
9584
|
function getStyledClassName(namespace) {
|
|
9231
9585
|
return `components-${(0, import_dist.paramCase)(namespace)}`;
|
|
9232
9586
|
}
|
|
9233
9587
|
var getStyledClassNameFromKey = memize(getStyledClassName);
|
|
9234
9588
|
//#endregion
|
|
9235
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9589
|
+
//#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
|
|
9236
9590
|
function contextConnect(Component, namespace) {
|
|
9237
9591
|
return _contextConnect(Component, namespace, { forwardsRef: true });
|
|
9238
9592
|
}
|
|
@@ -9249,7 +9603,7 @@ function _contextConnect(Component, namespace, options) {
|
|
|
9249
9603
|
});
|
|
9250
9604
|
}
|
|
9251
9605
|
//#endregion
|
|
9252
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9606
|
+
//#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
|
|
9253
9607
|
function getNamespace(componentName) {
|
|
9254
9608
|
return { [COMPONENT_NAMESPACE]: componentName };
|
|
9255
9609
|
}
|
|
@@ -9257,7 +9611,7 @@ function getConnectedNamespace() {
|
|
|
9257
9611
|
return { [CONNECTED_NAMESPACE]: true };
|
|
9258
9612
|
}
|
|
9259
9613
|
//#endregion
|
|
9260
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9614
|
+
//#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
|
|
9261
9615
|
function useContextSystem(props, namespace) {
|
|
9262
9616
|
const contextSystemProps = useComponentsContext();
|
|
9263
9617
|
if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
|
|
@@ -9277,7 +9631,7 @@ function useContextSystem(props, namespace) {
|
|
|
9277
9631
|
return finalComponentProps;
|
|
9278
9632
|
}
|
|
9279
9633
|
//#endregion
|
|
9280
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9634
|
+
//#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
|
|
9281
9635
|
var visuallyHidden = {
|
|
9282
9636
|
border: 0,
|
|
9283
9637
|
clip: "rect(1px, 1px, 1px, 1px)",
|
|
@@ -9293,7 +9647,7 @@ var visuallyHidden = {
|
|
|
9293
9647
|
wordBreak: "normal"
|
|
9294
9648
|
};
|
|
9295
9649
|
//#endregion
|
|
9296
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9650
|
+
//#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
|
|
9297
9651
|
var customAttributeRegExp = /^(data|aria|x)-/i;
|
|
9298
9652
|
var eventHandlerRegExp = /^on[A-Z]/;
|
|
9299
9653
|
var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
|
|
@@ -9383,7 +9737,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
|
|
|
9383
9737
|
}
|
|
9384
9738
|
var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
|
|
9385
9739
|
//#endregion
|
|
9386
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9740
|
+
//#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
|
|
9387
9741
|
function UnforwardedView({ css, ...restProps }, ref) {
|
|
9388
9742
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
|
|
9389
9743
|
ref,
|
|
@@ -9392,7 +9746,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
|
|
|
9392
9746
|
}
|
|
9393
9747
|
var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
|
|
9394
9748
|
//#endregion
|
|
9395
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9749
|
+
//#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
|
|
9396
9750
|
function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
9397
9751
|
const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
|
|
9398
9752
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
|
|
@@ -9407,7 +9761,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
|
9407
9761
|
}
|
|
9408
9762
|
var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
|
|
9409
9763
|
//#endregion
|
|
9410
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9764
|
+
//#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
|
|
9411
9765
|
var breakpoints = [
|
|
9412
9766
|
"40em",
|
|
9413
9767
|
"52em",
|
|
@@ -9441,7 +9795,7 @@ function useResponsiveValue(values, options = {}) {
|
|
|
9441
9795
|
return array[index >= array.length ? array.length - 1 : index];
|
|
9442
9796
|
}
|
|
9443
9797
|
//#endregion
|
|
9444
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9798
|
+
//#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
|
|
9445
9799
|
var ALIGNMENTS = {
|
|
9446
9800
|
bottom: {
|
|
9447
9801
|
alignItems: "flex-end",
|
|
@@ -9489,7 +9843,7 @@ function getAlignmentProps(alignment) {
|
|
|
9489
9843
|
return alignment ? ALIGNMENTS[alignment] : {};
|
|
9490
9844
|
}
|
|
9491
9845
|
//#endregion
|
|
9492
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9846
|
+
//#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
|
|
9493
9847
|
function useGrid(props) {
|
|
9494
9848
|
const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
|
|
9495
9849
|
const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
|
|
@@ -9531,7 +9885,7 @@ function useGrid(props) {
|
|
|
9531
9885
|
};
|
|
9532
9886
|
}
|
|
9533
9887
|
//#endregion
|
|
9534
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9888
|
+
//#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
|
|
9535
9889
|
function UnconnectedGrid(props, forwardedRef) {
|
|
9536
9890
|
const gridProps = useGrid(props);
|
|
9537
9891
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
|
|
@@ -10833,6 +11187,7 @@ exports.parseHslString = parseHslString;
|
|
|
10833
11187
|
exports.prefersLightText = prefersLightText;
|
|
10834
11188
|
exports.relativeLuminance = relativeLuminance;
|
|
10835
11189
|
exports.resolveCssVariable = resolveCssVariable;
|
|
11190
|
+
exports.useCalendarHeatmapData = useCalendarHeatmapData;
|
|
10836
11191
|
exports.useChartFormatting = useChartFormatting;
|
|
10837
11192
|
exports.useChartLegendItems = useChartLegendItems;
|
|
10838
11193
|
exports.useChartRegistration = useChartRegistration;
|