@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.js
CHANGED
|
@@ -6,7 +6,8 @@ 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";
|
|
@@ -84,46 +85,35 @@ function attachSubComponents(Chart, subComponents) {
|
|
|
84
85
|
return Object.assign(Chart, subComponents);
|
|
85
86
|
}
|
|
86
87
|
//#endregion
|
|
87
|
-
//#region src/utils/
|
|
88
|
+
//#region src/utils/warn-once.ts
|
|
89
|
+
const warned = /* @__PURE__ */ new Set();
|
|
88
90
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* This module provides utilities for parsing various date string formats and converting
|
|
92
|
-
* them to local timezone dates using the battle-tested date-fns library. For formats
|
|
93
|
-
* without timezone info, they're treated as local. For formats with timezone info,
|
|
94
|
-
* they're converted to the equivalent local time.
|
|
91
|
+
* Warns about a host-supplied value once per process, and never in production.
|
|
95
92
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
104
105
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* - YYYY-MM-DD HH:mm (treated as local)
|
|
109
|
-
* - YYYY-MM-DDTHH:mm:ss (treated as local)
|
|
110
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
|
|
111
|
-
* - YYYY-MM-DDTHH:mm (treated as local)
|
|
112
|
-
* - YYYY-MM-DDTHH:mm:ssZ (converted to local)
|
|
113
|
-
* - 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.
|
|
114
109
|
*
|
|
115
|
-
*
|
|
116
|
-
* ```typescript
|
|
117
|
-
* parseAsLocalDate("2025-01-01"); // Local timezone
|
|
118
|
-
* parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
|
|
119
|
-
* parseAsLocalDate("2025-01-01 14:30"); // Local timezone
|
|
120
|
-
* parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
|
|
121
|
-
* parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
|
|
122
|
-
* parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
|
|
123
|
-
* ```
|
|
110
|
+
* Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
|
|
124
111
|
*/
|
|
125
112
|
/**
|
|
126
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
|
+
*
|
|
127
117
|
* @param {string} dateString - The date string to check for timezone information
|
|
128
118
|
* @return {boolean} True if the date string contains timezone information, false otherwise
|
|
129
119
|
*/
|
|
@@ -131,28 +121,57 @@ const hasTimezone = (dateString) => {
|
|
|
131
121
|
const tIndex = dateString.indexOf("T");
|
|
132
122
|
if (tIndex === -1) return false;
|
|
133
123
|
if (dateString.endsWith("Z")) return true;
|
|
134
|
-
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();
|
|
135
133
|
};
|
|
136
134
|
/**
|
|
137
|
-
*
|
|
135
|
+
* The instant a wall-clock reading names in a given zone.
|
|
138
136
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* 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.
|
|
142
139
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* - YYYY-MM-DD HH:mm (local)
|
|
147
|
-
* - YYYY-MM-DDTHH:mm:ss (local)
|
|
148
|
-
* - YYYY-MM-DDTHH:mm:ss.SSS (local)
|
|
149
|
-
* - YYYY-MM-DDTHH:mm (local)
|
|
150
|
-
* - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
|
|
151
|
-
* - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
|
|
152
|
-
* @param {string} dateString - The date string to parse into a local timezone date
|
|
153
|
-
* @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.
|
|
154
143
|
*/
|
|
155
|
-
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) => {
|
|
156
175
|
const trimmedString = dateString.trim();
|
|
157
176
|
if (hasTimezone(trimmedString)) {
|
|
158
177
|
const isoDate = parseISO(trimmedString);
|
|
@@ -168,7 +187,10 @@ const parseAsLocalDate = (dateString) => {
|
|
|
168
187
|
"yyyy-MM-dd'T'HH:mm"
|
|
169
188
|
]) {
|
|
170
189
|
const result = parse(trimmedString, format, /* @__PURE__ */ new Date());
|
|
171
|
-
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;
|
|
172
194
|
}
|
|
173
195
|
return /* @__PURE__ */ new Date(NaN);
|
|
174
196
|
};
|
|
@@ -273,6 +295,31 @@ const formatPercentage = (value) => {
|
|
|
273
295
|
} });
|
|
274
296
|
};
|
|
275
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
|
|
276
323
|
//#region src/utils/get-longest-tick-width.ts
|
|
277
324
|
/**
|
|
278
325
|
* Returns the width of the longest tick.
|
|
@@ -659,12 +706,6 @@ const getFormatter$1 = (options, { locale, timeZone }) => {
|
|
|
659
706
|
}
|
|
660
707
|
return formatter;
|
|
661
708
|
};
|
|
662
|
-
const warned = /* @__PURE__ */ new Set();
|
|
663
|
-
const warnOnce = (key, message) => {
|
|
664
|
-
if (warned.has(key) || process.env.NODE_ENV === "production") return;
|
|
665
|
-
warned.add(key);
|
|
666
|
-
console.warn(`[Charts] ${message}`);
|
|
667
|
-
};
|
|
668
709
|
const isUsable = (formatting) => {
|
|
669
710
|
try {
|
|
670
711
|
Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
|
|
@@ -1290,13 +1331,28 @@ const useXYChartTheme = (data) => {
|
|
|
1290
1331
|
]);
|
|
1291
1332
|
};
|
|
1292
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
|
|
1293
1349
|
//#region src/hooks/use-chart-data-transform.ts
|
|
1294
1350
|
/**
|
|
1295
1351
|
* Hook that transforms and sorts chart data, handling date parsing and sorting
|
|
1296
1352
|
*
|
|
1297
1353
|
* This hook extracts the common data transformation logic used in both line-chart
|
|
1298
1354
|
* and bar-chart components. It:
|
|
1299
|
-
* 1. Parses date strings into Date objects
|
|
1355
|
+
* 1. Parses date strings into Date objects, dated in the provider's time zone
|
|
1300
1356
|
* 2. Sorts data points by date when date properties are present
|
|
1301
1357
|
* 3. Returns the original data unchanged when no date properties are found
|
|
1302
1358
|
*
|
|
@@ -1304,6 +1360,7 @@ const useXYChartTheme = (data) => {
|
|
|
1304
1360
|
* @return {SeriesData[]} The transformed and sorted data
|
|
1305
1361
|
*/
|
|
1306
1362
|
const useChartDataTransform = (data) => {
|
|
1363
|
+
const { timeZone } = useChartFormatting();
|
|
1307
1364
|
return useMemo(() => {
|
|
1308
1365
|
const firstPoint = data?.[0]?.data?.[0];
|
|
1309
1366
|
if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
|
|
@@ -1312,7 +1369,7 @@ const useChartDataTransform = (data) => {
|
|
|
1312
1369
|
data: series.data.map((point) => {
|
|
1313
1370
|
let date;
|
|
1314
1371
|
if ("date" in point && point.date) date = point.date;
|
|
1315
|
-
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
|
|
1372
|
+
else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
|
|
1316
1373
|
return {
|
|
1317
1374
|
...point,
|
|
1318
1375
|
date
|
|
@@ -1322,7 +1379,7 @@ const useChartDataTransform = (data) => {
|
|
|
1322
1379
|
return a.date.getTime() - b.date.getTime();
|
|
1323
1380
|
})
|
|
1324
1381
|
}));
|
|
1325
|
-
}, [data]);
|
|
1382
|
+
}, [data, timeZone]);
|
|
1326
1383
|
};
|
|
1327
1384
|
//#endregion
|
|
1328
1385
|
//#region src/hooks/use-chart-margin.tsx
|
|
@@ -1364,11 +1421,32 @@ const DEFAULT_TICK_LENGTH = 8;
|
|
|
1364
1421
|
* measure them via getLongestTickWidth.
|
|
1365
1422
|
*/
|
|
1366
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
|
+
};
|
|
1367
1444
|
const getXAxisLabelMetrics = (theme, orientation) => {
|
|
1368
1445
|
const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
|
|
1369
1446
|
return {
|
|
1370
1447
|
fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
|
|
1371
|
-
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
|
|
1448
|
+
tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
|
|
1449
|
+
tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
|
|
1372
1450
|
};
|
|
1373
1451
|
};
|
|
1374
1452
|
const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
@@ -1380,7 +1458,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1380
1458
|
const maxY = Math.max(...allDataPoints.map((d) => d.value));
|
|
1381
1459
|
const yScale = createScale({
|
|
1382
1460
|
...options.yScale,
|
|
1383
|
-
domain: [minY, maxY],
|
|
1461
|
+
domain: options.yScale?.domain ?? [minY, maxY],
|
|
1384
1462
|
range: [height, 0]
|
|
1385
1463
|
});
|
|
1386
1464
|
return getTicks(yScale, options.axis?.y?.numTicks);
|
|
@@ -1399,18 +1477,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1399
1477
|
};
|
|
1400
1478
|
const yAxisOrientation = options.axis?.y?.orientation;
|
|
1401
1479
|
const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
|
|
1402
|
-
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
|
|
1480
|
+
const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
|
|
1403
1481
|
const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
|
|
1404
1482
|
const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
|
|
1405
|
-
if (
|
|
1406
|
-
|
|
1483
|
+
if (options.axis?.y?.display !== false) {
|
|
1484
|
+
if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
|
|
1485
|
+
else defaultMargin.left = yMarginValue;
|
|
1486
|
+
}
|
|
1407
1487
|
const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
|
|
1408
|
-
const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1488
|
+
const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
|
|
1409
1489
|
const computedXMargin = fontSize + tickLength;
|
|
1410
1490
|
if (xOrientation === "top") {
|
|
1411
1491
|
defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
|
|
1412
1492
|
defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
|
|
1413
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
|
+
}
|
|
1414
1499
|
return defaultMargin;
|
|
1415
1500
|
}, [
|
|
1416
1501
|
options,
|
|
@@ -1686,21 +1771,6 @@ const useGlobalChartsTheme = () => {
|
|
|
1686
1771
|
return useContext(GlobalChartsContext)?.theme ?? defaultTheme;
|
|
1687
1772
|
};
|
|
1688
1773
|
//#endregion
|
|
1689
|
-
//#region src/providers/chart-context/hooks/use-chart-formatting.ts
|
|
1690
|
-
const RUNTIME_FORMATTING = {};
|
|
1691
|
-
/**
|
|
1692
|
-
* The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
|
|
1693
|
-
*
|
|
1694
|
-
* Falls back to the runtime's own rather than throwing: charts render outside a
|
|
1695
|
-
* provider, and a host that sets neither gets exactly the browser-default
|
|
1696
|
-
* behavior this package had before the context existed.
|
|
1697
|
-
*
|
|
1698
|
-
* @return The host's formatting context.
|
|
1699
|
-
*/
|
|
1700
|
-
const useChartFormatting = () => {
|
|
1701
|
-
return useContext(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
|
|
1702
|
-
};
|
|
1703
|
-
//#endregion
|
|
1704
1774
|
//#region src/components/legend/utils/value-or-identity.ts
|
|
1705
1775
|
/**
|
|
1706
1776
|
* Returns an object's value if defined, or the object.
|
|
@@ -2140,6 +2210,8 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
|
|
|
2140
2210
|
//#endregion
|
|
2141
2211
|
//#region src/components/tooltip/private/bounded-tooltip.tsx
|
|
2142
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));
|
|
2143
2215
|
const isClipping = (element) => {
|
|
2144
2216
|
const { overflow, overflowX, overflowY } = getComputedStyle(element);
|
|
2145
2217
|
return [
|
|
@@ -2157,33 +2229,49 @@ const findClippingAncestor = (wrapper) => {
|
|
|
2157
2229
|
return null;
|
|
2158
2230
|
};
|
|
2159
2231
|
/**
|
|
2160
|
-
* Where the box goes, in wrapper coordinates.
|
|
2161
|
-
*
|
|
2162
|
-
*
|
|
2163
|
-
*
|
|
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.
|
|
2164
2240
|
*
|
|
2165
|
-
* @param params - Anchor, offsets, the box size and the
|
|
2241
|
+
* @param params - Anchor, offsets, the box size and the rects to keep inside.
|
|
2166
2242
|
* @param params.left - Anchor x, in wrapper coordinates.
|
|
2167
2243
|
* @param params.top - Anchor y, in wrapper coordinates.
|
|
2168
2244
|
* @param params.offsetLeft - Gap between the anchor and the box, horizontally.
|
|
2169
2245
|
* @param params.offsetTop - Gap between the anchor and the box, vertically.
|
|
2170
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.
|
|
2171
2249
|
* @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
|
|
2172
|
-
* @return The box's top-left corner
|
|
2250
|
+
* @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
|
|
2173
2251
|
*/
|
|
2174
|
-
const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
|
|
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
|
+
};
|
|
2175
2263
|
const rightX = left + offsetLeft;
|
|
2176
2264
|
const leftX = left - offsetLeft - box.width;
|
|
2177
|
-
const rightOverflow = rightX + box.width -
|
|
2178
|
-
const leftOverflow =
|
|
2265
|
+
const rightOverflow = rightX + box.width - fit.right;
|
|
2266
|
+
const leftOverflow = fit.left - leftX;
|
|
2179
2267
|
let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
|
|
2180
2268
|
const downY = top + offsetTop;
|
|
2181
2269
|
const upY = top - offsetTop - box.height;
|
|
2182
|
-
const downOverflow = downY + box.height -
|
|
2183
|
-
const upOverflow =
|
|
2270
|
+
const downOverflow = downY + box.height - fit.bottom;
|
|
2271
|
+
const upOverflow = fit.top - upY;
|
|
2184
2272
|
let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
|
|
2185
|
-
x =
|
|
2186
|
-
y =
|
|
2273
|
+
x = clamp(x, bounds.left, bounds.right, box.width);
|
|
2274
|
+
y = clamp(y, bounds.top, bounds.bottom, box.height);
|
|
2187
2275
|
return {
|
|
2188
2276
|
x: Math.round(x),
|
|
2189
2277
|
y: Math.round(y)
|
|
@@ -2197,7 +2285,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
|
|
|
2197
2285
|
* alone would let the box spill into an `overflow: hidden` card and be cut off.
|
|
2198
2286
|
*
|
|
2199
2287
|
* Re-measures on every render, so a box whose content changes width between
|
|
2200
|
-
* two hovers is placed for its current size.
|
|
2288
|
+
* two hovers is placed for its current size. See `getBoundedPosition` for the
|
|
2289
|
+
* below-axis exception.
|
|
2201
2290
|
*
|
|
2202
2291
|
* @param props - visx `Tooltip` props.
|
|
2203
2292
|
* @param props.left - Anchor x, in wrapper coordinates.
|
|
@@ -2207,9 +2296,10 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
|
|
|
2207
2296
|
* @param props.style - Box styles; visx's defaults unless `unstyled`.
|
|
2208
2297
|
* @param props.unstyled - Skip `style` and leave the box bare.
|
|
2209
2298
|
* @param props.children - Box content.
|
|
2299
|
+
* @param props.placement - Fixed below-axis placement or automatic flipping.
|
|
2210
2300
|
* @return The tooltip box.
|
|
2211
2301
|
*/
|
|
2212
|
-
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, ...rest }) => {
|
|
2302
|
+
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
|
|
2213
2303
|
const nodeRef = useRef(null);
|
|
2214
2304
|
const clipRef = useRef(null);
|
|
2215
2305
|
const [position, setPosition] = useState(null);
|
|
@@ -2234,10 +2324,17 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2234
2324
|
top,
|
|
2235
2325
|
offsetLeft,
|
|
2236
2326
|
offsetTop,
|
|
2327
|
+
placement,
|
|
2237
2328
|
box: {
|
|
2238
2329
|
width: own.width,
|
|
2239
2330
|
height: own.height
|
|
2240
2331
|
},
|
|
2332
|
+
wrapper: {
|
|
2333
|
+
left: 0,
|
|
2334
|
+
top: 0,
|
|
2335
|
+
right: wrapperRect.width,
|
|
2336
|
+
bottom: wrapperRect.height
|
|
2337
|
+
},
|
|
2241
2338
|
bounds: {
|
|
2242
2339
|
left: clipRect.left - wrapperRect.left,
|
|
2243
2340
|
top: clipRect.top - wrapperRect.top,
|
|
@@ -2251,11 +2348,12 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2251
2348
|
top,
|
|
2252
2349
|
offsetLeft,
|
|
2253
2350
|
offsetTop,
|
|
2254
|
-
children
|
|
2351
|
+
children,
|
|
2352
|
+
placement
|
|
2255
2353
|
]);
|
|
2256
2354
|
const x = position?.x ?? left + offsetLeft;
|
|
2257
|
-
const y = position?.y ?? top + offsetTop;
|
|
2258
|
-
return /* @__PURE__ */
|
|
2355
|
+
const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
|
|
2356
|
+
return /* @__PURE__ */ jsxs(Tooltip, {
|
|
2259
2357
|
ref: nodeRef,
|
|
2260
2358
|
style: {
|
|
2261
2359
|
position: "absolute",
|
|
@@ -2265,12 +2363,40 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
|
|
|
2265
2363
|
...!unstyled && style
|
|
2266
2364
|
},
|
|
2267
2365
|
...rest,
|
|
2268
|
-
children
|
|
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]
|
|
2269
2379
|
});
|
|
2270
2380
|
};
|
|
2271
2381
|
//#endregion
|
|
2272
2382
|
//#region src/components/tooltip/xy-chart-tooltip.tsx
|
|
2273
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
|
+
};
|
|
2274
2400
|
const DEFAULT_GLYPH_RADIUS = 4;
|
|
2275
2401
|
const FALLBACK_COLOR = "#222";
|
|
2276
2402
|
const DEFAULT_TOOLTIP_Z_INDEX = 3;
|
|
@@ -2301,7 +2427,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ jsx(DefaultG
|
|
|
2301
2427
|
seriesKey: key,
|
|
2302
2428
|
...props
|
|
2303
2429
|
}, key);
|
|
2304
|
-
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 }) => {
|
|
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 }) => {
|
|
2305
2431
|
const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
|
|
2306
2432
|
const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = useContext(DataContext) || {};
|
|
2307
2433
|
const tooltipContent = renderTooltip ? renderTooltip({
|
|
@@ -2320,9 +2446,9 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2320
2446
|
};
|
|
2321
2447
|
const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
|
|
2322
2448
|
let { tooltipLeft, tooltipTop } = tooltipContext;
|
|
2323
|
-
if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
|
|
2449
|
+
if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
|
|
2324
2450
|
const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
|
|
2325
|
-
if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
|
|
2451
|
+
if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
|
|
2326
2452
|
if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
|
|
2327
2453
|
}
|
|
2328
2454
|
const glyphs = [];
|
|
@@ -2356,13 +2482,14 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2356
2482
|
const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
2357
2483
|
const marginTop = margin?.top ?? 0;
|
|
2358
2484
|
const marginLeft = margin?.left ?? 0;
|
|
2359
|
-
const TooltipComponent = detectBounds ? BoundedTooltip : Tooltip;
|
|
2485
|
+
const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : Tooltip;
|
|
2360
2486
|
const boxStyle = {
|
|
2361
2487
|
...defaultStyles,
|
|
2362
2488
|
zIndex,
|
|
2363
|
-
|
|
2364
|
-
boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
|
|
2365
|
-
...theme?.htmlLabel
|
|
2489
|
+
backgroundColor: theme?.backgroundColor ?? "white",
|
|
2490
|
+
boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
|
|
2491
|
+
...theme?.htmlLabel,
|
|
2492
|
+
...style
|
|
2366
2493
|
};
|
|
2367
2494
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
|
|
2368
2495
|
className: "visx-tooltip-overlay",
|
|
@@ -2370,32 +2497,33 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2370
2497
|
children: [
|
|
2371
2498
|
showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ jsx("line", {
|
|
2372
2499
|
className: "visx-crosshair visx-crosshair-vertical",
|
|
2500
|
+
stroke: crosshairStroke,
|
|
2501
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2502
|
+
...crosshairPaintProps(verticalCrosshairStyle),
|
|
2373
2503
|
x1: tooltipLeft,
|
|
2374
2504
|
x2: tooltipLeft,
|
|
2375
2505
|
y1: marginTop,
|
|
2376
|
-
y2: marginTop + innerHeight
|
|
2377
|
-
stroke: crosshairStroke,
|
|
2378
|
-
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2379
|
-
...verticalCrosshairStyle
|
|
2506
|
+
y2: marginTop + innerHeight
|
|
2380
2507
|
}),
|
|
2381
2508
|
showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ jsx("line", {
|
|
2382
2509
|
className: "visx-crosshair visx-crosshair-horizontal",
|
|
2510
|
+
stroke: crosshairStroke,
|
|
2511
|
+
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2512
|
+
...crosshairPaintProps(horizontalCrosshairStyle),
|
|
2383
2513
|
x1: marginLeft,
|
|
2384
2514
|
x2: marginLeft + innerWidth,
|
|
2385
2515
|
y1: tooltipTop,
|
|
2386
|
-
y2: tooltipTop
|
|
2387
|
-
stroke: crosshairStroke,
|
|
2388
|
-
strokeWidth: CROSSHAIR_STROKE_WIDTH,
|
|
2389
|
-
...horizontalCrosshairStyle
|
|
2516
|
+
y2: tooltipTop
|
|
2390
2517
|
}),
|
|
2391
2518
|
glyphs
|
|
2392
2519
|
]
|
|
2393
2520
|
}), container && createPortal(/* @__PURE__ */ jsx(TooltipComponent, {
|
|
2394
2521
|
left: tooltipLeft,
|
|
2395
|
-
top: tooltipTop,
|
|
2522
|
+
top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
|
|
2396
2523
|
style: boxStyle,
|
|
2397
2524
|
applyPositionStyle: true,
|
|
2398
2525
|
...tooltipProps,
|
|
2526
|
+
...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
|
|
2399
2527
|
children: tooltipContent
|
|
2400
2528
|
}), container)] });
|
|
2401
2529
|
};
|
|
@@ -2416,7 +2544,8 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2416
2544
|
* isolation keeps the box's `zIndex` from competing with page chrome. The box
|
|
2417
2545
|
* flips and clamps to stay inside the nearest ancestor that clips its overflow
|
|
2418
2546
|
* (or the viewport), so it may extend past the chart wrapper but is never cut
|
|
2419
|
-
* off unless that ancestor is smaller than the box itself.
|
|
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.
|
|
2420
2549
|
*
|
|
2421
2550
|
* @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
|
|
2422
2551
|
* @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
|
|
@@ -2533,12 +2662,16 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2533
2662
|
renderTooltip: focusableRenderTooltip
|
|
2534
2663
|
});
|
|
2535
2664
|
};
|
|
2536
|
-
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
|
|
2665
|
+
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
|
|
2537
2666
|
return {
|
|
2538
2667
|
tooltipRef: useCallback((element) => {
|
|
2539
|
-
if (element && selectedIndex !== void 0)
|
|
2540
|
-
|
|
2541
|
-
|
|
2668
|
+
if (element && selectedIndex !== void 0) {
|
|
2669
|
+
if (preventTooltipScroll) element.focus({ preventScroll: true });
|
|
2670
|
+
else element.focus();
|
|
2671
|
+
}
|
|
2672
|
+
}, [preventTooltipScroll, selectedIndex]),
|
|
2673
|
+
onChartFocus: useCallback((event) => {
|
|
2674
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
2542
2675
|
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
2543
2676
|
}, [
|
|
2544
2677
|
isNavigating,
|
|
@@ -2550,15 +2683,19 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2550
2683
|
}, [setIsNavigating]),
|
|
2551
2684
|
onChartKeyDown: useCallback((event) => {
|
|
2552
2685
|
if (totalPoints === 0) return;
|
|
2686
|
+
const focusChart = () => {
|
|
2687
|
+
if (preventTooltipScroll) chartRef.current?.focus({ preventScroll: true });
|
|
2688
|
+
else chartRef.current?.focus();
|
|
2689
|
+
};
|
|
2553
2690
|
if (event.key === "Tab") {
|
|
2554
|
-
|
|
2691
|
+
focusChart();
|
|
2555
2692
|
setSelectedIndex(void 0);
|
|
2556
2693
|
setIsNavigating(false);
|
|
2557
2694
|
return;
|
|
2558
2695
|
}
|
|
2559
2696
|
const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
|
|
2560
2697
|
if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
|
|
2561
|
-
|
|
2698
|
+
focusChart();
|
|
2562
2699
|
setSelectedIndex(void 0);
|
|
2563
2700
|
setIsNavigating(false);
|
|
2564
2701
|
return;
|
|
@@ -2573,7 +2710,7 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2573
2710
|
} else if (event.key === "Escape") {
|
|
2574
2711
|
setSelectedIndex(void 0);
|
|
2575
2712
|
setIsNavigating(false);
|
|
2576
|
-
|
|
2713
|
+
focusChart();
|
|
2577
2714
|
} else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
|
|
2578
2715
|
}, [
|
|
2579
2716
|
totalPoints,
|
|
@@ -2581,7 +2718,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2581
2718
|
setSelectedIndex,
|
|
2582
2719
|
setIsNavigating,
|
|
2583
2720
|
chartRef,
|
|
2584
|
-
onActivate
|
|
2721
|
+
onActivate,
|
|
2722
|
+
preventTooltipScroll
|
|
2585
2723
|
])
|
|
2586
2724
|
};
|
|
2587
2725
|
};
|
|
@@ -3869,10 +4007,11 @@ const TooltipDate = ({ date, displayResolution }) => {
|
|
|
3869
4007
|
* one row per visible series (label + formatted value), sorted descending by
|
|
3870
4008
|
* value. Reused by AreaChart, which has the same multi-series shape.
|
|
3871
4009
|
*
|
|
3872
|
-
* @param params
|
|
4010
|
+
* @param params - visx tooltip data and the chart's optional `bucketInfo`.
|
|
4011
|
+
* @param contentStyle - Explicit tooltip content color overrides.
|
|
3873
4012
|
* @return Tooltip JSX, or `null` when no datum is hovered.
|
|
3874
4013
|
*/
|
|
3875
|
-
const renderDefaultTooltip = (params) => {
|
|
4014
|
+
const renderDefaultTooltip = (params, contentStyle) => {
|
|
3876
4015
|
const { tooltipData, bucketInfo } = params;
|
|
3877
4016
|
const nearestDatum = tooltipData?.nearestDatum?.datum;
|
|
3878
4017
|
if (!nearestDatum) return null;
|
|
@@ -3882,6 +4021,7 @@ const renderDefaultTooltip = (params) => {
|
|
|
3882
4021
|
})).sort((a, b) => b.value - a.value);
|
|
3883
4022
|
return /* @__PURE__ */ jsxs("div", {
|
|
3884
4023
|
className: line_chart_module_default["line-chart__tooltip"],
|
|
4024
|
+
style: contentStyle,
|
|
3885
4025
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3886
4026
|
className: line_chart_module_default["line-chart__tooltip-date"],
|
|
3887
4027
|
children: /* @__PURE__ */ jsx(TooltipDate, {
|
|
@@ -3931,7 +4071,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
|
|
|
3931
4071
|
]);
|
|
3932
4072
|
return null;
|
|
3933
4073
|
};
|
|
3934
|
-
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) => {
|
|
4074
|
+
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) => {
|
|
3935
4075
|
const legendInteractive = legend.interactive ?? false;
|
|
3936
4076
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
3937
4077
|
const legendShape = legend.shape ?? "line";
|
|
@@ -4009,7 +4149,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4009
4149
|
setIsNavigating,
|
|
4010
4150
|
chartRef,
|
|
4011
4151
|
totalPoints: dataSorted[0]?.data.length || 0,
|
|
4012
|
-
onActivate: activateSelectedPoint
|
|
4152
|
+
onActivate: activateSelectedPoint,
|
|
4153
|
+
preventTooltipScroll: tooltipPlacement === "below-axis"
|
|
4013
4154
|
});
|
|
4014
4155
|
const chartOptions = useMemo(() => {
|
|
4015
4156
|
return {
|
|
@@ -4118,10 +4259,29 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4118
4259
|
xAccessor: (d) => d?.date,
|
|
4119
4260
|
yAccessor: (d) => d?.value
|
|
4120
4261
|
};
|
|
4121
|
-
const
|
|
4262
|
+
const resolvedTooltipStyle = useMemo(() => {
|
|
4263
|
+
if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
|
|
4264
|
+
if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
|
|
4265
|
+
return {
|
|
4266
|
+
backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
|
|
4267
|
+
...tooltipStyle
|
|
4268
|
+
};
|
|
4269
|
+
}, [renderTooltip, tooltipStyle]);
|
|
4270
|
+
const tooltipRenderer = useMemo(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
|
|
4122
4271
|
...params,
|
|
4123
4272
|
bucketInfo
|
|
4124
|
-
}
|
|
4273
|
+
}, {
|
|
4274
|
+
color: tooltipStyle?.color,
|
|
4275
|
+
background: tooltipStyle?.background,
|
|
4276
|
+
backgroundColor: tooltipStyle?.backgroundColor
|
|
4277
|
+
}) : renderTooltip({
|
|
4278
|
+
...params,
|
|
4279
|
+
bucketInfo
|
|
4280
|
+
}), [
|
|
4281
|
+
renderTooltip,
|
|
4282
|
+
bucketInfo,
|
|
4283
|
+
tooltipStyle
|
|
4284
|
+
]);
|
|
4125
4285
|
if (error) return /* @__PURE__ */ jsx("div", {
|
|
4126
4286
|
className: clsx("line-chart", line_chart_module_default["line-chart"]),
|
|
4127
4287
|
children: error
|
|
@@ -4162,6 +4322,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4162
4322
|
children: ({ contentHeight }) => {
|
|
4163
4323
|
const chartHeight = contentHeight > 0 ? contentHeight : height;
|
|
4164
4324
|
return /* @__PURE__ */ jsx("div", {
|
|
4325
|
+
ref: chartRef,
|
|
4165
4326
|
role: "grid",
|
|
4166
4327
|
"aria-label": __("Line chart", "jetpack-charts"),
|
|
4167
4328
|
tabIndex: 0,
|
|
@@ -4169,7 +4330,6 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4169
4330
|
onFocus: onChartFocus,
|
|
4170
4331
|
onBlur: onChartBlur,
|
|
4171
4332
|
children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
|
|
4172
|
-
ref: chartRef,
|
|
4173
4333
|
className: xy_plot_module_default["xy-plot"],
|
|
4174
4334
|
children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
|
|
4175
4335
|
theme,
|
|
@@ -4260,6 +4420,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4260
4420
|
withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
|
|
4261
4421
|
detectBounds: true,
|
|
4262
4422
|
snapTooltipToDatumX: true,
|
|
4423
|
+
tooltipPlacement,
|
|
4424
|
+
style: resolvedTooltipStyle,
|
|
4263
4425
|
snapTooltipToDatumY: true,
|
|
4264
4426
|
showSeriesGlyphs: true,
|
|
4265
4427
|
renderTooltip: tooltipRenderer,
|
|
@@ -4267,6 +4429,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4267
4429
|
glyphStyle,
|
|
4268
4430
|
showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
|
|
4269
4431
|
showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
|
|
4432
|
+
verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
|
|
4433
|
+
horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
|
|
4270
4434
|
selectedIndex,
|
|
4271
4435
|
tooltipRef,
|
|
4272
4436
|
keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
|
|
@@ -4663,6 +4827,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4663
4827
|
children: ({ contentHeight }) => {
|
|
4664
4828
|
const chartHeight = contentHeight > 0 ? contentHeight : height;
|
|
4665
4829
|
return /* @__PURE__ */ jsx("div", {
|
|
4830
|
+
ref: chartRef,
|
|
4666
4831
|
role: "grid",
|
|
4667
4832
|
"aria-label": __("Area chart", "jetpack-charts"),
|
|
4668
4833
|
tabIndex: 0,
|
|
@@ -4670,7 +4835,6 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
|
|
|
4670
4835
|
onFocus: onChartFocus,
|
|
4671
4836
|
onBlur: onChartBlur,
|
|
4672
4837
|
children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
|
|
4673
|
-
ref: chartRef,
|
|
4674
4838
|
className: xy_plot_module_default["xy-plot"],
|
|
4675
4839
|
children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
|
|
4676
4840
|
theme,
|
|
@@ -6419,9 +6583,13 @@ var heatmap_chart_module_default = {
|
|
|
6419
6583
|
"heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
|
|
6420
6584
|
"heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
|
|
6421
6585
|
"heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
|
|
6586
|
+
"heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
|
|
6422
6587
|
"heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
|
|
6423
6588
|
"heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
|
|
6589
|
+
"heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
|
|
6424
6590
|
"heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
|
|
6591
|
+
"heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
|
|
6592
|
+
"heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
|
|
6425
6593
|
"heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
|
|
6426
6594
|
"heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
|
|
6427
6595
|
"heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
|
|
@@ -6435,17 +6603,21 @@ var heatmap_chart_module_default = {
|
|
|
6435
6603
|
//#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
|
|
6436
6604
|
const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
|
|
6437
6605
|
/**
|
|
6438
|
-
* Get the min and max values from heatmap data, ignoring null/NaN.
|
|
6606
|
+
* Get the min and max values from heatmap data, ignoring null/NaN. Summary
|
|
6607
|
+
* columns stay out: a roll-up on the scale would flatten every real cell.
|
|
6439
6608
|
* @param data - The heatmap columns
|
|
6440
6609
|
* @return Tuple of [min, max] values
|
|
6441
6610
|
*/
|
|
6442
6611
|
const getValueExtent = (data) => {
|
|
6443
6612
|
let min = Infinity;
|
|
6444
6613
|
let max = -Infinity;
|
|
6445
|
-
for (const column of data)
|
|
6446
|
-
if (
|
|
6447
|
-
|
|
6448
|
-
|
|
6614
|
+
for (const column of data) {
|
|
6615
|
+
if (column.summary) continue;
|
|
6616
|
+
for (const cell of column.data) {
|
|
6617
|
+
if (!isPresent(cell.value)) continue;
|
|
6618
|
+
if (cell.value < min) min = cell.value;
|
|
6619
|
+
if (cell.value > max) max = cell.value;
|
|
6620
|
+
}
|
|
6449
6621
|
}
|
|
6450
6622
|
if (min === Infinity) return [0, 0];
|
|
6451
6623
|
return [min, max];
|
|
@@ -6466,101 +6638,6 @@ const getNormalizedValue = (value, extent) => {
|
|
|
6466
6638
|
return Math.min(1, Math.max(0, (value - min) / (max - min)));
|
|
6467
6639
|
};
|
|
6468
6640
|
//#endregion
|
|
6469
|
-
//#region src/charts/heatmap-chart/private/build-calendar-data.ts
|
|
6470
|
-
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
6471
|
-
const LABELLED_ROWS = [
|
|
6472
|
-
0,
|
|
6473
|
-
2,
|
|
6474
|
-
4
|
|
6475
|
-
];
|
|
6476
|
-
/**
|
|
6477
|
-
* Resolve one grid bound: an unparseable or narrowing value falls back to the
|
|
6478
|
-
* series' own bound, so the grid can only ever be widened.
|
|
6479
|
-
*
|
|
6480
|
-
* @param bound - Requested bound as `yyyy-MM-dd`, if any.
|
|
6481
|
-
* @param fallback - The series' own bound on this side.
|
|
6482
|
-
* @param direction - Which way the bound is allowed to move.
|
|
6483
|
-
* @return The bound to draw to.
|
|
6484
|
-
*/
|
|
6485
|
-
const widenTo = (bound, fallback, direction) => {
|
|
6486
|
-
if (!bound) return fallback;
|
|
6487
|
-
const parsed = parseISO(bound);
|
|
6488
|
-
if (isNaN(parsed.getTime())) return fallback;
|
|
6489
|
-
if (direction === "earlier") return parsed < fallback ? parsed : fallback;
|
|
6490
|
-
return parsed > fallback ? parsed : fallback;
|
|
6491
|
-
};
|
|
6492
|
-
const toDate = (point) => {
|
|
6493
|
-
if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
|
|
6494
|
-
if (point.dateString) {
|
|
6495
|
-
const parsed = parseISO(point.dateString);
|
|
6496
|
-
if (!isNaN(parsed.getTime())) return parsed;
|
|
6497
|
-
}
|
|
6498
|
-
return null;
|
|
6499
|
-
};
|
|
6500
|
-
const buildCalendarHeatmapData = (series, options = {}) => {
|
|
6501
|
-
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
6502
|
-
const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
|
|
6503
|
-
const entries = series.map((point) => ({
|
|
6504
|
-
date: toDate(point),
|
|
6505
|
-
value: point.value
|
|
6506
|
-
})).filter((entry) => entry.date !== null);
|
|
6507
|
-
if (!entries.length) return {
|
|
6508
|
-
data: [],
|
|
6509
|
-
rowLabels: []
|
|
6510
|
-
};
|
|
6511
|
-
const valueByDay = /* @__PURE__ */ new Map();
|
|
6512
|
-
let minDate = entries[0].date;
|
|
6513
|
-
let maxDate = entries[0].date;
|
|
6514
|
-
for (const { date, value } of entries) {
|
|
6515
|
-
valueByDay.set(format(date, "yyyy-MM-dd"), value);
|
|
6516
|
-
if (date < minDate) minDate = date;
|
|
6517
|
-
if (date > maxDate) maxDate = date;
|
|
6518
|
-
}
|
|
6519
|
-
const minDayKey = format(minDate, "yyyy-MM-dd");
|
|
6520
|
-
const maxDayKey = format(maxDate, "yyyy-MM-dd");
|
|
6521
|
-
const gridMinDate = widenTo(options.gridSpan?.start, minDate, "earlier");
|
|
6522
|
-
const gridMaxDate = widenTo(options.gridSpan?.end, maxDate, "later");
|
|
6523
|
-
const gridMaxDayKey = format(gridMaxDate, "yyyy-MM-dd");
|
|
6524
|
-
const gridStart = startOfWeek(gridMinDate, { weekStartsOn });
|
|
6525
|
-
const gridMinDayKey = format(gridMinDate < minDate ? gridStart : gridMinDate, "yyyy-MM-dd");
|
|
6526
|
-
const weekCount = differenceInCalendarWeeks(gridMaxDate, gridStart, { weekStartsOn }) + 1;
|
|
6527
|
-
const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? format(addDays(gridStart, row), "EEE") : "");
|
|
6528
|
-
const MIN_FIRST_MONTH_WEEKS = 2;
|
|
6529
|
-
const firstMonth = gridStart.getMonth();
|
|
6530
|
-
let firstMonthWeeks = 0;
|
|
6531
|
-
while (firstMonthWeeks < weekCount && addDays(gridStart, firstMonthWeeks * 7).getMonth() === firstMonth) firstMonthWeeks++;
|
|
6532
|
-
const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
|
|
6533
|
-
const data = [];
|
|
6534
|
-
let previousMonth = -1;
|
|
6535
|
-
for (let week = 0; week < weekCount; week++) {
|
|
6536
|
-
const columnStart = addDays(gridStart, week * 7);
|
|
6537
|
-
const month = columnStart.getMonth();
|
|
6538
|
-
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? format(columnStart, "MMM") : "";
|
|
6539
|
-
previousMonth = month;
|
|
6540
|
-
const cells = [];
|
|
6541
|
-
for (let row = 0; row < 7; row++) {
|
|
6542
|
-
const day = addDays(gridStart, week * 7 + row);
|
|
6543
|
-
const key = format(day, "yyyy-MM-dd");
|
|
6544
|
-
const cell = {
|
|
6545
|
-
label: format(day, "EEE, MMM d, yyyy"),
|
|
6546
|
-
value: valueByDay.has(key) ? valueByDay.get(key) : null
|
|
6547
|
-
};
|
|
6548
|
-
if (key < gridMinDayKey || key > gridMaxDayKey) {
|
|
6549
|
-
if (hideOutOfRangeDays) cell.hidden = true;
|
|
6550
|
-
} else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
|
|
6551
|
-
cells.push(cell);
|
|
6552
|
-
}
|
|
6553
|
-
data.push({
|
|
6554
|
-
label,
|
|
6555
|
-
data: cells
|
|
6556
|
-
});
|
|
6557
|
-
}
|
|
6558
|
-
return {
|
|
6559
|
-
data,
|
|
6560
|
-
rowLabels
|
|
6561
|
-
};
|
|
6562
|
-
};
|
|
6563
|
-
//#endregion
|
|
6564
6641
|
//#region src/charts/heatmap-chart/private/heatmap-context.ts
|
|
6565
6642
|
/** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
|
|
6566
6643
|
const HeatmapContext = createContext(null);
|
|
@@ -6610,7 +6687,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6610
6687
|
//#endregion
|
|
6611
6688
|
//#region src/charts/heatmap-chart/heatmap-chart.tsx
|
|
6612
6689
|
const CELL_MIX_FLOOR = .15;
|
|
6613
|
-
const
|
|
6690
|
+
const NO_ROW_LABELS = [];
|
|
6691
|
+
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 }) => {
|
|
6614
6692
|
const chartId = useChartId(providedChartId);
|
|
6615
6693
|
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
6616
6694
|
const scopeElement = useChartScopeElement();
|
|
@@ -6761,13 +6839,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6761
6839
|
const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
|
|
6762
6840
|
const gridStyle = {
|
|
6763
6841
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
6764
|
-
gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
|
|
6842
|
+
gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
|
|
6765
6843
|
gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
|
|
6766
6844
|
};
|
|
6767
6845
|
if (compact) {
|
|
6768
6846
|
gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
|
|
6769
6847
|
gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
|
|
6770
6848
|
}
|
|
6849
|
+
const summaryGaps = (columnIndex) => {
|
|
6850
|
+
if (!data[columnIndex]?.summary) return {};
|
|
6851
|
+
return {
|
|
6852
|
+
[heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
|
|
6853
|
+
[heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
|
|
6854
|
+
};
|
|
6855
|
+
};
|
|
6771
6856
|
const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
|
|
6772
6857
|
const heightCapped = !compact && Boolean(maxCellHeight);
|
|
6773
6858
|
return /* @__PURE__ */ jsx(HeatmapContext.Provider, {
|
|
@@ -6805,7 +6890,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6805
6890
|
"aria-hidden": "true",
|
|
6806
6891
|
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
6807
6892
|
children: [/* @__PURE__ */ jsx("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ jsx("span", {
|
|
6808
|
-
className: heatmap_chart_module_default["heatmap-chart__col-label"],
|
|
6893
|
+
className: clsx(heatmap_chart_module_default["heatmap-chart__col-label"], {
|
|
6894
|
+
[heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
|
|
6895
|
+
...summaryGaps(columnIndex)
|
|
6896
|
+
}),
|
|
6809
6897
|
children: column.label
|
|
6810
6898
|
}, `col-${columnIndex}`))]
|
|
6811
6899
|
}), Array.from({ length: rows }).map((_row, rowIndex) => {
|
|
@@ -6830,7 +6918,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6830
6918
|
}, `cell-${columnIndex}-${rowIndex}`);
|
|
6831
6919
|
const value = cell?.value ?? null;
|
|
6832
6920
|
const present = isPresent(value);
|
|
6833
|
-
const
|
|
6921
|
+
const filled = present && !column.summary;
|
|
6922
|
+
const normalized = filled ? getNormalizedValue(value, extent) : 0;
|
|
6834
6923
|
const flatIndex = columnIndex * rows + rowIndex;
|
|
6835
6924
|
const info = buildTooltipData(columnIndex, rowIndex);
|
|
6836
6925
|
const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value)}`;
|
|
@@ -6843,14 +6932,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6843
6932
|
"data-column": columnIndex,
|
|
6844
6933
|
"data-row": rowIndex,
|
|
6845
6934
|
className: clsx(heatmap_chart_module_default["heatmap-chart__cell"], {
|
|
6846
|
-
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]:
|
|
6847
|
-
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]:
|
|
6935
|
+
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
|
|
6936
|
+
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
|
|
6937
|
+
[heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
|
|
6938
|
+
...summaryGaps(columnIndex),
|
|
6848
6939
|
[heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
|
|
6849
6940
|
}),
|
|
6850
|
-
style:
|
|
6941
|
+
style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
|
|
6851
6942
|
onMouseMove: handleCellMouseMove,
|
|
6852
6943
|
onMouseLeave: handleCellMouseLeave,
|
|
6853
|
-
children: drawValues && present && /* @__PURE__ */ jsx("span", {
|
|
6944
|
+
children: (drawValues || column.summary) && present && /* @__PURE__ */ jsx("span", {
|
|
6854
6945
|
className: heatmap_chart_module_default["heatmap-chart__cell-value"],
|
|
6855
6946
|
children: formatNumberCompact(value)
|
|
6856
6947
|
})
|
|
@@ -6886,6 +6977,269 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ jsx(HeatmapChartW
|
|
|
6886
6977
|
HeatmapChartResponsiveInner.displayName = "HeatmapChart";
|
|
6887
6978
|
const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
|
|
6888
6979
|
//#endregion
|
|
6980
|
+
//#region src/charts/heatmap-chart/private/civil-day.ts
|
|
6981
|
+
const DAY_MS = 864e5;
|
|
6982
|
+
const KEY_OPTIONS = {
|
|
6983
|
+
calendar: "gregory",
|
|
6984
|
+
numberingSystem: "latn",
|
|
6985
|
+
year: "numeric",
|
|
6986
|
+
month: "2-digit",
|
|
6987
|
+
day: "2-digit"
|
|
6988
|
+
};
|
|
6989
|
+
const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
6990
|
+
const pad = (value, length) => String(value).padStart(length, "0");
|
|
6991
|
+
/**
|
|
6992
|
+
* The day a UTC proxy stands for.
|
|
6993
|
+
*
|
|
6994
|
+
* @param date - UTC proxy.
|
|
6995
|
+
* @return Its day key.
|
|
6996
|
+
*/
|
|
6997
|
+
const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
|
|
6998
|
+
/**
|
|
6999
|
+
* A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
|
|
7000
|
+
* `DAY_MS`.
|
|
7001
|
+
*
|
|
7002
|
+
* @param key - Day key.
|
|
7003
|
+
* @return The proxy, or null when the key names no real day.
|
|
7004
|
+
*/
|
|
7005
|
+
const civilDate = (key) => {
|
|
7006
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
|
7007
|
+
if (!match) return null;
|
|
7008
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
7009
|
+
date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
7010
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
7011
|
+
return civilKey(date) === key ? date : null;
|
|
7012
|
+
};
|
|
7013
|
+
/**
|
|
7014
|
+
* The day a written date names, ignoring any time and zone it carries.
|
|
7015
|
+
*
|
|
7016
|
+
* @param text - `yyyy-MM-dd`, optionally followed by a time.
|
|
7017
|
+
* @return Its day key, or null.
|
|
7018
|
+
*/
|
|
7019
|
+
const writtenDayKey = (text) => {
|
|
7020
|
+
const match = DATE_PREFIX.exec(text);
|
|
7021
|
+
if (!match) return null;
|
|
7022
|
+
const key = `${match[1]}-${match[2]}-${match[3]}`;
|
|
7023
|
+
return civilDate(key) ? key : null;
|
|
7024
|
+
};
|
|
7025
|
+
/**
|
|
7026
|
+
* Reads the day an instant falls on in a zone.
|
|
7027
|
+
*
|
|
7028
|
+
* @param timeZone - IANA zone name; absent leaves the runtime's own.
|
|
7029
|
+
* @return Reader taking a `Date`, null for a day no key can hold.
|
|
7030
|
+
*/
|
|
7031
|
+
const instantDayReader = (timeZone) => {
|
|
7032
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
7033
|
+
...KEY_OPTIONS,
|
|
7034
|
+
timeZone
|
|
7035
|
+
});
|
|
7036
|
+
return (date) => {
|
|
7037
|
+
const parts = formatter.formatToParts(date);
|
|
7038
|
+
const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
7039
|
+
const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
|
|
7040
|
+
return civilDate(key) ? key : null;
|
|
7041
|
+
};
|
|
7042
|
+
};
|
|
7043
|
+
/**
|
|
7044
|
+
* The day a series point belongs to.
|
|
7045
|
+
*
|
|
7046
|
+
* A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
|
|
7047
|
+
* zone. Anything else is a wall clock the host wrote, so its date part is the day.
|
|
7048
|
+
*
|
|
7049
|
+
* @param point - Series point.
|
|
7050
|
+
* @param readInstant - Reader from `instantDayReader`.
|
|
7051
|
+
* @return Its day key, or null when the point carries no usable date.
|
|
7052
|
+
*/
|
|
7053
|
+
const pointDayKey = (point, readInstant) => {
|
|
7054
|
+
if (point.date instanceof Date && !isNaN(point.date.getTime())) {
|
|
7055
|
+
const key = readInstant(point.date);
|
|
7056
|
+
if (key) return key;
|
|
7057
|
+
}
|
|
7058
|
+
const text = point.dateString;
|
|
7059
|
+
if (!text) return null;
|
|
7060
|
+
if (!hasTimezone(text)) return writtenDayKey(text);
|
|
7061
|
+
const parsed = parseISO(text);
|
|
7062
|
+
return isNaN(parsed.getTime()) ? null : readInstant(parsed);
|
|
7063
|
+
};
|
|
7064
|
+
/**
|
|
7065
|
+
* Steps a proxy by whole days.
|
|
7066
|
+
*
|
|
7067
|
+
* @param date - UTC proxy.
|
|
7068
|
+
* @param days - Days to add; may be negative.
|
|
7069
|
+
* @return The shifted proxy.
|
|
7070
|
+
*/
|
|
7071
|
+
const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
|
|
7072
|
+
/**
|
|
7073
|
+
* Rounds a proxy down to its week.
|
|
7074
|
+
*
|
|
7075
|
+
* @param date - UTC proxy.
|
|
7076
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
7077
|
+
* @return The proxy for that week's first day.
|
|
7078
|
+
*/
|
|
7079
|
+
const startOfCivilWeek = (date, weekStartsOn) => {
|
|
7080
|
+
const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
|
|
7081
|
+
return addCivilDays(date, -daysSinceWeekStart);
|
|
7082
|
+
};
|
|
7083
|
+
/**
|
|
7084
|
+
* Counts the columns a span needs.
|
|
7085
|
+
*
|
|
7086
|
+
* @param from - Earlier UTC proxy.
|
|
7087
|
+
* @param to - Later UTC proxy.
|
|
7088
|
+
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
7089
|
+
* @return Columns needed to cover the span, inclusive.
|
|
7090
|
+
*/
|
|
7091
|
+
const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
|
|
7092
|
+
//#endregion
|
|
7093
|
+
//#region src/charts/heatmap-chart/build-calendar-data.ts
|
|
7094
|
+
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
7095
|
+
const LABELLED_ROWS = [
|
|
7096
|
+
0,
|
|
7097
|
+
2,
|
|
7098
|
+
4
|
|
7099
|
+
];
|
|
7100
|
+
/**
|
|
7101
|
+
* Resolve one grid bound: an unparseable or narrowing value falls back to the
|
|
7102
|
+
* series' own bound, so the grid can only ever be widened.
|
|
7103
|
+
*
|
|
7104
|
+
* @param bound - Requested bound as `yyyy-MM-dd`, if any.
|
|
7105
|
+
* @param fallback - The series' own bound on this side.
|
|
7106
|
+
* @param direction - Which way the bound is allowed to move.
|
|
7107
|
+
* @return The day key to draw to.
|
|
7108
|
+
*/
|
|
7109
|
+
const widenTo = (bound, fallback, direction) => {
|
|
7110
|
+
const key = bound ? writtenDayKey(bound) : null;
|
|
7111
|
+
if (!key) {
|
|
7112
|
+
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.`);
|
|
7113
|
+
return fallback;
|
|
7114
|
+
}
|
|
7115
|
+
if (direction === "earlier") return key < fallback ? key : fallback;
|
|
7116
|
+
return key > fallback ? key : fallback;
|
|
7117
|
+
};
|
|
7118
|
+
/**
|
|
7119
|
+
* Lay a day-bucketed series out as calendar columns.
|
|
7120
|
+
*
|
|
7121
|
+
* @param series - Points to bucket.
|
|
7122
|
+
* @param options - Grid shape, plus the locale and zone to read days in.
|
|
7123
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
7124
|
+
*/
|
|
7125
|
+
const buildCalendarHeatmapData = (series, options = {}) => {
|
|
7126
|
+
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
7127
|
+
const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
|
|
7128
|
+
const { locale, timeZone } = sanitizeFormatting({
|
|
7129
|
+
locale: options.locale,
|
|
7130
|
+
timeZone: options.timeZone
|
|
7131
|
+
});
|
|
7132
|
+
const readInstant = instantDayReader(timeZone);
|
|
7133
|
+
const valueByDay = /* @__PURE__ */ new Map();
|
|
7134
|
+
let minDayKey;
|
|
7135
|
+
let maxDayKey;
|
|
7136
|
+
for (const point of series) {
|
|
7137
|
+
const key = pointDayKey(point, readInstant);
|
|
7138
|
+
if (!key) {
|
|
7139
|
+
const offending = point.dateString ?? (point.date && String(point.date));
|
|
7140
|
+
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.");
|
|
7141
|
+
continue;
|
|
7142
|
+
}
|
|
7143
|
+
valueByDay.set(key, point.value);
|
|
7144
|
+
if (!minDayKey || key < minDayKey) minDayKey = key;
|
|
7145
|
+
if (!maxDayKey || key > maxDayKey) maxDayKey = key;
|
|
7146
|
+
}
|
|
7147
|
+
if (!minDayKey || !maxDayKey) return {
|
|
7148
|
+
data: [],
|
|
7149
|
+
rowLabels: []
|
|
7150
|
+
};
|
|
7151
|
+
const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
|
|
7152
|
+
const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
|
|
7153
|
+
const requestedMinDate = civilDate(requestedMinDayKey);
|
|
7154
|
+
const gridMaxDate = civilDate(gridMaxDayKey);
|
|
7155
|
+
const labelFormatting = {
|
|
7156
|
+
locale,
|
|
7157
|
+
timeZone: "UTC"
|
|
7158
|
+
};
|
|
7159
|
+
const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
|
|
7160
|
+
const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
|
|
7161
|
+
const formatDay = createDateFormatter({
|
|
7162
|
+
weekday: "short",
|
|
7163
|
+
month: "short",
|
|
7164
|
+
day: "numeric",
|
|
7165
|
+
year: "numeric"
|
|
7166
|
+
}, labelFormatting);
|
|
7167
|
+
const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
|
|
7168
|
+
const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
|
|
7169
|
+
const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
|
|
7170
|
+
const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
|
|
7171
|
+
const MIN_FIRST_MONTH_WEEKS = 2;
|
|
7172
|
+
const firstMonth = gridStart.getUTCMonth();
|
|
7173
|
+
let firstMonthWeeks = 0;
|
|
7174
|
+
while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
|
|
7175
|
+
const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
|
|
7176
|
+
const data = [];
|
|
7177
|
+
let previousMonth = -1;
|
|
7178
|
+
for (let week = 0; week < weekCount; week++) {
|
|
7179
|
+
const columnStart = addCivilDays(gridStart, week * 7);
|
|
7180
|
+
const month = columnStart.getUTCMonth();
|
|
7181
|
+
const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
|
|
7182
|
+
previousMonth = month;
|
|
7183
|
+
const cells = [];
|
|
7184
|
+
for (let row = 0; row < 7; row++) {
|
|
7185
|
+
const day = addCivilDays(gridStart, week * 7 + row);
|
|
7186
|
+
const key = civilKey(day);
|
|
7187
|
+
const cell = {
|
|
7188
|
+
label: formatDay(day),
|
|
7189
|
+
value: valueByDay.has(key) ? valueByDay.get(key) : null
|
|
7190
|
+
};
|
|
7191
|
+
if (key < gridMinDayKey || key > gridMaxDayKey) {
|
|
7192
|
+
if (hideOutOfRangeDays) cell.hidden = true;
|
|
7193
|
+
} else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
|
|
7194
|
+
cells.push(cell);
|
|
7195
|
+
}
|
|
7196
|
+
data.push({
|
|
7197
|
+
label,
|
|
7198
|
+
data: cells
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
return {
|
|
7202
|
+
data,
|
|
7203
|
+
rowLabels
|
|
7204
|
+
};
|
|
7205
|
+
};
|
|
7206
|
+
//#endregion
|
|
7207
|
+
//#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
|
|
7208
|
+
/**
|
|
7209
|
+
* `buildCalendarHeatmapData` with the locale and zone taken from
|
|
7210
|
+
* `GlobalChartsProvider` where the caller names neither.
|
|
7211
|
+
*
|
|
7212
|
+
* @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
|
|
7213
|
+
* @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
|
|
7214
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
7215
|
+
*/
|
|
7216
|
+
const useCalendarHeatmapData = (series, options = {}) => {
|
|
7217
|
+
const formatting = useChartFormatting();
|
|
7218
|
+
const locale = options.locale ?? formatting.locale;
|
|
7219
|
+
const timeZone = options.timeZone ?? formatting.timeZone;
|
|
7220
|
+
const { weekStartsOn, hideOutOfRangeDays } = options;
|
|
7221
|
+
const gridStart = options.gridSpan?.start;
|
|
7222
|
+
const gridEnd = options.gridSpan?.end;
|
|
7223
|
+
return useMemo(() => buildCalendarHeatmapData(series, {
|
|
7224
|
+
locale,
|
|
7225
|
+
timeZone,
|
|
7226
|
+
weekStartsOn,
|
|
7227
|
+
hideOutOfRangeDays,
|
|
7228
|
+
gridSpan: {
|
|
7229
|
+
start: gridStart,
|
|
7230
|
+
end: gridEnd
|
|
7231
|
+
}
|
|
7232
|
+
}), [
|
|
7233
|
+
series,
|
|
7234
|
+
locale,
|
|
7235
|
+
timeZone,
|
|
7236
|
+
weekStartsOn,
|
|
7237
|
+
hideOutOfRangeDays,
|
|
7238
|
+
gridStart,
|
|
7239
|
+
gridEnd
|
|
7240
|
+
]);
|
|
7241
|
+
};
|
|
7242
|
+
//#endregion
|
|
6889
7243
|
//#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
|
|
6890
7244
|
/*!
|
|
6891
7245
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
@@ -6953,7 +7307,7 @@ function paramCase(input, options) {
|
|
|
6953
7307
|
return dotCase(input, __assign({ delimiter: "-" }, options));
|
|
6954
7308
|
}
|
|
6955
7309
|
//#endregion
|
|
6956
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
7310
|
+
//#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
|
|
6957
7311
|
function useUpdateEffect(effect, deps) {
|
|
6958
7312
|
const mountedRef = useRef$1(false);
|
|
6959
7313
|
useEffect$1(() => {
|
|
@@ -8523,7 +8877,7 @@ _createEmotion.css;
|
|
|
8523
8877
|
_createEmotion.sheet;
|
|
8524
8878
|
_createEmotion.cache;
|
|
8525
8879
|
//#endregion
|
|
8526
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
8880
|
+
//#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
|
|
8527
8881
|
var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
|
|
8528
8882
|
var useCx = () => {
|
|
8529
8883
|
const cache = __unsafe_useEmotionCache();
|
|
@@ -8630,7 +8984,7 @@ function memize(fn, options) {
|
|
|
8630
8984
|
return memoized;
|
|
8631
8985
|
}
|
|
8632
8986
|
//#endregion
|
|
8633
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
8987
|
+
//#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
|
|
8634
8988
|
var white = "#fff";
|
|
8635
8989
|
var GRAY = {
|
|
8636
8990
|
900: "#1e1e1e",
|
|
@@ -8709,7 +9063,7 @@ var COLORS = Object.freeze({
|
|
|
8709
9063
|
ui: UI
|
|
8710
9064
|
});
|
|
8711
9065
|
//#endregion
|
|
8712
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9066
|
+
//#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
|
|
8713
9067
|
var CONTROL_HEIGHT = "36px";
|
|
8714
9068
|
var CONTROL_PROPS = {
|
|
8715
9069
|
controlPaddingX: 12,
|
|
@@ -8827,7 +9181,7 @@ function warning(message) {
|
|
|
8827
9181
|
logged.add(message);
|
|
8828
9182
|
}
|
|
8829
9183
|
//#endregion
|
|
8830
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9184
|
+
//#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
|
|
8831
9185
|
var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
|
|
8832
9186
|
var ComponentsContext = createContext$1(
|
|
8833
9187
|
/** @type {Record<string, any>} */
|
|
@@ -8854,18 +9208,18 @@ var BaseContextSystemProvider = ({ children, value }) => {
|
|
|
8854
9208
|
};
|
|
8855
9209
|
memo(BaseContextSystemProvider);
|
|
8856
9210
|
//#endregion
|
|
8857
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9211
|
+
//#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
|
|
8858
9212
|
var COMPONENT_NAMESPACE = "data-wp-component";
|
|
8859
9213
|
var CONNECTED_NAMESPACE = "data-wp-c16t";
|
|
8860
9214
|
var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
|
|
8861
9215
|
//#endregion
|
|
8862
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
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/get-styled-class-name-from-key.mjs
|
|
8863
9217
|
function getStyledClassName(namespace) {
|
|
8864
9218
|
return `components-${paramCase(namespace)}`;
|
|
8865
9219
|
}
|
|
8866
9220
|
var getStyledClassNameFromKey = memize(getStyledClassName);
|
|
8867
9221
|
//#endregion
|
|
8868
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9222
|
+
//#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
|
|
8869
9223
|
function contextConnect(Component, namespace) {
|
|
8870
9224
|
return _contextConnect(Component, namespace, { forwardsRef: true });
|
|
8871
9225
|
}
|
|
@@ -8882,7 +9236,7 @@ function _contextConnect(Component, namespace, options) {
|
|
|
8882
9236
|
});
|
|
8883
9237
|
}
|
|
8884
9238
|
//#endregion
|
|
8885
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9239
|
+
//#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
|
|
8886
9240
|
function getNamespace(componentName) {
|
|
8887
9241
|
return { [COMPONENT_NAMESPACE]: componentName };
|
|
8888
9242
|
}
|
|
@@ -8890,7 +9244,7 @@ function getConnectedNamespace() {
|
|
|
8890
9244
|
return { [CONNECTED_NAMESPACE]: true };
|
|
8891
9245
|
}
|
|
8892
9246
|
//#endregion
|
|
8893
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9247
|
+
//#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
|
|
8894
9248
|
function useContextSystem(props, namespace) {
|
|
8895
9249
|
const contextSystemProps = useComponentsContext();
|
|
8896
9250
|
if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
|
|
@@ -8910,7 +9264,7 @@ function useContextSystem(props, namespace) {
|
|
|
8910
9264
|
return finalComponentProps;
|
|
8911
9265
|
}
|
|
8912
9266
|
//#endregion
|
|
8913
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9267
|
+
//#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
|
|
8914
9268
|
var visuallyHidden = {
|
|
8915
9269
|
border: 0,
|
|
8916
9270
|
clip: "rect(1px, 1px, 1px, 1px)",
|
|
@@ -8926,7 +9280,7 @@ var visuallyHidden = {
|
|
|
8926
9280
|
wordBreak: "normal"
|
|
8927
9281
|
};
|
|
8928
9282
|
//#endregion
|
|
8929
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9283
|
+
//#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
|
|
8930
9284
|
var customAttributeRegExp = /^(data|aria|x)-/i;
|
|
8931
9285
|
var eventHandlerRegExp = /^on[A-Z]/;
|
|
8932
9286
|
var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
|
|
@@ -9016,7 +9370,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
|
|
|
9016
9370
|
}
|
|
9017
9371
|
var PolymorphicElement = forwardRef$1(UnforwardedPolymorphicElement);
|
|
9018
9372
|
//#endregion
|
|
9019
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9373
|
+
//#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
|
|
9020
9374
|
function UnforwardedView({ css, ...restProps }, ref) {
|
|
9021
9375
|
return /* @__PURE__ */ jsx(PolymorphicElement, {
|
|
9022
9376
|
ref,
|
|
@@ -9025,7 +9379,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
|
|
|
9025
9379
|
}
|
|
9026
9380
|
var component_default$2 = Object.assign(forwardRef$1(UnforwardedView), { selector: ".components-view" });
|
|
9027
9381
|
//#endregion
|
|
9028
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9382
|
+
//#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
|
|
9029
9383
|
function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
9030
9384
|
const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
|
|
9031
9385
|
return /* @__PURE__ */ jsx(component_default$2, {
|
|
@@ -9040,7 +9394,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
|
9040
9394
|
}
|
|
9041
9395
|
var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
|
|
9042
9396
|
//#endregion
|
|
9043
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9397
|
+
//#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
|
|
9044
9398
|
var breakpoints = [
|
|
9045
9399
|
"40em",
|
|
9046
9400
|
"52em",
|
|
@@ -9074,7 +9428,7 @@ function useResponsiveValue(values, options = {}) {
|
|
|
9074
9428
|
return array[index >= array.length ? array.length - 1 : index];
|
|
9075
9429
|
}
|
|
9076
9430
|
//#endregion
|
|
9077
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9431
|
+
//#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
|
|
9078
9432
|
var ALIGNMENTS = {
|
|
9079
9433
|
bottom: {
|
|
9080
9434
|
alignItems: "flex-end",
|
|
@@ -9122,7 +9476,7 @@ function getAlignmentProps(alignment) {
|
|
|
9122
9476
|
return alignment ? ALIGNMENTS[alignment] : {};
|
|
9123
9477
|
}
|
|
9124
9478
|
//#endregion
|
|
9125
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9479
|
+
//#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
|
|
9126
9480
|
function useGrid(props) {
|
|
9127
9481
|
const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
|
|
9128
9482
|
const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
|
|
@@ -9164,7 +9518,7 @@ function useGrid(props) {
|
|
|
9164
9518
|
};
|
|
9165
9519
|
}
|
|
9166
9520
|
//#endregion
|
|
9167
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@
|
|
9521
|
+
//#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
|
|
9168
9522
|
function UnconnectedGrid(props, forwardedRef) {
|
|
9169
9523
|
const gridProps = useGrid(props);
|
|
9170
9524
|
return /* @__PURE__ */ jsx(component_default$2, {
|
|
@@ -10420,6 +10774,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
|
|
|
10420
10774
|
});
|
|
10421
10775
|
}
|
|
10422
10776
|
//#endregion
|
|
10423
|
-
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, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
|
|
10777
|
+
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 };
|
|
10424
10778
|
|
|
10425
10779
|
//# sourceMappingURL=index.js.map
|