@automattic/charts 4.1.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/TOKENS.md +1 -1
- package/dist/index.cjs +1335 -348
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +30 -1
- package/dist/index.d.cts +129 -8
- package/dist/index.d.ts +129 -8
- package/dist/index.js +1336 -351
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/charts/area-chart/area-chart.tsx +42 -10
- package/src/charts/area-chart/private/overlays.tsx +1 -2
- package/src/charts/area-chart/private/validate-data.ts +2 -3
- package/src/charts/area-chart/test/area-chart.test.tsx +72 -1
- package/src/charts/bar-chart/bar-chart.tsx +137 -70
- package/src/charts/bar-chart/index.ts +1 -1
- package/src/charts/bar-chart/private/band-highlight.tsx +87 -0
- package/src/charts/bar-chart/private/band-scale.ts +13 -0
- package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
- package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
- package/src/charts/bar-chart/private/index.ts +3 -0
- package/src/charts/bar-chart/private/test/band-highlight.test.tsx +135 -0
- package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
- package/src/charts/bar-chart/private/test/value-domain.test.ts +100 -0
- package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +13 -40
- package/src/charts/bar-chart/private/value-domain.ts +90 -0
- package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
- package/src/charts/bar-chart/test/bar-chart.test.tsx +1058 -19
- package/src/charts/bar-chart/types.ts +39 -0
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
- package/src/charts/conversion-funnel-chart/types.ts +4 -2
- package/src/charts/geo-chart/types.ts +4 -2
- package/src/charts/heatmap-chart/build-calendar-data.ts +5 -11
- package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
- package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
- package/src/charts/heatmap-chart/index.ts +6 -0
- package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
- package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
- package/src/charts/heatmap-chart/private/index.ts +2 -0
- package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +36 -0
- package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
- package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
- package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
- package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
- package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
- package/src/charts/heatmap-chart/types.ts +59 -3
- package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
- package/src/charts/leaderboard-chart/types.ts +13 -14
- package/src/charts/line-chart/line-chart.tsx +149 -36
- package/src/charts/line-chart/private/index.ts +1 -0
- package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
- package/src/charts/line-chart/private/line-chart-glyph.tsx +17 -3
- package/src/charts/line-chart/private/nearest-pointer-events.tsx +155 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +604 -9
- package/src/charts/line-chart/types.ts +4 -3
- package/src/charts/private/readings.ts +34 -0
- package/src/charts/private/test/readings.test.ts +29 -0
- package/src/charts/private/test/whole-number-ticks.test.tsx +57 -0
- package/src/charts/private/time-axis-options.ts +1 -1
- package/src/charts/private/whole-number-ticks.tsx +79 -0
- package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
- package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
- package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +124 -0
- package/src/components/legend/hooks/use-chart-legend-items.ts +60 -2
- package/src/components/legend/private/base-legend.tsx +37 -22
- package/src/components/legend/test/legend.test.tsx +84 -0
- package/src/components/legend/types.ts +2 -0
- package/src/components/legend/utils/index.ts +0 -1
- package/src/components/tooltip/accessible-tooltip.tsx +128 -41
- package/src/components/tooltip/base-tooltip.module.scss +6 -1
- package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +128 -0
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +7 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +9 -10
- package/src/hooks/test/use-chart-margin.test.tsx +35 -0
- package/src/hooks/test/use-element-size.test.tsx +13 -13
- package/src/hooks/use-chart-margin.tsx +7 -2
- package/src/hooks/use-xychart-theme.ts +1 -1
- package/src/index.ts +7 -1
- package/src/providers/chart-context/global-charts-provider.tsx +1 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +4 -4
- package/src/providers/chart-context/themes.ts +1 -0
- package/src/styles/test/chart-scope.test.ts +1 -1
- package/src/types.ts +14 -13
- package/src/utils/format-metric-value.ts +4 -4
- package/src/utils/get-edge-tick-widths.ts +1 -1
- package/src/utils/get-styles.ts +1 -1
- package/src/visx/types.ts +7 -10
- package/src/components/legend/utils/label-transform-factory.ts +0 -24
package/dist/index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ let _visx_gradient = require("@visx/gradient");
|
|
|
46
46
|
let _visx_curve = require("@visx/curve");
|
|
47
47
|
let _visx_responsive = require("@visx/responsive");
|
|
48
48
|
let _visx_annotation = require("@visx/annotation");
|
|
49
|
+
let _visx_vendor_d3_array = require("@visx/vendor/d3-array");
|
|
49
50
|
let _visx_pattern = require("@visx/pattern");
|
|
50
51
|
let react_google_charts = require("react-google-charts");
|
|
51
52
|
let dompurify = require("dompurify");
|
|
@@ -983,7 +984,8 @@ const defaultTheme = {
|
|
|
983
984
|
},
|
|
984
985
|
heatmapChart: {
|
|
985
986
|
compactCellGap: 2,
|
|
986
|
-
compactCellSize: 11
|
|
987
|
+
compactCellSize: 11,
|
|
988
|
+
groupGap: 24
|
|
987
989
|
}
|
|
988
990
|
};
|
|
989
991
|
//#endregion
|
|
@@ -1075,7 +1077,7 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
|
|
|
1075
1077
|
return getChartColor(index, colorCache);
|
|
1076
1078
|
}, [colorCache, groupToColorMap]);
|
|
1077
1079
|
const getElementStyles = (0, react.useCallback)(({ data, index, overrideColor, legendShape }) => {
|
|
1078
|
-
const isSeriesData = data && typeof data === "object" && "data" in data
|
|
1080
|
+
const isSeriesData = data && typeof data === "object" && "data" in data;
|
|
1079
1081
|
const isPointPercentageData = data && typeof data === "object" && "value" in data && typeof data.value === "number" && !("data" in data);
|
|
1080
1082
|
return {
|
|
1081
1083
|
color: resolveColor({
|
|
@@ -1456,8 +1458,9 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
|
|
|
1456
1458
|
const allDataPoints = data.flatMap((series) => series.data);
|
|
1457
1459
|
if (horizontal) return allDataPoints.map((d) => d.label || d.date?.getTime());
|
|
1458
1460
|
if (options.axis?.y?.tickValues?.length) return options.axis.y.tickValues;
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
+
const values = allDataPoints.map((d) => d.value).filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
1462
|
+
const minY = values.length ? Math.min(...values) : 0;
|
|
1463
|
+
const maxY = values.length ? Math.max(...values) : 1;
|
|
1461
1464
|
const yScale = (0, _visx_scale.createScale)({
|
|
1462
1465
|
...options.yScale,
|
|
1463
1466
|
domain: options.yScale?.domain ?? [minY, maxY],
|
|
@@ -1792,23 +1795,6 @@ function valueOrIdentityString(_) {
|
|
|
1792
1795
|
return String(valueOrIdentity(_));
|
|
1793
1796
|
}
|
|
1794
1797
|
//#endregion
|
|
1795
|
-
//#region src/components/legend/utils/label-transform-factory.ts
|
|
1796
|
-
/**
|
|
1797
|
-
* Returns a function which takes a Datum and index as input, and returns a formatted label object.
|
|
1798
|
-
* @param {object} root0 - The object to return the value of.
|
|
1799
|
-
* @param {AnyD3Scale} root0.scale - The scale to use.
|
|
1800
|
-
* @param {LabelFormatter<ScaleInput<Scale>>} root0.labelFormat - The label format to use.
|
|
1801
|
-
* @return {ItemTransformer<ScaleInput<Scale>, ReturnType<Scale>>} The label transform factory.
|
|
1802
|
-
*/
|
|
1803
|
-
function labelTransformFactory({ scale, labelFormat }) {
|
|
1804
|
-
return (d, i) => ({
|
|
1805
|
-
datum: d,
|
|
1806
|
-
index: i,
|
|
1807
|
-
text: `${labelFormat(d, i)}`,
|
|
1808
|
-
value: scale(d)
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
//#endregion
|
|
1812
1798
|
//#region src/components/legend/private/base-legend.module.scss
|
|
1813
1799
|
var base_legend_module_default = {
|
|
1814
1800
|
"legend": "a8ccharts-04TogW-legend",
|
|
@@ -1850,7 +1836,7 @@ const getLegendItemAriaLabel = (text, value, visible, interactive) => {
|
|
|
1850
1836
|
if (visible) return;
|
|
1851
1837
|
return (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n._x)("%s: hidden", "hidden non-interactive legend item", "jetpack-charts"), accessibleText);
|
|
1852
1838
|
};
|
|
1853
|
-
const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform
|
|
1839
|
+
const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform, itemStyles, itemClassName, labelStyles, labelClassName, shapeStyles, render, interactive = false, chartId }, ref) => {
|
|
1854
1840
|
const { margin: itemMargin = "0", flexDirection: itemDirection = "row" } = itemStyles ?? {};
|
|
1855
1841
|
const { justifyContent: labelJustifyContent = "flex-start", flex: labelFlex = "0 0 auto", margin: labelMargin = "0 4px", maxWidth, textOverflow = "wrap" } = labelStyles ?? {};
|
|
1856
1842
|
const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
|
|
@@ -1858,11 +1844,17 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
|
|
|
1858
1844
|
const context = (0, react.useContext)(GlobalChartsContext);
|
|
1859
1845
|
const chartInstanceContext = (0, react.useContext)(ChartInstanceContext);
|
|
1860
1846
|
const standaloneScopeClass = useStandaloneScopeClass();
|
|
1847
|
+
const domain = items.map((item) => item.label);
|
|
1861
1848
|
const legendScale = (0, _visx_scale.scaleOrdinal)({
|
|
1862
|
-
domain
|
|
1849
|
+
domain,
|
|
1863
1850
|
range: items.map((item) => item.color)
|
|
1864
1851
|
});
|
|
1865
|
-
const
|
|
1852
|
+
const defaultLabelTransform = (0, react.useCallback)(({ labelFormat: format }) => (datum, index) => ({
|
|
1853
|
+
datum,
|
|
1854
|
+
index,
|
|
1855
|
+
text: String(format(datum, index)),
|
|
1856
|
+
value: items[index].color
|
|
1857
|
+
}), [items]);
|
|
1866
1858
|
const getShapeStyle = (0, react.useCallback)(({ index }) => items[index]?.shapeStyle, [items]);
|
|
1867
1859
|
const handleLegendClick = (0, react.useCallback)((seriesLabels) => {
|
|
1868
1860
|
if (interactive && chartId && context) {
|
|
@@ -1883,24 +1875,26 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
|
|
|
1883
1875
|
chartInstanceContext,
|
|
1884
1876
|
context
|
|
1885
1877
|
]);
|
|
1886
|
-
const createClickHandler = (0, react.useCallback)((seriesLabels) => {
|
|
1887
|
-
if (!
|
|
1878
|
+
const createClickHandler = (0, react.useCallback)((seriesLabels, itemInteractive) => {
|
|
1879
|
+
if (!itemInteractive) return;
|
|
1888
1880
|
return () => handleLegendClick(seriesLabels);
|
|
1889
|
-
}, [
|
|
1890
|
-
const createKeyDownHandler = (0, react.useCallback)((seriesLabels) => {
|
|
1891
|
-
if (!
|
|
1881
|
+
}, [handleLegendClick]);
|
|
1882
|
+
const createKeyDownHandler = (0, react.useCallback)((seriesLabels, itemInteractive) => {
|
|
1883
|
+
if (!itemInteractive) return;
|
|
1892
1884
|
return (event) => {
|
|
1893
1885
|
if (event.key === "Enter" || event.key === " ") {
|
|
1894
1886
|
event.preventDefault();
|
|
1895
1887
|
handleLegendClick(seriesLabels);
|
|
1896
1888
|
}
|
|
1897
1889
|
};
|
|
1898
|
-
}, [
|
|
1890
|
+
}, [handleLegendClick]);
|
|
1899
1891
|
const flexAlignment = ALIGNMENT_TO_FLEX[alignment] ?? "center";
|
|
1892
|
+
const staticItemRole = interactive ? void 0 : "listitem";
|
|
1900
1893
|
return render ? render(items) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_legend.LegendOrdinal, {
|
|
1901
1894
|
scale: legendScale,
|
|
1895
|
+
domain,
|
|
1902
1896
|
labelFormat,
|
|
1903
|
-
labelTransform,
|
|
1897
|
+
labelTransform: labelTransform ?? defaultLabelTransform,
|
|
1904
1898
|
children: (labels) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
1905
1899
|
ref,
|
|
1906
1900
|
direction: orientation === "vertical" ? "column" : "row",
|
|
@@ -1914,19 +1908,20 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
|
|
|
1914
1908
|
children: labels.map((label, i) => {
|
|
1915
1909
|
const matchedItem = items[i];
|
|
1916
1910
|
const seriesLabels = matchedItem?.seriesLabels?.length ? matchedItem.seriesLabels : [label.text];
|
|
1917
|
-
const visible = isSeriesVisible(seriesLabels[0]);
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1911
|
+
const visible = matchedItem?.interactive === false || isSeriesVisible(seriesLabels[0]);
|
|
1912
|
+
const itemInteractive = interactive && matchedItem?.interactive !== false;
|
|
1913
|
+
const handleClick = createClickHandler(seriesLabels, itemInteractive);
|
|
1914
|
+
const handleKeyDown = createKeyDownHandler(seriesLabels, itemInteractive);
|
|
1920
1915
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_legend.LegendItem, {
|
|
1921
|
-
className: (0, clsx.default)("visx-legend-item", base_legend_module_default["legend-item"],
|
|
1916
|
+
className: (0, clsx.default)("visx-legend-item", base_legend_module_default["legend-item"], itemInteractive && base_legend_module_default["legend-item--interactive"], !visible && base_legend_module_default["legend-item--inactive"], itemClassName),
|
|
1922
1917
|
margin: itemMargin,
|
|
1923
1918
|
flexDirection: orientation === "vertical" && alignment === "end" ? "row-reverse" : itemDirection,
|
|
1924
1919
|
onClick: handleClick,
|
|
1925
1920
|
onKeyDown: handleKeyDown,
|
|
1926
|
-
role:
|
|
1927
|
-
tabIndex:
|
|
1928
|
-
"aria-pressed":
|
|
1929
|
-
"aria-label": getLegendItemAriaLabel(label.text, matchedItem?.value, visible,
|
|
1921
|
+
role: itemInteractive ? "button" : staticItemRole,
|
|
1922
|
+
tabIndex: itemInteractive ? 0 : void 0,
|
|
1923
|
+
"aria-pressed": itemInteractive ? visible : void 0,
|
|
1924
|
+
"aria-label": getLegendItemAriaLabel(label.text, matchedItem?.value, visible, itemInteractive),
|
|
1930
1925
|
children: [items[i]?.renderGlyph ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1931
1926
|
width: items[i]?.glyphSize * 2,
|
|
1932
1927
|
height: items[i]?.glyphSize * 2,
|
|
@@ -2128,6 +2123,32 @@ function processSeriesData(seriesData, getElementStyles, showValues, withGlyph,
|
|
|
2128
2123
|
});
|
|
2129
2124
|
}
|
|
2130
2125
|
/**
|
|
2126
|
+
* Builds the static item that names the comparison overlay, styled like its first series.
|
|
2127
|
+
* Skipped when a comparison series already has an item of its own, so nothing is listed twice.
|
|
2128
|
+
* @param seriesData - The series data to search for a comparison series
|
|
2129
|
+
* @param items - The legend items already built from the series
|
|
2130
|
+
* @param label - The item label
|
|
2131
|
+
* @param getElementStyles - Function to get element styles
|
|
2132
|
+
* @param legendShape - The shape type for legend items (string literal or React component)
|
|
2133
|
+
* @return The legend item, or null when it would add nothing
|
|
2134
|
+
*/
|
|
2135
|
+
function buildComparisonLegendItem(seriesData, items, label, getElementStyles, legendShape) {
|
|
2136
|
+
const itemLabels = new Set(items.map((item) => item.label));
|
|
2137
|
+
const index = seriesData.findIndex((series) => series.options?.type === "comparison" && !itemLabels.has(series.label));
|
|
2138
|
+
if (index === -1) return null;
|
|
2139
|
+
const { color, shapeStyles } = getElementStyles({
|
|
2140
|
+
data: seriesData[index],
|
|
2141
|
+
index,
|
|
2142
|
+
legendShape
|
|
2143
|
+
});
|
|
2144
|
+
return {
|
|
2145
|
+
label,
|
|
2146
|
+
color,
|
|
2147
|
+
shapeStyle: shapeStyles,
|
|
2148
|
+
interactive: false
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2131
2152
|
* Processes point data into legend items
|
|
2132
2153
|
* @param pointData - The point data to process
|
|
2133
2154
|
* @param getElementStyles - Function to get element styles
|
|
@@ -2163,11 +2184,16 @@ function processPointData(pointData, getElementStyles, showValues, legendValueDi
|
|
|
2163
2184
|
* @return Array of legend items ready for display
|
|
2164
2185
|
*/
|
|
2165
2186
|
function useChartLegendItems(data, options = {}, legendShape) {
|
|
2166
|
-
const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, renderGlyph } = options;
|
|
2187
|
+
const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, renderGlyph } = options;
|
|
2167
2188
|
const { getElementStyles } = useGlobalChartsContext();
|
|
2168
2189
|
return (0, react.useMemo)(() => {
|
|
2169
2190
|
if (!data || !Array.isArray(data) || data.length === 0) return [];
|
|
2170
|
-
if ("data" in data[0])
|
|
2191
|
+
if ("data" in data[0]) {
|
|
2192
|
+
const seriesData = data;
|
|
2193
|
+
const items = processSeriesData(seriesData, getElementStyles, showValues, withGlyph, glyphSize, collapseGroups, renderGlyph, legendShape);
|
|
2194
|
+
const comparison = comparisonItem ? buildComparisonLegendItem(seriesData, items, typeof comparisonItem === "string" ? comparisonItem : (0, _wordpress_i18n.__)("Comparison period", "jetpack-charts"), getElementStyles, legendShape) : null;
|
|
2195
|
+
return comparison ? [...items, comparison] : items;
|
|
2196
|
+
}
|
|
2171
2197
|
return processPointData(data, getElementStyles, showValues, legendValueDisplay, withGlyph, glyphSize, renderGlyph, legendShape);
|
|
2172
2198
|
}, [
|
|
2173
2199
|
data,
|
|
@@ -2177,13 +2203,17 @@ function useChartLegendItems(data, options = {}, legendShape) {
|
|
|
2177
2203
|
withGlyph,
|
|
2178
2204
|
glyphSize,
|
|
2179
2205
|
collapseGroups,
|
|
2206
|
+
comparisonItem,
|
|
2180
2207
|
renderGlyph,
|
|
2181
2208
|
legendShape
|
|
2182
2209
|
]);
|
|
2183
2210
|
}
|
|
2184
2211
|
//#endregion
|
|
2185
2212
|
//#region src/components/tooltip/base-tooltip.module.scss
|
|
2186
|
-
var base_tooltip_module_default = {
|
|
2213
|
+
var base_tooltip_module_default = {
|
|
2214
|
+
"surface": "a8ccharts--zY0xG-surface",
|
|
2215
|
+
"tooltip": "a8ccharts--zY0xG-tooltip"
|
|
2216
|
+
};
|
|
2187
2217
|
//#endregion
|
|
2188
2218
|
//#region src/components/tooltip/base-tooltip.tsx
|
|
2189
2219
|
const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
@@ -2233,7 +2263,8 @@ const findClippingAncestor = (wrapper) => {
|
|
|
2233
2263
|
/**
|
|
2234
2264
|
* Where the box goes, in wrapper coordinates. Automatic placement flips to
|
|
2235
2265
|
* the side that clips less, then clamps inside `bounds`; below-axis placement
|
|
2236
|
-
* is centered on the anchor and clamped horizontally
|
|
2266
|
+
* is centered on the anchor and clamped horizontally; beside placement preserves
|
|
2267
|
+
* the anchor's y and only flips horizontally before bounds clamping.
|
|
2237
2268
|
*
|
|
2238
2269
|
* The flip measures against the visible part of the wrapper, its intersection
|
|
2239
2270
|
* with `bounds`. Only the clamp is bound by the page: a box that fits inside the
|
|
@@ -2247,7 +2278,7 @@ const findClippingAncestor = (wrapper) => {
|
|
|
2247
2278
|
* @param params.offsetTop - Gap between the anchor and the box, vertically.
|
|
2248
2279
|
* @param params.box - Rendered size of the box.
|
|
2249
2280
|
* @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
|
|
2250
|
-
* @param params.placement -
|
|
2281
|
+
* @param params.placement - Below-axis, beside without vertical flipping, or automatic flipping.
|
|
2251
2282
|
* @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
|
|
2252
2283
|
* @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
|
|
2253
2284
|
*/
|
|
@@ -2271,7 +2302,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
|
|
|
2271
2302
|
const upY = top - offsetTop - box.height;
|
|
2272
2303
|
const downOverflow = downY + box.height - fit.bottom;
|
|
2273
2304
|
const upOverflow = fit.top - upY;
|
|
2274
|
-
let y =
|
|
2305
|
+
let y = top;
|
|
2306
|
+
if (placement !== "beside") y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
|
|
2275
2307
|
x = clamp(x, bounds.left, bounds.right, box.width);
|
|
2276
2308
|
y = clamp(y, bounds.top, bounds.bottom, box.height);
|
|
2277
2309
|
return {
|
|
@@ -2287,8 +2319,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
|
|
|
2287
2319
|
* alone would let the box spill into an `overflow: hidden` card and be cut off.
|
|
2288
2320
|
*
|
|
2289
2321
|
* Re-measures on every render, so a box whose content changes width between
|
|
2290
|
-
* two hovers is placed for its current size.
|
|
2291
|
-
* below-axis exception.
|
|
2322
|
+
* two hovers is placed for its current size.
|
|
2292
2323
|
*
|
|
2293
2324
|
* @param props - visx `Tooltip` props.
|
|
2294
2325
|
* @param props.left - Anchor x, in wrapper coordinates.
|
|
@@ -2298,7 +2329,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
|
|
|
2298
2329
|
* @param props.style - Box styles; visx's defaults unless `unstyled`.
|
|
2299
2330
|
* @param props.unstyled - Skip `style` and leave the box bare.
|
|
2300
2331
|
* @param props.children - Box content.
|
|
2301
|
-
* @param props.placement -
|
|
2332
|
+
* @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
|
|
2302
2333
|
* @return The tooltip box.
|
|
2303
2334
|
*/
|
|
2304
2335
|
const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
|
|
@@ -2401,7 +2432,6 @@ const crosshairPaintProps = (props = {}) => {
|
|
|
2401
2432
|
};
|
|
2402
2433
|
const DEFAULT_GLYPH_RADIUS = 4;
|
|
2403
2434
|
const FALLBACK_COLOR = "#222";
|
|
2404
|
-
const DEFAULT_TOOLTIP_Z_INDEX = 3;
|
|
2405
2435
|
const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
|
|
2406
2436
|
"scroll",
|
|
2407
2437
|
"debounce",
|
|
@@ -2429,7 +2459,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_js
|
|
|
2429
2459
|
seriesKey: key,
|
|
2430
2460
|
...props
|
|
2431
2461
|
}, key);
|
|
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 =
|
|
2462
|
+
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", tooltipAnchorTop, zIndex = 3, style, ...rest }) => {
|
|
2433
2463
|
const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
|
|
2434
2464
|
const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
|
|
2435
2465
|
const tooltipContent = renderTooltip ? renderTooltip({
|
|
@@ -2484,7 +2514,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2484
2514
|
const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
2485
2515
|
const marginTop = margin?.top ?? 0;
|
|
2486
2516
|
const marginLeft = margin?.left ?? 0;
|
|
2487
|
-
const TooltipComponent = detectBounds || tooltipPlacement
|
|
2517
|
+
const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : _visx_tooltip.Tooltip;
|
|
2488
2518
|
const boxStyle = {
|
|
2489
2519
|
..._visx_tooltip.defaultStyles,
|
|
2490
2520
|
zIndex,
|
|
@@ -2521,11 +2551,11 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
|
|
|
2521
2551
|
]
|
|
2522
2552
|
}), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
|
|
2523
2553
|
left: tooltipLeft,
|
|
2524
|
-
top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
|
|
2554
|
+
top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
|
|
2525
2555
|
style: boxStyle,
|
|
2526
2556
|
applyPositionStyle: true,
|
|
2527
2557
|
...tooltipProps,
|
|
2528
|
-
...tooltipPlacement
|
|
2558
|
+
...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
|
|
2529
2559
|
children: tooltipContent
|
|
2530
2560
|
}), container)] });
|
|
2531
2561
|
};
|
|
@@ -2591,11 +2621,32 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2591
2621
|
return flattened;
|
|
2592
2622
|
}, [series, mode]);
|
|
2593
2623
|
const hasKeyboardSelection = (0, react.useRef)(false);
|
|
2624
|
+
const closeTooltipNow = (0, react.useCallback)(() => {
|
|
2625
|
+
tooltipContext?.updateTooltip({
|
|
2626
|
+
tooltipOpen: false,
|
|
2627
|
+
tooltipLeft: void 0,
|
|
2628
|
+
tooltipTop: void 0,
|
|
2629
|
+
tooltipData: void 0
|
|
2630
|
+
});
|
|
2631
|
+
}, []);
|
|
2632
|
+
const chartId = (0, react.useContext)(ChartInstanceContext)?.chartId;
|
|
2633
|
+
const { getHiddenSeries } = useGlobalChartsContext();
|
|
2634
|
+
const hiddenSeriesKey = chartId ? JSON.stringify([...getHiddenSeries(chartId)].sort()) : void 0;
|
|
2635
|
+
const lastHiddenSeriesKey = (0, react.useRef)(hiddenSeriesKey);
|
|
2636
|
+
(0, react.useEffect)(() => {
|
|
2637
|
+
const changed = lastHiddenSeriesKey.current !== hiddenSeriesKey;
|
|
2638
|
+
lastHiddenSeriesKey.current = hiddenSeriesKey;
|
|
2639
|
+
if (changed && selectedIndex === void 0) closeTooltipNow();
|
|
2640
|
+
}, [
|
|
2641
|
+
hiddenSeriesKey,
|
|
2642
|
+
selectedIndex,
|
|
2643
|
+
closeTooltipNow
|
|
2644
|
+
]);
|
|
2594
2645
|
(0, react.useEffect)(() => {
|
|
2595
2646
|
if (selectedIndex === void 0) {
|
|
2596
2647
|
if (hasKeyboardSelection.current) {
|
|
2597
2648
|
hasKeyboardSelection.current = false;
|
|
2598
|
-
|
|
2649
|
+
closeTooltipNow();
|
|
2599
2650
|
}
|
|
2600
2651
|
return;
|
|
2601
2652
|
}
|
|
@@ -2616,7 +2667,7 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2616
2667
|
tooltipContext?.showTooltip({
|
|
2617
2668
|
datum: tooltipItem.datum,
|
|
2618
2669
|
key: tooltipItem.seriesLabel,
|
|
2619
|
-
index: tooltipItem.
|
|
2670
|
+
index: tooltipItem.dataPointIndex
|
|
2620
2671
|
});
|
|
2621
2672
|
}
|
|
2622
2673
|
}
|
|
@@ -2664,14 +2715,43 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
|
|
|
2664
2715
|
renderTooltip: focusableRenderTooltip
|
|
2665
2716
|
});
|
|
2666
2717
|
};
|
|
2667
|
-
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
|
|
2718
|
+
const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
|
|
2719
|
+
const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
|
|
2720
|
+
const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
|
|
2721
|
+
if (preventTooltipScroll) element?.focus({ preventScroll: true });
|
|
2722
|
+
else element?.focus();
|
|
2723
|
+
}, [preventTooltipScroll]);
|
|
2724
|
+
const tooltipRef = (0, react.useCallback)((element) => {
|
|
2725
|
+
if (element && selectedIndex !== void 0) focusWithoutScrollIfNeeded(element);
|
|
2726
|
+
}, [focusWithoutScrollIfNeeded, selectedIndex]);
|
|
2727
|
+
const previousTotalPoints = (0, react.useRef)(totalPoints);
|
|
2728
|
+
const previousVisibleSeriesKey = (0, react.useRef)(visibleSeriesKey);
|
|
2729
|
+
(0, react.useEffect)(() => {
|
|
2730
|
+
const countChanged = previousTotalPoints.current !== totalPoints;
|
|
2731
|
+
const seriesChanged = previousVisibleSeriesKey.current !== visibleSeriesKey;
|
|
2732
|
+
previousTotalPoints.current = totalPoints;
|
|
2733
|
+
previousVisibleSeriesKey.current = visibleSeriesKey;
|
|
2734
|
+
if (!countChanged && !seriesChanged || selectedIndex === void 0) return;
|
|
2735
|
+
const { activeElement } = document;
|
|
2736
|
+
const focusIsInChart = activeElement !== null && !!getChartRoot()?.contains(activeElement);
|
|
2737
|
+
if (totalPoints === 0 || !focusIsInChart || !countChanged) {
|
|
2738
|
+
setSelectedIndex(void 0);
|
|
2739
|
+
setIsNavigating(false);
|
|
2740
|
+
if (focusIsInChart) focusWithoutScrollIfNeeded(getChartRoot());
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
setSelectedIndex(Math.min(selectedIndex, totalPoints - 1));
|
|
2744
|
+
}, [
|
|
2745
|
+
selectedIndex,
|
|
2746
|
+
totalPoints,
|
|
2747
|
+
visibleSeriesKey,
|
|
2748
|
+
setSelectedIndex,
|
|
2749
|
+
setIsNavigating,
|
|
2750
|
+
getChartRoot,
|
|
2751
|
+
focusWithoutScrollIfNeeded
|
|
2752
|
+
]);
|
|
2668
2753
|
return {
|
|
2669
|
-
tooltipRef
|
|
2670
|
-
if (element && selectedIndex !== void 0) {
|
|
2671
|
-
if (preventTooltipScroll) element.focus({ preventScroll: true });
|
|
2672
|
-
else element.focus();
|
|
2673
|
-
}
|
|
2674
|
-
}, [preventTooltipScroll, selectedIndex]),
|
|
2754
|
+
tooltipRef,
|
|
2675
2755
|
onChartFocus: (0, react.useCallback)((event) => {
|
|
2676
2756
|
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
2677
2757
|
if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
|
|
@@ -2684,44 +2764,35 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
|
|
|
2684
2764
|
setIsNavigating(false);
|
|
2685
2765
|
}, [setIsNavigating]),
|
|
2686
2766
|
onChartKeyDown: (0, react.useCallback)((event) => {
|
|
2687
|
-
if (
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
else chartRef.current?.focus();
|
|
2691
|
-
};
|
|
2692
|
-
if (event.key === "Tab") {
|
|
2693
|
-
focusChart();
|
|
2767
|
+
if (event.key === "Tab" || event.key === "Escape") {
|
|
2768
|
+
if (event.key === "Escape" && selectedIndex !== void 0) event.preventDefault();
|
|
2769
|
+
focusWithoutScrollIfNeeded(getChartRoot());
|
|
2694
2770
|
setSelectedIndex(void 0);
|
|
2695
2771
|
setIsNavigating(false);
|
|
2696
2772
|
return;
|
|
2697
2773
|
}
|
|
2774
|
+
if (totalPoints === 0) return;
|
|
2698
2775
|
const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
|
|
2699
|
-
if (
|
|
2700
|
-
|
|
2701
|
-
setSelectedIndex(void 0);
|
|
2702
|
-
setIsNavigating(false);
|
|
2703
|
-
return;
|
|
2704
|
-
}
|
|
2705
|
-
event.preventDefault();
|
|
2706
|
-
if (["ArrowRight"].includes(event.key)) {
|
|
2776
|
+
if (event.key === "ArrowRight") {
|
|
2777
|
+
event.preventDefault();
|
|
2707
2778
|
setIsNavigating(true);
|
|
2708
|
-
setSelectedIndex((currentSelectedIndex + 1
|
|
2709
|
-
} else if (
|
|
2779
|
+
setSelectedIndex(Math.min(currentSelectedIndex + 1, totalPoints - 1));
|
|
2780
|
+
} else if (event.key === "ArrowLeft") {
|
|
2781
|
+
event.preventDefault();
|
|
2710
2782
|
setIsNavigating(true);
|
|
2711
|
-
setSelectedIndex((currentSelectedIndex - 1
|
|
2712
|
-
} else if (event.key === "
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
} else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
|
|
2783
|
+
setSelectedIndex(Math.max(currentSelectedIndex - 1, 0));
|
|
2784
|
+
} else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) {
|
|
2785
|
+
event.preventDefault();
|
|
2786
|
+
onActivate?.(selectedIndex);
|
|
2787
|
+
}
|
|
2717
2788
|
}, [
|
|
2718
2789
|
totalPoints,
|
|
2719
2790
|
selectedIndex,
|
|
2720
2791
|
setSelectedIndex,
|
|
2721
2792
|
setIsNavigating,
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2793
|
+
getChartRoot,
|
|
2794
|
+
focusWithoutScrollIfNeeded,
|
|
2795
|
+
onActivate
|
|
2725
2796
|
])
|
|
2726
2797
|
};
|
|
2727
2798
|
};
|
|
@@ -2919,6 +2990,31 @@ const DefaultGlyph = (props) => {
|
|
|
2919
2990
|
});
|
|
2920
2991
|
};
|
|
2921
2992
|
//#endregion
|
|
2993
|
+
//#region src/charts/private/readings.ts
|
|
2994
|
+
/**
|
|
2995
|
+
* Whether a value is a reading a scale can place.
|
|
2996
|
+
*
|
|
2997
|
+
* @param value - The point's value.
|
|
2998
|
+
* @return True for a finite number.
|
|
2999
|
+
*/
|
|
3000
|
+
const isReading = (value) => typeof value === "number" && Number.isFinite(value);
|
|
3001
|
+
/**
|
|
3002
|
+
* Whether a data point's value cannot be drawn.
|
|
3003
|
+
*
|
|
3004
|
+
* @param value - The point's value.
|
|
3005
|
+
* @param options - Validation options.
|
|
3006
|
+
* @param options.allowMissing - Whether `null` is a bucket with no reading rather than a fault.
|
|
3007
|
+
* @return True when the value is invalid.
|
|
3008
|
+
*/
|
|
3009
|
+
const isInvalidReading = (value, { allowMissing }) => value === null ? !allowMissing : value === void 0 || isNaN(value);
|
|
3010
|
+
/**
|
|
3011
|
+
* Formats a reading for a tooltip.
|
|
3012
|
+
*
|
|
3013
|
+
* @param value - The reading, or null when the bucket has none.
|
|
3014
|
+
* @return The formatted value.
|
|
3015
|
+
*/
|
|
3016
|
+
const formatReading = (value) => value == null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(value);
|
|
3017
|
+
//#endregion
|
|
2922
3018
|
//#region src/charts/private/center/center.module.scss
|
|
2923
3019
|
var center_module_default = { "center": "a8ccharts-w3qxlG-center" };
|
|
2924
3020
|
//#endregion
|
|
@@ -3291,6 +3387,54 @@ const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoo
|
|
|
3291
3387
|
};
|
|
3292
3388
|
};
|
|
3293
3389
|
//#endregion
|
|
3390
|
+
//#region src/charts/private/whole-number-ticks.tsx
|
|
3391
|
+
/**
|
|
3392
|
+
* Whether every reading in the series is a whole number.
|
|
3393
|
+
*
|
|
3394
|
+
* @param series - The series visx renders.
|
|
3395
|
+
* @return True when at least one reading exists and none has a fraction.
|
|
3396
|
+
*/
|
|
3397
|
+
const hasOnlyWholeNumbers = (series) => {
|
|
3398
|
+
let sawReading = false;
|
|
3399
|
+
for (const { data } of series) for (const point of data) {
|
|
3400
|
+
const value = point?.value;
|
|
3401
|
+
if (!isReading(value)) continue;
|
|
3402
|
+
if (!Number.isInteger(value)) return false;
|
|
3403
|
+
sawReading = true;
|
|
3404
|
+
}
|
|
3405
|
+
return sawReading;
|
|
3406
|
+
};
|
|
3407
|
+
/**
|
|
3408
|
+
* The scale's ticks with the fractional ones dropped.
|
|
3409
|
+
*
|
|
3410
|
+
* d3 steps by 1, 2 or 5 times a power of ten, so every whole number inside the domain is one of its ticks.
|
|
3411
|
+
*
|
|
3412
|
+
* @param scale - The value scale visx built.
|
|
3413
|
+
* @param count - Ticks to ask the scale for; undefined uses the scale's own default, as visx does.
|
|
3414
|
+
* @return Whole-number ticks, or undefined when visx's own ticks need no change.
|
|
3415
|
+
*/
|
|
3416
|
+
const getWholeNumberTickValues = (scale, count) => {
|
|
3417
|
+
const ticks = scale?.ticks?.(count);
|
|
3418
|
+
if (!ticks?.every((tick) => typeof tick === "number")) return;
|
|
3419
|
+
const whole = ticks.filter((tick) => Number.isInteger(tick));
|
|
3420
|
+
return whole.length > 1 && whole.length < ticks.length ? whole : void 0;
|
|
3421
|
+
};
|
|
3422
|
+
/**
|
|
3423
|
+
* Hands its children the whole-number ticks of the chart's value scale.
|
|
3424
|
+
*
|
|
3425
|
+
* @param props - Component props.
|
|
3426
|
+
* @param props.axis - Which scale carries the values.
|
|
3427
|
+
* @param props.numTicks - Ticks to ask the scale for.
|
|
3428
|
+
* @param props.enabled - Whether the plotted values are all whole numbers and the caller pinned neither the ticks nor the value domain.
|
|
3429
|
+
* @param props.children - Renders the axis and grid with the tick values.
|
|
3430
|
+
* @return The rendered children.
|
|
3431
|
+
*/
|
|
3432
|
+
const WholeNumberTicks = ({ axis, numTicks, enabled, children }) => {
|
|
3433
|
+
const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
3434
|
+
const tickValues = enabled ? getWholeNumberTickValues(axis === "x" ? xScale : yScale, numTicks) : void 0;
|
|
3435
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: children(tickValues) });
|
|
3436
|
+
};
|
|
3437
|
+
//#endregion
|
|
3294
3438
|
//#region src/charts/private/with-responsive/with-responsive.module.scss
|
|
3295
3439
|
var with_responsive_module_default = {
|
|
3296
3440
|
"container": "a8ccharts-sP1gHa-container",
|
|
@@ -3957,11 +4101,15 @@ const toNumber$1 = (val) => {
|
|
|
3957
4101
|
const LineChartGlyph = ({ data, index, color, glyphStyle, renderGlyph, accessors, position }) => {
|
|
3958
4102
|
const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext) || {};
|
|
3959
4103
|
if (!xScale || !yScale) return null;
|
|
3960
|
-
|
|
3961
|
-
|
|
4104
|
+
const hasFiniteY = (datum) => {
|
|
4105
|
+
const scaledY = yScale(accessors.yAccessor(datum));
|
|
4106
|
+
return typeof scaledY === "number" && Number.isFinite(scaledY);
|
|
4107
|
+
};
|
|
4108
|
+
const point = position === "start" ? data.data.find(hasFiniteY) : data.data.findLast(hasFiniteY);
|
|
4109
|
+
if (!point) return null;
|
|
3962
4110
|
const x = xScale(accessors.xAccessor(point));
|
|
3963
4111
|
const y = yScale(accessors.yAccessor(point));
|
|
3964
|
-
if (typeof x !== "number" || typeof y !== "number") return null;
|
|
4112
|
+
if (typeof x !== "number" || typeof y !== "number" || !Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
3965
4113
|
const size = Math.max(0, toNumber$1(glyphStyle?.radius) ?? 4);
|
|
3966
4114
|
return renderGlyph({
|
|
3967
4115
|
key: `${position}-glyph-${data.label}`,
|
|
@@ -3976,6 +4124,106 @@ const LineChartGlyph = ({ data, index, color, glyphStyle, renderGlyph, accessors
|
|
|
3976
4124
|
});
|
|
3977
4125
|
};
|
|
3978
4126
|
//#endregion
|
|
4127
|
+
//#region src/charts/line-chart/private/nearest-pointer-events.tsx
|
|
4128
|
+
/**
|
|
4129
|
+
* Find the datum nearest a pointer along x, in one series sorted by x.
|
|
4130
|
+
*
|
|
4131
|
+
* @param data - The series' registered data, in ascending x order.
|
|
4132
|
+
* @param xAccessor - Reads a datum's x value.
|
|
4133
|
+
* @param xScale - The chart's x scale.
|
|
4134
|
+
* @param x - Pointer x in SVG coordinates.
|
|
4135
|
+
* @return Index of the nearest datum and its distance, or index -1 when none has a position.
|
|
4136
|
+
*/
|
|
4137
|
+
function nearestByX(data, xAccessor, xScale, x) {
|
|
4138
|
+
return (typeof xScale.invert === "function" ? [(0, _visx_vendor_d3_array.bisector)(xAccessor).left(data, xScale.invert(x))].flatMap((index) => [index - 1, index]) : data.map((_datum, index) => index)).filter((index) => index >= 0 && index < data.length).reduce((best, index) => {
|
|
4139
|
+
const distance = Math.abs(Number(xScale(xAccessor(data[index]))) - x);
|
|
4140
|
+
return distance < best.distance ? {
|
|
4141
|
+
index,
|
|
4142
|
+
distance
|
|
4143
|
+
} : best;
|
|
4144
|
+
}, {
|
|
4145
|
+
index: -1,
|
|
4146
|
+
distance: Infinity
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Pick the candidate to report: the closest placed reading, else the closest bucket with no reading along x.
|
|
4151
|
+
*
|
|
4152
|
+
* @param candidates - One candidate per series.
|
|
4153
|
+
* @return The chosen candidate's params, or undefined when there are none.
|
|
4154
|
+
*/
|
|
4155
|
+
function pickNearest(candidates) {
|
|
4156
|
+
const placed = candidates.map(({ params }) => params).filter((params) => Number.isFinite(params.distanceY));
|
|
4157
|
+
const missing = candidates.filter(({ isMissing }) => isMissing).map(({ params }) => params);
|
|
4158
|
+
const distance = (params) => placed.length ? Math.hypot(params.distanceX ?? 0, params.distanceY ?? 0) : params.distanceX ?? 0;
|
|
4159
|
+
return (placed.length ? placed : missing).reduce((best, params) => !best || distance(params) < distance(best) ? params : best, void 0);
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Report pointer events at the nearest datum, including a bucket with no reading.
|
|
4163
|
+
*
|
|
4164
|
+
* visx's own nearest search measures y distance too, which is NaN for a null value, so a pointer
|
|
4165
|
+
* over a bucket where no series has a reading fires no event at all.
|
|
4166
|
+
*
|
|
4167
|
+
* @param props - Handlers to call.
|
|
4168
|
+
* @param props.onPointerDown - Receives the nearest datum on pointer down.
|
|
4169
|
+
* @param props.onPointerMove - Receives the nearest datum on pointer move.
|
|
4170
|
+
* @param props.onPointerUp - Receives the nearest datum on pointer up.
|
|
4171
|
+
* @return No visual content.
|
|
4172
|
+
*/
|
|
4173
|
+
function NearestPointerEvents({ onPointerDown, onPointerMove, onPointerUp }) {
|
|
4174
|
+
const { xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
4175
|
+
const findNearest = (0, react.useCallback)((params) => {
|
|
4176
|
+
const point = params?.svgPoint;
|
|
4177
|
+
if (!point || !xScale || !yScale || !dataRegistry) return;
|
|
4178
|
+
const candidates = [];
|
|
4179
|
+
for (const key of dataRegistry.keys()) {
|
|
4180
|
+
const entry = dataRegistry.get(key);
|
|
4181
|
+
if (!entry) continue;
|
|
4182
|
+
const { index, distance } = nearestByX(entry.data, entry.xAccessor, xScale, point.x);
|
|
4183
|
+
if (index < 0) continue;
|
|
4184
|
+
const datum = entry.data[index];
|
|
4185
|
+
const value = entry.yAccessor(datum);
|
|
4186
|
+
candidates.push({
|
|
4187
|
+
params: {
|
|
4188
|
+
event: params.event,
|
|
4189
|
+
svgPoint: point,
|
|
4190
|
+
key,
|
|
4191
|
+
datum,
|
|
4192
|
+
index,
|
|
4193
|
+
distanceX: distance,
|
|
4194
|
+
distanceY: Math.abs(Number(yScale(value)) - point.y)
|
|
4195
|
+
},
|
|
4196
|
+
isMissing: value == null
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
return pickNearest(candidates);
|
|
4200
|
+
}, [
|
|
4201
|
+
xScale,
|
|
4202
|
+
yScale,
|
|
4203
|
+
dataRegistry
|
|
4204
|
+
]);
|
|
4205
|
+
const handlers = (0, react.useMemo)(() => {
|
|
4206
|
+
const report = (handler) => handler && ((params) => {
|
|
4207
|
+
const nearest = findNearest(params);
|
|
4208
|
+
if (nearest) handler(nearest);
|
|
4209
|
+
});
|
|
4210
|
+
return {
|
|
4211
|
+
pointerdown: report(onPointerDown),
|
|
4212
|
+
pointermove: report(onPointerMove),
|
|
4213
|
+
pointerup: report(onPointerUp)
|
|
4214
|
+
};
|
|
4215
|
+
}, [
|
|
4216
|
+
findNearest,
|
|
4217
|
+
onPointerDown,
|
|
4218
|
+
onPointerMove,
|
|
4219
|
+
onPointerUp
|
|
4220
|
+
]);
|
|
4221
|
+
(0, _visx_xychart.useEventEmitter)("pointerdown", handlers.pointerdown);
|
|
4222
|
+
(0, _visx_xychart.useEventEmitter)("pointermove", handlers.pointermove);
|
|
4223
|
+
(0, _visx_xychart.useEventEmitter)("pointerup", handlers.pointerup);
|
|
4224
|
+
return null;
|
|
4225
|
+
}
|
|
4226
|
+
//#endregion
|
|
3979
4227
|
//#region src/charts/line-chart/line-chart.tsx
|
|
3980
4228
|
const defaultRenderGlyph = (props) => {
|
|
3981
4229
|
return /* @__PURE__ */ (0, react.createElement)(DefaultGlyph, {
|
|
@@ -4019,8 +4267,13 @@ const renderDefaultTooltip = (params, contentStyle) => {
|
|
|
4019
4267
|
if (!nearestDatum) return null;
|
|
4020
4268
|
const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
|
|
4021
4269
|
key,
|
|
4022
|
-
value: datum.value
|
|
4023
|
-
})).sort((a, b) =>
|
|
4270
|
+
value: datum.value ?? null
|
|
4271
|
+
})).sort((a, b) => {
|
|
4272
|
+
if (a.value === null && b.value === null) return 0;
|
|
4273
|
+
if (a.value === null) return 1;
|
|
4274
|
+
if (b.value === null) return -1;
|
|
4275
|
+
return b.value - a.value;
|
|
4276
|
+
});
|
|
4024
4277
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4025
4278
|
className: line_chart_module_default["line-chart__tooltip"],
|
|
4026
4279
|
style: contentStyle,
|
|
@@ -4040,16 +4293,23 @@ const renderDefaultTooltip = (params, contentStyle) => {
|
|
|
4040
4293
|
children: [point.key, ":"]
|
|
4041
4294
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4042
4295
|
className: line_chart_module_default["line-chart__tooltip-value"],
|
|
4043
|
-
children: (
|
|
4296
|
+
children: formatReading(point.value)
|
|
4044
4297
|
})]
|
|
4045
4298
|
}, point.key))]
|
|
4046
4299
|
});
|
|
4047
4300
|
};
|
|
4048
4301
|
const validateData$4 = (data) => {
|
|
4049
|
-
if (!data?.length) return "No data available";
|
|
4050
|
-
if (data.some((series) => series.data.some((point) =>
|
|
4302
|
+
if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
|
|
4303
|
+
if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: true }) || "date" in point && point.date && isNaN(point.date.getTime())))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
|
|
4051
4304
|
return null;
|
|
4052
4305
|
};
|
|
4306
|
+
const getFallbackYDomain = (readingExtent, isLogScale) => {
|
|
4307
|
+
const emptyDomain = isLogScale ? [1, 10] : [0, 1];
|
|
4308
|
+
if (!readingExtent) return emptyDomain;
|
|
4309
|
+
const [min, max] = readingExtent;
|
|
4310
|
+
if (min !== max || isLogScale) return;
|
|
4311
|
+
return min === 0 ? emptyDomain : [Math.min(0, min), Math.max(0, max)];
|
|
4312
|
+
};
|
|
4053
4313
|
const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
|
|
4054
4314
|
const context = (0, react.useContext)(_visx_xychart.DataContext);
|
|
4055
4315
|
(0, react.useImperativeHandle)(chartRef, () => ({
|
|
@@ -4076,6 +4336,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
|
|
|
4076
4336
|
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) => {
|
|
4077
4337
|
const legendInteractive = legend.interactive ?? false;
|
|
4078
4338
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
4339
|
+
const legendComparisonItem = legend.comparisonItem ?? false;
|
|
4079
4340
|
const legendShape = legend.shape ?? "line";
|
|
4080
4341
|
const legendPosition = legend.position ?? "bottom";
|
|
4081
4342
|
const formatting = useChartFormatting();
|
|
@@ -4122,19 +4383,32 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4122
4383
|
const allSeriesHidden = (0, react.useMemo)(() => {
|
|
4123
4384
|
return seriesWithVisibility.every(({ isVisible }) => !isVisible);
|
|
4124
4385
|
}, [seriesWithVisibility]);
|
|
4125
|
-
const
|
|
4126
|
-
|
|
4386
|
+
const yDomain = (0, react.useMemo)(() => {
|
|
4387
|
+
const includeZero = options?.yScale?.zero === true && (0, _visx_scale.scaleCanBeZeroed)({ type: options.yScale.type ?? "linear" });
|
|
4388
|
+
if (rescaleYOnVisibilityChange && !includeZero) return;
|
|
4127
4389
|
let min = Infinity;
|
|
4128
4390
|
let max = -Infinity;
|
|
4129
|
-
for (const series of dataSorted)
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4391
|
+
for (const series of dataSorted) {
|
|
4392
|
+
if (rescaleYOnVisibilityChange && !isSeriesVisible(series.label)) continue;
|
|
4393
|
+
for (const point of series.data ?? []) {
|
|
4394
|
+
const value = point?.value;
|
|
4395
|
+
if (isReading(value)) {
|
|
4396
|
+
min = Math.min(min, value);
|
|
4397
|
+
max = Math.max(max, value);
|
|
4398
|
+
}
|
|
4134
4399
|
}
|
|
4135
4400
|
}
|
|
4401
|
+
if (includeZero) {
|
|
4402
|
+
if (min === Infinity) return;
|
|
4403
|
+
return max === 0 && min === 0 ? [0, 1] : [Math.min(0, min), Math.max(0, max)];
|
|
4404
|
+
}
|
|
4136
4405
|
return min < max ? [min, max] : void 0;
|
|
4137
|
-
}, [
|
|
4406
|
+
}, [
|
|
4407
|
+
rescaleYOnVisibilityChange,
|
|
4408
|
+
dataSorted,
|
|
4409
|
+
isSeriesVisible,
|
|
4410
|
+
options?.yScale
|
|
4411
|
+
]);
|
|
4138
4412
|
const activateSelectedPoint = (0, react.useCallback)((index) => {
|
|
4139
4413
|
const series = dataSorted[0];
|
|
4140
4414
|
const datum = series?.data[index];
|
|
@@ -4154,7 +4428,24 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4154
4428
|
onActivate: activateSelectedPoint,
|
|
4155
4429
|
preventTooltipScroll: tooltipPlacement === "below-axis"
|
|
4156
4430
|
});
|
|
4431
|
+
const visibleReadingExtent = (0, react.useMemo)(() => {
|
|
4432
|
+
let min = Infinity;
|
|
4433
|
+
let max = -Infinity;
|
|
4434
|
+
for (const series of dataSorted) {
|
|
4435
|
+
if (!isSeriesVisible(series.label)) continue;
|
|
4436
|
+
for (const point of series.data) {
|
|
4437
|
+
const value = point?.value;
|
|
4438
|
+
if (isReading(value)) {
|
|
4439
|
+
min = Math.min(min, value);
|
|
4440
|
+
max = Math.max(max, value);
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
return min <= max ? [min, max] : void 0;
|
|
4445
|
+
}, [dataSorted, isSeriesVisible]);
|
|
4446
|
+
const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter((series) => isSeriesVisible(series.label))), [dataSorted, isSeriesVisible]);
|
|
4157
4447
|
const chartOptions = (0, react.useMemo)(() => {
|
|
4448
|
+
const fallbackYDomain = getFallbackYDomain(visibleReadingExtent, options?.yScale?.type === "log");
|
|
4158
4449
|
return {
|
|
4159
4450
|
axis: {
|
|
4160
4451
|
x: buildTimeAxisOptions({
|
|
@@ -4183,7 +4474,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4183
4474
|
type: "linear",
|
|
4184
4475
|
nice: true,
|
|
4185
4476
|
zero: false,
|
|
4186
|
-
...
|
|
4477
|
+
...fallbackYDomain ? { domain: fallbackYDomain } : {},
|
|
4478
|
+
...yDomain ? { domain: yDomain } : {},
|
|
4187
4479
|
...options?.yScale
|
|
4188
4480
|
}
|
|
4189
4481
|
};
|
|
@@ -4192,7 +4484,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4192
4484
|
dataSorted,
|
|
4193
4485
|
width,
|
|
4194
4486
|
zoom.domain,
|
|
4195
|
-
|
|
4487
|
+
yDomain,
|
|
4488
|
+
visibleReadingExtent,
|
|
4196
4489
|
formatting,
|
|
4197
4490
|
isSeriesVisible
|
|
4198
4491
|
]);
|
|
@@ -4227,11 +4520,13 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4227
4520
|
withGlyph: withLegendGlyph,
|
|
4228
4521
|
glyphSize: Math.max(0, toNumber(glyphStyle?.radius) ?? 4),
|
|
4229
4522
|
collapseGroups: legendCollapseGroups,
|
|
4523
|
+
comparisonItem: legendComparisonItem,
|
|
4230
4524
|
renderGlyph
|
|
4231
4525
|
}), [
|
|
4232
4526
|
withLegendGlyph,
|
|
4233
4527
|
glyphStyle?.radius,
|
|
4234
4528
|
legendCollapseGroups,
|
|
4529
|
+
legendComparisonItem,
|
|
4235
4530
|
renderGlyph
|
|
4236
4531
|
]), legendShape);
|
|
4237
4532
|
const chartMetadata = (0, react.useMemo)(() => ({
|
|
@@ -4343,18 +4638,26 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4343
4638
|
},
|
|
4344
4639
|
xScale: chartOptions.xScale,
|
|
4345
4640
|
yScale: chartOptions.yScale,
|
|
4346
|
-
onPointerDown: zoom.handlers.onPointerDown,
|
|
4347
|
-
onPointerUp: zoom.handlers.onPointerUp,
|
|
4348
|
-
onPointerMove: zoom.handlers.onPointerMove,
|
|
4349
4641
|
onPointerOut,
|
|
4350
|
-
pointerEventsDataKey: "nearest",
|
|
4351
4642
|
children: [
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4643
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NearestPointerEvents, { ...zoom.handlers }),
|
|
4644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
|
|
4645
|
+
axis: "y",
|
|
4646
|
+
numTicks: chartOptions.axis.y.numTicks,
|
|
4647
|
+
enabled: hasWholeNumberValues && !chartOptions.axis.y.tickValues && !options?.yScale?.domain,
|
|
4648
|
+
children: (tickValues) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4649
|
+
!allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
|
|
4650
|
+
columns: false,
|
|
4651
|
+
numTicks: chartOptions.axis.y.numTicks,
|
|
4652
|
+
tickValues: tickValues ?? chartOptions.axis.y.tickValues
|
|
4653
|
+
}),
|
|
4654
|
+
!allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
|
|
4655
|
+
!allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
|
|
4656
|
+
...chartOptions.axis.y,
|
|
4657
|
+
...tickValues ? { tickValues } : {}
|
|
4658
|
+
})
|
|
4659
|
+
] })
|
|
4355
4660
|
}),
|
|
4356
|
-
!allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
|
|
4357
|
-
!allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y }),
|
|
4358
4661
|
allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
|
|
4359
4662
|
x: width / 2,
|
|
4360
4663
|
y: chartHeight / 2,
|
|
@@ -4495,7 +4798,7 @@ var area_chart_module_default = {
|
|
|
4495
4798
|
const validateData$3 = (data) => {
|
|
4496
4799
|
if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
|
|
4497
4800
|
if (data.some((series) => !series.data?.length)) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
|
|
4498
|
-
if (data.some((series) => series.data.some((point) =>
|
|
4801
|
+
if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: false }) || "date" in point && point.date && isNaN(point.date.getTime())))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
|
|
4499
4802
|
return null;
|
|
4500
4803
|
};
|
|
4501
4804
|
//#endregion
|
|
@@ -4611,6 +4914,12 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4611
4914
|
}));
|
|
4612
4915
|
}, [dataSorted, hiddenSeries]);
|
|
4613
4916
|
const allSeriesHidden = (0, react.useMemo)(() => seriesWithVisibility.every(({ isVisible }) => !isVisible), [seriesWithVisibility]);
|
|
4917
|
+
const hasWholeNumberValues = (0, react.useMemo)(() => (!stacked || stackOffset === "none") && hasOnlyWholeNumbers(dataSorted.filter((series) => isSeriesVisible(series.label))), [
|
|
4918
|
+
dataSorted,
|
|
4919
|
+
isSeriesVisible,
|
|
4920
|
+
stacked,
|
|
4921
|
+
stackOffset
|
|
4922
|
+
]);
|
|
4614
4923
|
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
|
|
4615
4924
|
selectedIndex,
|
|
4616
4925
|
setSelectedIndex,
|
|
@@ -4703,8 +5012,9 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4703
5012
|
const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
|
|
4704
5013
|
withGlyph: false,
|
|
4705
5014
|
glyphSize: 0,
|
|
4706
|
-
collapseGroups: legend.collapseGroups ?? false
|
|
4707
|
-
|
|
5015
|
+
collapseGroups: legend.collapseGroups ?? false,
|
|
5016
|
+
comparisonItem: legend.comparisonItem ?? false
|
|
5017
|
+
}), [legend.collapseGroups, legend.comparisonItem]), legendShape);
|
|
4708
5018
|
const chartMetadata = (0, react.useMemo)(() => ({
|
|
4709
5019
|
stacked,
|
|
4710
5020
|
stackOffset,
|
|
@@ -4854,12 +5164,23 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
|
|
|
4854
5164
|
onPointerOut,
|
|
4855
5165
|
pointerEventsDataKey: "nearest",
|
|
4856
5166
|
children: [
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
numTicks:
|
|
5167
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
|
|
5168
|
+
axis: "y",
|
|
5169
|
+
numTicks: chartOptions.axis.y.numTicks,
|
|
5170
|
+
enabled: hasWholeNumberValues && !chartOptions.axis.y.tickValues && !options?.yScale?.domain,
|
|
5171
|
+
children: (tickValues) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5172
|
+
!allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
|
|
5173
|
+
columns: false,
|
|
5174
|
+
numTicks: chartOptions.axis.y.numTicks,
|
|
5175
|
+
tickValues: tickValues ?? chartOptions.axis.y.tickValues
|
|
5176
|
+
}),
|
|
5177
|
+
!allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
|
|
5178
|
+
!allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
|
|
5179
|
+
...chartOptions.axis.y,
|
|
5180
|
+
...tickValues ? { tickValues } : {}
|
|
5181
|
+
})
|
|
5182
|
+
] })
|
|
4860
5183
|
}),
|
|
4861
|
-
!allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
|
|
4862
|
-
!allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y }),
|
|
4863
5184
|
allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
|
|
4864
5185
|
x: width / 2,
|
|
4865
5186
|
y: chartHeight / 2,
|
|
@@ -5048,6 +5369,65 @@ const createTruncatedTickComponent = (axis) => (props) => {
|
|
|
5048
5369
|
const TruncatedXTickComponent = createTruncatedTickComponent("x");
|
|
5049
5370
|
const TruncatedYTickComponent = createTruncatedTickComponent("y");
|
|
5050
5371
|
//#endregion
|
|
5372
|
+
//#region src/charts/bar-chart/private/value-domain.ts
|
|
5373
|
+
/** Domain for a value axis that has nothing to scale, so its ticks still read 0 to 1. */
|
|
5374
|
+
const EMPTY_DOMAIN = [0, 1];
|
|
5375
|
+
/**
|
|
5376
|
+
* The value a bar is drawn at, preferring `visualValue` so a zero still shows a sliver.
|
|
5377
|
+
*
|
|
5378
|
+
* A missing reading is NaN, not null: `ComparisonBars` coerces the value with `Number()`,
|
|
5379
|
+
* which turns null into 0 and would draw a comparison shadow at the baseline.
|
|
5380
|
+
*
|
|
5381
|
+
* @param point - A data point handed to visx.
|
|
5382
|
+
* @return The bar value, or NaN when the bucket has no reading.
|
|
5383
|
+
*/
|
|
5384
|
+
const getBarValue = (point) => {
|
|
5385
|
+
const enhanced = point;
|
|
5386
|
+
return (enhanced?.visualValue !== void 0 ? enhanced.visualValue : point?.value) ?? NaN;
|
|
5387
|
+
};
|
|
5388
|
+
/**
|
|
5389
|
+
* Whether visx renders a bar for a point.
|
|
5390
|
+
*
|
|
5391
|
+
* @param point - A data point handed to visx.
|
|
5392
|
+
* @return True when the point has a finite bar value.
|
|
5393
|
+
*/
|
|
5394
|
+
const isBarRendered = (point) => Number.isFinite(getBarValue(point));
|
|
5395
|
+
/**
|
|
5396
|
+
* How many bars visx renders for a list of points.
|
|
5397
|
+
*
|
|
5398
|
+
* @param points - Data points handed to visx.
|
|
5399
|
+
* @return The number of points that get a bar.
|
|
5400
|
+
*/
|
|
5401
|
+
const countRenderedBars = (points) => points.filter(isBarRendered).length;
|
|
5402
|
+
/**
|
|
5403
|
+
* An explicit domain for the value scale, or null to let visx fit one to the data.
|
|
5404
|
+
*
|
|
5405
|
+
* Zero goes into the domain here rather than through the scale's `zero` flag: visx applies
|
|
5406
|
+
* `nice` before `zero`, which would leave the axis topping out at a raw maximum.
|
|
5407
|
+
*
|
|
5408
|
+
* @param data - Every series handed to the chart.
|
|
5409
|
+
* @param includeZero - Whether the domain must span zero.
|
|
5410
|
+
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
5411
|
+
* @return Value domain, or null when visx should fit its own.
|
|
5412
|
+
*/
|
|
5413
|
+
const getValueScaleDomain = (data, includeZero, isSeriesRendered) => {
|
|
5414
|
+
let min = Infinity;
|
|
5415
|
+
let max = -Infinity;
|
|
5416
|
+
for (const series of data) {
|
|
5417
|
+
if (!isSeriesRendered(series)) continue;
|
|
5418
|
+
for (const point of series.data) {
|
|
5419
|
+
const value = getBarValue(point);
|
|
5420
|
+
if (!Number.isFinite(value)) continue;
|
|
5421
|
+
min = Math.min(min, value);
|
|
5422
|
+
max = Math.max(max, value);
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
if (min === Infinity) return [...EMPTY_DOMAIN];
|
|
5426
|
+
if (min === max) return min === 0 ? [...EMPTY_DOMAIN] : [Math.min(0, min), Math.max(0, max)];
|
|
5427
|
+
if (includeZero) return [Math.min(0, min), Math.max(0, max)];
|
|
5428
|
+
return null;
|
|
5429
|
+
};
|
|
5430
|
+
//#endregion
|
|
5051
5431
|
//#region src/charts/bar-chart/private/use-bar-chart-options.ts
|
|
5052
5432
|
/** Outer padding of the category band scale (space at the chart edges). */
|
|
5053
5433
|
const BASE_BAND_PADDING = .2;
|
|
@@ -5172,10 +5552,6 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
5172
5552
|
const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
|
|
5173
5553
|
const valueFormatter = _automattic_number_formatters.formatNumberCompact;
|
|
5174
5554
|
const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
|
|
5175
|
-
const valueAccessor = (d) => {
|
|
5176
|
-
const enhancedPoint = d;
|
|
5177
|
-
return enhancedPoint?.visualValue !== void 0 ? enhancedPoint.visualValue : d?.value;
|
|
5178
|
-
};
|
|
5179
5555
|
return {
|
|
5180
5556
|
timeAxis: bandDomain && timeTickFormatter && {
|
|
5181
5557
|
domain: bandDomain,
|
|
@@ -5186,7 +5562,7 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
5186
5562
|
yTickFormat: valueFormatter,
|
|
5187
5563
|
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
5188
5564
|
xAccessor: bucketAccessor,
|
|
5189
|
-
yAccessor:
|
|
5565
|
+
yAccessor: getBarValue,
|
|
5190
5566
|
gridVisibility: "x",
|
|
5191
5567
|
xScale: bandScale,
|
|
5192
5568
|
yScale: linearScale
|
|
@@ -5195,7 +5571,7 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
5195
5571
|
xTickFormat: valueFormatter,
|
|
5196
5572
|
yTickFormat: labelFormatter,
|
|
5197
5573
|
tooltipLabelFormatter: tooltipDatumFormatter,
|
|
5198
|
-
xAccessor:
|
|
5574
|
+
xAccessor: getBarValue,
|
|
5199
5575
|
yAccessor: bucketAccessor,
|
|
5200
5576
|
gridVisibility: "y",
|
|
5201
5577
|
xScale: linearScale,
|
|
@@ -5210,20 +5586,10 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
5210
5586
|
]);
|
|
5211
5587
|
return (0, react.useMemo)(() => {
|
|
5212
5588
|
const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
data.forEach((series) => {
|
|
5218
|
-
series.data.forEach((d) => {
|
|
5219
|
-
const enhanced = d;
|
|
5220
|
-
const v = enhanced.visualValue !== void 0 ? enhanced.visualValue : d.value;
|
|
5221
|
-
if (typeof v === "number" && Number.isFinite(v)) allValues.push(v);
|
|
5222
|
-
});
|
|
5223
|
-
});
|
|
5224
|
-
if (allValues.length > 0) valueScaleDomainOverride = { domain: [Math.min(0, ...allValues), Math.max(0, ...allValues)] };
|
|
5225
|
-
}
|
|
5226
|
-
}
|
|
5589
|
+
const valueScaleOptions = horizontal ? stableOptions.xScale : stableOptions.yScale;
|
|
5590
|
+
const includeZero = data.some((s) => s.options?.type === "comparison") || valueScaleOptions?.zero !== false;
|
|
5591
|
+
const domain = valueScaleOptions?.domain ? null : getValueScaleDomain(data, includeZero, isSeriesRendered);
|
|
5592
|
+
const valueScaleDomainOverride = domain ? { domain } : {};
|
|
5227
5593
|
const xScale = {
|
|
5228
5594
|
...baseXScale,
|
|
5229
5595
|
...stableOptions.xScale || {},
|
|
@@ -5274,10 +5640,25 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
|
|
|
5274
5640
|
axisConfig,
|
|
5275
5641
|
stableOptions,
|
|
5276
5642
|
horizontal,
|
|
5277
|
-
data
|
|
5643
|
+
data,
|
|
5644
|
+
isSeriesRendered
|
|
5278
5645
|
]);
|
|
5279
5646
|
}
|
|
5280
5647
|
//#endregion
|
|
5648
|
+
//#region src/charts/bar-chart/private/band-scale.ts
|
|
5649
|
+
/**
|
|
5650
|
+
* Match the inner slot scale used by visx BarGroup.
|
|
5651
|
+
* @param keys - Visible primary series keys.
|
|
5652
|
+
* @param bandwidth - Category band width.
|
|
5653
|
+
* @param padding - Padding between slots.
|
|
5654
|
+
* @return The grouped bar scale.
|
|
5655
|
+
*/
|
|
5656
|
+
const createGroupScale = (keys, bandwidth, padding) => (0, _visx_scale.scaleBand)({
|
|
5657
|
+
domain: keys,
|
|
5658
|
+
range: [0, bandwidth],
|
|
5659
|
+
padding
|
|
5660
|
+
});
|
|
5661
|
+
//#endregion
|
|
5281
5662
|
//#region src/charts/bar-chart/private/comparison-bars-geometry.ts
|
|
5282
5663
|
/**
|
|
5283
5664
|
* Output position of a value scale's baseline: zero if in-domain, else the
|
|
@@ -5350,11 +5731,7 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
|
|
|
5350
5731
|
const valueScale = horizontal ? xScale : yScale;
|
|
5351
5732
|
const bandwidth = bandScale.bandwidth ? bandScale.bandwidth() : 0;
|
|
5352
5733
|
if (!bandwidth) return null;
|
|
5353
|
-
const groupScale = (
|
|
5354
|
-
domain: primaryKeys,
|
|
5355
|
-
range: [0, bandwidth],
|
|
5356
|
-
padding: groupPadding
|
|
5357
|
-
});
|
|
5734
|
+
const groupScale = createGroupScale(primaryKeys, bandwidth, groupPadding);
|
|
5358
5735
|
const slotThickness = groupScale.bandwidth();
|
|
5359
5736
|
const baseline = getValueScaleBaseline(valueScale);
|
|
5360
5737
|
const bandAccessor = horizontal ? yAccessor : xAccessor;
|
|
@@ -5406,10 +5783,169 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
|
|
|
5406
5783
|
});
|
|
5407
5784
|
};
|
|
5408
5785
|
//#endregion
|
|
5786
|
+
//#region src/charts/bar-chart/private/band-highlight.tsx
|
|
5787
|
+
/**
|
|
5788
|
+
* Render and report the active band using the chart’s registered scales.
|
|
5789
|
+
* @param root0 - Highlight options.
|
|
5790
|
+
* @param root0.visible - Whether to paint the band.
|
|
5791
|
+
* @param root0.horizontal - Whether categories run vertically.
|
|
5792
|
+
* @param root0.onChange - Receives selected band geometry.
|
|
5793
|
+
* @return The optional band highlight.
|
|
5794
|
+
*/
|
|
5795
|
+
function BandHighlight({ visible, horizontal, onChange }) {
|
|
5796
|
+
const { xScale, yScale, dataRegistry, margin, innerWidth, innerHeight } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
5797
|
+
const { tooltipOpen, tooltipData } = (0, react.useContext)(_visx_xychart.TooltipContext);
|
|
5798
|
+
const nearest = tooltipData?.nearestDatum;
|
|
5799
|
+
const selection = (0, react.useMemo)(() => {
|
|
5800
|
+
if (!tooltipOpen || !nearest) return null;
|
|
5801
|
+
const scale = horizontal ? yScale : xScale;
|
|
5802
|
+
const entry = dataRegistry?.get(nearest.key);
|
|
5803
|
+
const accessor = horizontal ? entry?.yAccessor : entry?.xAccessor;
|
|
5804
|
+
if (!scale?.bandwidth || !accessor) return null;
|
|
5805
|
+
const position = scale(accessor(nearest.datum));
|
|
5806
|
+
if (position === void 0 || !Number.isFinite(position)) return null;
|
|
5807
|
+
return {
|
|
5808
|
+
datum: nearest.datum,
|
|
5809
|
+
key: nearest.key,
|
|
5810
|
+
index: nearest.index,
|
|
5811
|
+
x: horizontal ? margin?.left ?? 0 : position,
|
|
5812
|
+
y: horizontal ? position : margin?.top ?? 0,
|
|
5813
|
+
width: horizontal ? innerWidth : scale.bandwidth(),
|
|
5814
|
+
height: horizontal ? scale.bandwidth() : innerHeight
|
|
5815
|
+
};
|
|
5816
|
+
}, [
|
|
5817
|
+
tooltipOpen,
|
|
5818
|
+
nearest,
|
|
5819
|
+
horizontal,
|
|
5820
|
+
xScale,
|
|
5821
|
+
yScale,
|
|
5822
|
+
dataRegistry,
|
|
5823
|
+
margin,
|
|
5824
|
+
innerWidth,
|
|
5825
|
+
innerHeight
|
|
5826
|
+
]);
|
|
5827
|
+
const stableSelection = useDeepMemo(selection);
|
|
5828
|
+
const onChangeRef = (0, react.useRef)(onChange);
|
|
5829
|
+
onChangeRef.current = onChange;
|
|
5830
|
+
(0, react.useEffect)(() => {
|
|
5831
|
+
onChangeRef.current?.(stableSelection);
|
|
5832
|
+
}, [stableSelection]);
|
|
5833
|
+
(0, react.useEffect)(() => () => onChangeRef.current?.(null), []);
|
|
5834
|
+
if (!visible || !selection) return null;
|
|
5835
|
+
const { x, y, width, height } = selection;
|
|
5836
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
5837
|
+
x,
|
|
5838
|
+
y,
|
|
5839
|
+
width,
|
|
5840
|
+
height,
|
|
5841
|
+
pointerEvents: "none",
|
|
5842
|
+
fill: CATALOG_POINTERS.surfaceSecondary
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
//#endregion
|
|
5846
|
+
//#region src/charts/bar-chart/private/band-tooltip.tsx
|
|
5847
|
+
/**
|
|
5848
|
+
* Find a category by its painted center, including outer padding and reversed ranges.
|
|
5849
|
+
* @param data - Registered series points.
|
|
5850
|
+
* @param accessor - Category accessor.
|
|
5851
|
+
* @param scale - Rendered category band scale.
|
|
5852
|
+
* @param position - Pointer coordinate on the category axis.
|
|
5853
|
+
* @return Index of the nearest category, or -1 when none is registered.
|
|
5854
|
+
*/
|
|
5855
|
+
function nearestBandIndex(data, accessor, scale, position) {
|
|
5856
|
+
let nearest = -1;
|
|
5857
|
+
let distance = Infinity;
|
|
5858
|
+
data.forEach((datum, index) => {
|
|
5859
|
+
const start = scale(accessor(datum));
|
|
5860
|
+
if (start === void 0) return;
|
|
5861
|
+
const candidate = Math.abs(start + scale.bandwidth() / 2 - position);
|
|
5862
|
+
if (candidate < distance) {
|
|
5863
|
+
distance = candidate;
|
|
5864
|
+
nearest = index;
|
|
5865
|
+
}
|
|
5866
|
+
});
|
|
5867
|
+
return nearest;
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Correct visx's range-based band inversion, which omits outer padding.
|
|
5871
|
+
* @param root0 - Registered series selection.
|
|
5872
|
+
* @param root0.keys - Visible series keys.
|
|
5873
|
+
* @param root0.groupPadding - Padding between grouped bars.
|
|
5874
|
+
* @param root0.withTooltips - Whether pointer events update the tooltip.
|
|
5875
|
+
* @param root0.onPointerDown - Receives the corrected pointer-down datum.
|
|
5876
|
+
* @param root0.onPointerUp - Receives the corrected pointer-up datum.
|
|
5877
|
+
* @return No visual content.
|
|
5878
|
+
*/
|
|
5879
|
+
function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp }) {
|
|
5880
|
+
const { xScale, yScale, dataRegistry, horizontal } = (0, react.useContext)(_visx_xychart.DataContext);
|
|
5881
|
+
const { showTooltip } = (0, react.useContext)(_visx_xychart.TooltipContext);
|
|
5882
|
+
const scale = horizontal ? yScale : xScale;
|
|
5883
|
+
const bandwidth = scale?.bandwidth?.() ?? 0;
|
|
5884
|
+
const groupScale = (0, react.useMemo)(() => createGroupScale(keys, bandwidth, groupPadding), [
|
|
5885
|
+
keys,
|
|
5886
|
+
bandwidth,
|
|
5887
|
+
groupPadding
|
|
5888
|
+
]);
|
|
5889
|
+
const getSelections = (0, react.useCallback)((params) => {
|
|
5890
|
+
const point = params?.svgPoint;
|
|
5891
|
+
if (!point || !scale?.bandwidth) return [];
|
|
5892
|
+
const selections = [];
|
|
5893
|
+
for (const key of keys) {
|
|
5894
|
+
const entry = dataRegistry?.get(key);
|
|
5895
|
+
if (!entry) continue;
|
|
5896
|
+
const accessor = horizontal ? entry.yAccessor : entry.xAccessor;
|
|
5897
|
+
const index = nearestBandIndex(entry.data, accessor, scale, horizontal ? point.y : point.x);
|
|
5898
|
+
if (index < 0) continue;
|
|
5899
|
+
const datum = entry.data[index];
|
|
5900
|
+
const start = Number(scale(accessor(datum))) + Number(groupScale(key));
|
|
5901
|
+
const end = start + groupScale.step();
|
|
5902
|
+
const position = horizontal ? point.y : point.x;
|
|
5903
|
+
const distance = position >= start && position <= end ? 0 : Math.abs(position - (start + end) / 2);
|
|
5904
|
+
selections.push({
|
|
5905
|
+
event: params.event,
|
|
5906
|
+
key,
|
|
5907
|
+
datum,
|
|
5908
|
+
index,
|
|
5909
|
+
svgPoint: point,
|
|
5910
|
+
distanceX: horizontal ? 0 : distance,
|
|
5911
|
+
distanceY: horizontal ? distance : 0
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
return selections;
|
|
5915
|
+
}, [
|
|
5916
|
+
scale,
|
|
5917
|
+
dataRegistry,
|
|
5918
|
+
horizontal,
|
|
5919
|
+
keys,
|
|
5920
|
+
groupScale
|
|
5921
|
+
]);
|
|
5922
|
+
const handlePointer = (0, react.useCallback)((params) => {
|
|
5923
|
+
const selections = getSelections(params);
|
|
5924
|
+
if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
|
|
5925
|
+
const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
|
|
5926
|
+
if (params?.event.type === "pointermove" || !callback) return;
|
|
5927
|
+
const nearest = selections.reduce((best, selection) => {
|
|
5928
|
+
const distance = (value) => Math.hypot(value.distanceX, value.distanceY);
|
|
5929
|
+
return !best || distance(selection) <= distance(best) ? selection : best;
|
|
5930
|
+
}, void 0);
|
|
5931
|
+
if (nearest) callback(nearest);
|
|
5932
|
+
}, [
|
|
5933
|
+
getSelections,
|
|
5934
|
+
withTooltips,
|
|
5935
|
+
showTooltip,
|
|
5936
|
+
onPointerDown,
|
|
5937
|
+
onPointerUp
|
|
5938
|
+
]);
|
|
5939
|
+
(0, _visx_xychart.useEventEmitter)("pointermove", withTooltips ? handlePointer : void 0);
|
|
5940
|
+
(0, _visx_xychart.useEventEmitter)("pointerdown", handlePointer);
|
|
5941
|
+
(0, _visx_xychart.useEventEmitter)("pointerup", onPointerUp ? handlePointer : void 0);
|
|
5942
|
+
return null;
|
|
5943
|
+
}
|
|
5944
|
+
//#endregion
|
|
5409
5945
|
//#region src/charts/bar-chart/bar-chart.tsx
|
|
5410
5946
|
const validateData$2 = (data) => {
|
|
5411
|
-
if (!data?.length) return "No data available";
|
|
5412
|
-
if (data.some((series) => series.data.some((point) =>
|
|
5947
|
+
if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
|
|
5948
|
+
if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: true }) || !point.label && (!("date" in point && point.date) || isNaN(point.date.getTime()))))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
|
|
5413
5949
|
return null;
|
|
5414
5950
|
};
|
|
5415
5951
|
const getPatternId = (chartId, index) => `bar-pattern-${chartId}-${index}`;
|
|
@@ -5417,9 +5953,11 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
|
|
|
5417
5953
|
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
5418
5954
|
children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
|
|
5419
5955
|
});
|
|
5420
|
-
const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
|
|
5956
|
+
const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
|
|
5957
|
+
if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
|
|
5421
5958
|
const legendInteractive = legend.interactive ?? false;
|
|
5422
5959
|
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
5960
|
+
const legendComparisonItem = legend.comparisonItem ?? false;
|
|
5423
5961
|
const horizontal = orientation === "horizontal";
|
|
5424
5962
|
const chartId = useChartId(providedChartId);
|
|
5425
5963
|
const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
|
|
@@ -5430,10 +5968,21 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5430
5968
|
enabled: showZeroValues,
|
|
5431
5969
|
valueAxisLength: horizontal ? width : height
|
|
5432
5970
|
});
|
|
5433
|
-
const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
|
|
5971
|
+
const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
|
|
5972
|
+
collapseGroups: legendCollapseGroups,
|
|
5973
|
+
comparisonItem: legendComparisonItem
|
|
5974
|
+
}), [legendCollapseGroups, legendComparisonItem]));
|
|
5434
5975
|
const { getElementStyles } = useGlobalChartsContext();
|
|
5435
5976
|
const isSeriesRendered = (0, react.useCallback)((series) => isSeriesVisible(series.label), [isSeriesVisible]);
|
|
5977
|
+
const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter(isSeriesRendered)), [dataSorted, isSeriesRendered]);
|
|
5436
5978
|
const chartOptions = useBarChartOptions(dataWithVisibleZeros, horizontal, options, isSeriesRendered);
|
|
5979
|
+
const valueAxis = horizontal ? chartOptions.axis.x : chartOptions.axis.y;
|
|
5980
|
+
const callerValueDomain = horizontal ? options.xScale?.domain : options.yScale?.domain;
|
|
5981
|
+
const wholeNumberTicksProps = {
|
|
5982
|
+
axis: horizontal ? "x" : "y",
|
|
5983
|
+
numTicks: valueAxis.numTicks,
|
|
5984
|
+
enabled: hasWholeNumberValues && !valueAxis.tickValues && !callerValueDomain
|
|
5985
|
+
};
|
|
5437
5986
|
const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme, horizontal);
|
|
5438
5987
|
const chartRef = (0, react.useRef)(null);
|
|
5439
5988
|
const { legendChildren, nonLegendChildren } = useChartChildren(children, "BarChart");
|
|
@@ -5443,8 +5992,6 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5443
5992
|
}, [height]);
|
|
5444
5993
|
const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
|
|
5445
5994
|
const [isNavigating, setIsNavigating] = (0, react.useState)(false);
|
|
5446
|
-
const primarySeriesForNav = dataWithVisibleZeros.filter((s) => s.options?.type !== "comparison");
|
|
5447
|
-
const totalPoints = Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length;
|
|
5448
5995
|
const seriesWithVisibility = (0, react.useMemo)(() => dataWithVisibleZeros.map((series, index) => ({
|
|
5449
5996
|
series,
|
|
5450
5997
|
index,
|
|
@@ -5454,6 +6001,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5454
6001
|
return seriesWithVisibility.every(({ isVisible }) => !isVisible);
|
|
5455
6002
|
}, [seriesWithVisibility]);
|
|
5456
6003
|
const primaryEntries = (0, react.useMemo)(() => seriesWithVisibility.filter(({ isVisible, series }) => isVisible && series.options?.type !== "comparison"), [seriesWithVisibility]);
|
|
6004
|
+
const totalPoints = Math.max(0, ...primaryEntries.map((e) => e.series.data.length)) * primaryEntries.length;
|
|
5457
6005
|
const primaryKeys = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series.label), [primaryEntries]);
|
|
5458
6006
|
const primarySeries = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series), [primaryEntries]);
|
|
5459
6007
|
const activateSelectedBar = (0, react.useCallback)((index) => {
|
|
@@ -5468,6 +6016,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5468
6016
|
key: series.label
|
|
5469
6017
|
});
|
|
5470
6018
|
}, [primaryEntries, onDatumActivate]);
|
|
6019
|
+
const visibleSeriesKey = (0, react.useMemo)(() => JSON.stringify(primaryKeys), [primaryKeys]);
|
|
5471
6020
|
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
|
|
5472
6021
|
selectedIndex,
|
|
5473
6022
|
setSelectedIndex,
|
|
@@ -5475,7 +6024,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5475
6024
|
setIsNavigating,
|
|
5476
6025
|
chartRef,
|
|
5477
6026
|
totalPoints,
|
|
5478
|
-
onActivate: activateSelectedBar
|
|
6027
|
+
onActivate: activateSelectedBar,
|
|
6028
|
+
visibleSeriesKey
|
|
5479
6029
|
});
|
|
5480
6030
|
const comparisonEntries = (0, react.useMemo)(() => {
|
|
5481
6031
|
const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
|
|
@@ -5538,7 +6088,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5538
6088
|
chartOptions.yScale,
|
|
5539
6089
|
horizontal
|
|
5540
6090
|
]);
|
|
5541
|
-
const getBarBackground = (0, react.useCallback)((index) => () => withPatterns ? `url(#${getPatternId(chartId, index)})` : getElementStyles({
|
|
6091
|
+
const getBarBackground = (0, react.useCallback)((index) => (datum) => withPatterns ? `url(#${getPatternId(chartId, index)})` : datum.color ?? getElementStyles({
|
|
5542
6092
|
data: dataSorted[index],
|
|
5543
6093
|
index
|
|
5544
6094
|
}).color, [
|
|
@@ -5565,15 +6115,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5565
6115
|
const p = point;
|
|
5566
6116
|
return nearestDatum.label != null ? p.label === nearestDatum.label : !!nearestDatum.date && !!p.date && p.date.getTime() === nearestDatum.date.getTime();
|
|
5567
6117
|
});
|
|
5568
|
-
if (comparisonEntry && comparisonDatum
|
|
6118
|
+
if (comparisonEntry && comparisonDatum) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5569
6119
|
className: bar_chart_module_default["bar-chart__tooltip"],
|
|
5570
6120
|
children: [
|
|
5571
6121
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5572
6122
|
className: bar_chart_module_default["bar-chart__tooltip-header"],
|
|
5573
6123
|
children: categoryLabel
|
|
5574
6124
|
}),
|
|
5575
|
-
renderTooltipRow(primaryKey, (
|
|
5576
|
-
renderTooltipRow(comparisonEntry.series.label, (
|
|
6125
|
+
renderTooltipRow(primaryKey, formatReading(nearestDatum.value)),
|
|
6126
|
+
renderTooltipRow(comparisonEntry.series.label, formatReading(comparisonDatum.value))
|
|
5577
6127
|
]
|
|
5578
6128
|
});
|
|
5579
6129
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5581,7 +6131,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5581
6131
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5582
6132
|
className: bar_chart_module_default["bar-chart__tooltip-header"],
|
|
5583
6133
|
children: primaryKey
|
|
5584
|
-
}), renderTooltipRow(categoryLabel, (
|
|
6134
|
+
}), renderTooltipRow(categoryLabel, formatReading(nearestDatum.value))]
|
|
5585
6135
|
});
|
|
5586
6136
|
}, [chartOptions.tooltip, comparisonEntries]);
|
|
5587
6137
|
const renderPattern = (0, react.useCallback)((index, color) => {
|
|
@@ -5632,15 +6182,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5632
6182
|
const createKeyboardHighlightStyle = (0, react.useCallback)(() => {
|
|
5633
6183
|
if (selectedIndex === void 0) return "";
|
|
5634
6184
|
const primaryCount = primaryEntries.length;
|
|
5635
|
-
|
|
6185
|
+
if (!primaryCount) return "";
|
|
5636
6186
|
const dataPointIndex = Math.floor(selectedIndex / primaryCount);
|
|
5637
6187
|
const seriesIndex = selectedIndex % primaryCount;
|
|
5638
|
-
if (dataPointIndex >= maxDataPoints || seriesIndex >= primaryCount) return "";
|
|
5639
6188
|
const seriesData = primaryEntries[seriesIndex]?.series;
|
|
5640
|
-
|
|
5641
|
-
|
|
6189
|
+
const datum = seriesData?.data[dataPointIndex];
|
|
6190
|
+
if (!seriesData || !datum || !isBarRendered(datum)) return "";
|
|
6191
|
+
const domBarIndex = primaryEntries.slice(0, seriesIndex).reduce((count, { series }) => count + countRenderedBars(series.data), 0) + countRenderedBars(seriesData.data.slice(0, dataPointIndex));
|
|
5642
6192
|
return `
|
|
5643
|
-
.bar-chart[data-chart-id="bar-chart-${chartId}"] .visx-bar-group .visx-bar:nth-child(${
|
|
6193
|
+
.bar-chart[data-chart-id="bar-chart-${chartId}"] .visx-bar-group .visx-bar:nth-child(${domBarIndex + 1}) {
|
|
5644
6194
|
stroke: #005fcc;
|
|
5645
6195
|
stroke-width: 2px;
|
|
5646
6196
|
}
|
|
@@ -5708,13 +6258,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5708
6258
|
const chartHeight = contentHeight > 0 ? contentHeight : height;
|
|
5709
6259
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5710
6260
|
role: "grid",
|
|
6261
|
+
ref: chartRef,
|
|
5711
6262
|
"aria-label": (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
|
|
5712
6263
|
tabIndex: 0,
|
|
5713
6264
|
onKeyDown: onChartKeyDown,
|
|
5714
6265
|
onFocus: onChartFocus,
|
|
5715
6266
|
onBlur: onChartBlur,
|
|
5716
6267
|
children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5717
|
-
ref: chartRef,
|
|
5718
6268
|
className: xy_plot_module_default["xy-plot"],
|
|
5719
6269
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
|
|
5720
6270
|
theme,
|
|
@@ -5727,14 +6277,26 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5727
6277
|
xScale,
|
|
5728
6278
|
yScale,
|
|
5729
6279
|
horizontal,
|
|
5730
|
-
onPointerDown,
|
|
5731
|
-
onPointerUp,
|
|
5732
6280
|
pointerEventsDataKey: "nearest",
|
|
5733
6281
|
children: [
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
6282
|
+
withTooltips && (withBandHighlight || onBandHighlightChange) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandHighlight, {
|
|
6283
|
+
visible: withBandHighlight,
|
|
6284
|
+
horizontal,
|
|
6285
|
+
onChange: onBandHighlightChange
|
|
6286
|
+
}),
|
|
6287
|
+
!allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
|
|
6288
|
+
...wholeNumberTicksProps,
|
|
6289
|
+
children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
|
|
6290
|
+
columns: gridVisibility.includes("y"),
|
|
6291
|
+
rows: false,
|
|
6292
|
+
numTicks: chartOptions.axis.x.numTicks,
|
|
6293
|
+
tickValues: (horizontal ? valueTicks : void 0) ?? chartOptions.axis.x.tickValues
|
|
6294
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
|
|
6295
|
+
columns: false,
|
|
6296
|
+
rows: gridVisibility.includes("x"),
|
|
6297
|
+
numTicks: chartOptions.axis.y.numTicks,
|
|
6298
|
+
tickValues: (horizontal ? void 0 : valueTicks) ?? chartOptions.axis.y.tickValues
|
|
6299
|
+
})] })
|
|
5738
6300
|
}),
|
|
5739
6301
|
withPatterns && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: dataSorted.map((seriesData, index) => renderPattern(index, getElementStyles({
|
|
5740
6302
|
data: seriesData,
|
|
@@ -5771,8 +6333,26 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
5771
6333
|
colorAccessor: getBarBackground(index)
|
|
5772
6334
|
}, seriesData?.label))
|
|
5773
6335
|
}),
|
|
5774
|
-
|
|
6336
|
+
(withTooltips || onPointerDown || onPointerUp) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandTooltip, {
|
|
6337
|
+
keys: primaryKeys,
|
|
6338
|
+
groupPadding,
|
|
6339
|
+
withTooltips,
|
|
6340
|
+
onPointerDown,
|
|
6341
|
+
onPointerUp
|
|
6342
|
+
}),
|
|
6343
|
+
!allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
|
|
6344
|
+
...wholeNumberTicksProps,
|
|
6345
|
+
children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
|
|
6346
|
+
...chartOptions.axis.x,
|
|
6347
|
+
...horizontal && valueTicks ? { tickValues: valueTicks } : {}
|
|
6348
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
|
|
6349
|
+
...chartOptions.axis.y,
|
|
6350
|
+
...!horizontal && valueTicks ? { tickValues: valueTicks } : {}
|
|
6351
|
+
})] })
|
|
6352
|
+
}),
|
|
5775
6353
|
withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
|
|
6354
|
+
tooltipPlacement,
|
|
6355
|
+
tooltipAnchorTop,
|
|
5776
6356
|
detectBounds: true,
|
|
5777
6357
|
snapTooltipToDatumX: true,
|
|
5778
6358
|
snapTooltipToDatumY: true,
|
|
@@ -6594,8 +7174,11 @@ var heatmap_chart_module_default = {
|
|
|
6594
7174
|
"heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
|
|
6595
7175
|
"heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
|
|
6596
7176
|
"heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
|
|
7177
|
+
"heatmap-chart__grid--flex-gaps": "a8ccharts-O3YMOW-heatmap-chart__grid--flex-gaps",
|
|
6597
7178
|
"heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
|
|
7179
|
+
"heatmap-chart__group-label": "a8ccharts-O3YMOW-heatmap-chart__group-label",
|
|
6598
7180
|
"heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
|
|
7181
|
+
"heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
|
|
6599
7182
|
"heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
|
|
6600
7183
|
"heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
|
|
6601
7184
|
"heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
|
|
@@ -6645,7 +7228,7 @@ const getNormalizedValue = (value, extent) => {
|
|
|
6645
7228
|
const HeatmapContext = (0, react.createContext)(null);
|
|
6646
7229
|
//#endregion
|
|
6647
7230
|
//#region src/charts/heatmap-chart/private/heatmap-legend.tsx
|
|
6648
|
-
const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
7231
|
+
const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }) => {
|
|
6649
7232
|
const context = (0, react.useContext)(HeatmapContext);
|
|
6650
7233
|
const { legend } = useGlobalChartsTheme();
|
|
6651
7234
|
if (!context) return null;
|
|
@@ -6664,11 +7247,12 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6664
7247
|
}),
|
|
6665
7248
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
|
|
6666
7249
|
direction: "row",
|
|
6667
|
-
gap: "xs",
|
|
7250
|
+
gap: variant === "bar" ? void 0 : "xs",
|
|
7251
|
+
"aria-hidden": "true",
|
|
7252
|
+
className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
|
|
6668
7253
|
children: Array.from({ length: steps }, (_, index) => {
|
|
6669
7254
|
const intensity = steps <= 1 ? 1 : index / (steps - 1);
|
|
6670
7255
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6671
|
-
"aria-hidden": "true",
|
|
6672
7256
|
className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
|
|
6673
7257
|
style: {
|
|
6674
7258
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
@@ -6687,16 +7271,219 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
|
|
|
6687
7271
|
});
|
|
6688
7272
|
};
|
|
6689
7273
|
//#endregion
|
|
7274
|
+
//#region src/charts/heatmap-chart/private/column-groups.ts
|
|
7275
|
+
const isSpan = (span) => Number.isInteger(span) && span > 0;
|
|
7276
|
+
/**
|
|
7277
|
+
* Validates column groups and lays them over the columns.
|
|
7278
|
+
*
|
|
7279
|
+
* @param groups - Groups as passed to the chart.
|
|
7280
|
+
* @param columnCount - Number of data columns.
|
|
7281
|
+
* @param firstLine - Grid column line of the first data column.
|
|
7282
|
+
* @return Grid column lines for every column and group; no groups when the input was unusable.
|
|
7283
|
+
*/
|
|
7284
|
+
const resolveColumnGroups = (groups, columnCount, firstLine) => {
|
|
7285
|
+
const ungrouped = () => ({
|
|
7286
|
+
columns: Array.from({ length: columnCount }, (_, index) => ({
|
|
7287
|
+
line: firstLine + index,
|
|
7288
|
+
gapBefore: false
|
|
7289
|
+
})),
|
|
7290
|
+
groups: []
|
|
7291
|
+
});
|
|
7292
|
+
if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
|
|
7293
|
+
const spans = groups.map((group) => group.span);
|
|
7294
|
+
const total = spans.reduce((sum, span) => sum + span, 0);
|
|
7295
|
+
if (!spans.every(isSpan) || total > columnCount) {
|
|
7296
|
+
warnOnce(`heatmap:columnGroups:${JSON.stringify(spans)}:${columnCount}`, `columnGroups spans ${JSON.stringify(spans)} must be positive integers that fit the ${columnCount} columns, so no groups are drawn.`);
|
|
7297
|
+
return ungrouped();
|
|
7298
|
+
}
|
|
7299
|
+
const columns = [];
|
|
7300
|
+
const laidOut = [];
|
|
7301
|
+
let line = firstLine;
|
|
7302
|
+
groups.forEach((group, groupIndex) => {
|
|
7303
|
+
if (groupIndex > 0) line += 1;
|
|
7304
|
+
laidOut.push({
|
|
7305
|
+
...group,
|
|
7306
|
+
line
|
|
7307
|
+
});
|
|
7308
|
+
for (let offset = 0; offset < group.span; offset++) {
|
|
7309
|
+
columns.push({
|
|
7310
|
+
line,
|
|
7311
|
+
gapBefore: groupIndex > 0 && offset === 0,
|
|
7312
|
+
group: groupIndex
|
|
7313
|
+
});
|
|
7314
|
+
line += 1;
|
|
7315
|
+
}
|
|
7316
|
+
});
|
|
7317
|
+
while (columns.length < columnCount) {
|
|
7318
|
+
columns.push({
|
|
7319
|
+
line,
|
|
7320
|
+
gapBefore: false
|
|
7321
|
+
});
|
|
7322
|
+
line += 1;
|
|
7323
|
+
}
|
|
7324
|
+
return {
|
|
7325
|
+
columns,
|
|
7326
|
+
groups: laidOut
|
|
7327
|
+
};
|
|
7328
|
+
};
|
|
7329
|
+
//#endregion
|
|
7330
|
+
//#region src/charts/heatmap-chart/private/keyboard-navigation.ts
|
|
7331
|
+
const ARROW_KEYS = [
|
|
7332
|
+
"ArrowLeft",
|
|
7333
|
+
"ArrowRight",
|
|
7334
|
+
"ArrowUp",
|
|
7335
|
+
"ArrowDown"
|
|
7336
|
+
];
|
|
7337
|
+
const PAGE_KEYS = ["PageUp", "PageDown"];
|
|
7338
|
+
const isNavigationKey = (key, mode) => ARROW_KEYS.includes(key) || mode === "calendar" && PAGE_KEYS.includes(key);
|
|
7339
|
+
const inGrid = (grid, { column, row }) => column >= 0 && column < grid.columns && row >= 0 && row < grid.rows;
|
|
7340
|
+
const firstSelectable = (grid, cells) => cells.find((cell) => inGrid(grid, cell) && !grid.isInert(cell.column, cell.row));
|
|
7341
|
+
/**
|
|
7342
|
+
* Column-major scan for the first selectable cell.
|
|
7343
|
+
*
|
|
7344
|
+
* @param grid - The grid.
|
|
7345
|
+
* @return The cell, or undefined when every slot is inert.
|
|
7346
|
+
*/
|
|
7347
|
+
const firstGridCell = (grid) => {
|
|
7348
|
+
for (let column = 0; column < grid.columns; column++) for (let row = 0; row < grid.rows; row++) if (!grid.isInert(column, row)) return {
|
|
7349
|
+
column,
|
|
7350
|
+
row
|
|
7351
|
+
};
|
|
7352
|
+
};
|
|
7353
|
+
/**
|
|
7354
|
+
* Steps across the grid in the key's direction, past inert slots, staying put at the edge.
|
|
7355
|
+
*
|
|
7356
|
+
* @param grid - The grid.
|
|
7357
|
+
* @param from - The selected cell.
|
|
7358
|
+
* @param key - The arrow key.
|
|
7359
|
+
* @return The next cell, or undefined to keep the selection.
|
|
7360
|
+
*/
|
|
7361
|
+
const stepGridCell = (grid, from, key) => {
|
|
7362
|
+
const step = {
|
|
7363
|
+
ArrowRight: [1, 0],
|
|
7364
|
+
ArrowLeft: [-1, 0],
|
|
7365
|
+
ArrowDown: [0, 1],
|
|
7366
|
+
ArrowUp: [0, -1]
|
|
7367
|
+
}[key];
|
|
7368
|
+
if (!step) return;
|
|
7369
|
+
const [stepColumn, stepRow] = step;
|
|
7370
|
+
let cell = from;
|
|
7371
|
+
do
|
|
7372
|
+
cell = {
|
|
7373
|
+
column: cell.column + stepColumn,
|
|
7374
|
+
row: cell.row + stepRow
|
|
7375
|
+
};
|
|
7376
|
+
while (inGrid(grid, cell) && grid.isInert(cell.column, cell.row));
|
|
7377
|
+
return inGrid(grid, cell) ? cell : void 0;
|
|
7378
|
+
};
|
|
7379
|
+
/**
|
|
7380
|
+
* Every slot of the blocks in reading order: block by block, each a row at a time.
|
|
7381
|
+
*
|
|
7382
|
+
* @param grid - The grid.
|
|
7383
|
+
* @param blocks - The blocks, left to right.
|
|
7384
|
+
* @return The slots in order.
|
|
7385
|
+
*/
|
|
7386
|
+
const readingOrder = (grid, blocks) => blocks.flatMap((block) => Array.from({ length: grid.rows }, (_row, row) => Array.from({ length: block.end - block.start }, (_offset, offset) => ({
|
|
7387
|
+
column: block.start + offset,
|
|
7388
|
+
row
|
|
7389
|
+
}))).flat());
|
|
7390
|
+
const blockOf = (blocks, column) => blocks.findIndex((block) => column >= block.start && column < block.end);
|
|
7391
|
+
/**
|
|
7392
|
+
* The slots after a cell down its column (or before it, going up), continuing at the
|
|
7393
|
+
* same column offset of each following block.
|
|
7394
|
+
*
|
|
7395
|
+
* @param grid - The grid.
|
|
7396
|
+
* @param blocks - The blocks, left to right.
|
|
7397
|
+
* @param from - The selected cell.
|
|
7398
|
+
* @param direction - 1 downwards, -1 upwards.
|
|
7399
|
+
* @return The slots in order.
|
|
7400
|
+
*/
|
|
7401
|
+
const columnOrderFrom = (grid, blocks, from, direction) => {
|
|
7402
|
+
const blockIndex = blockOf(blocks, from.column);
|
|
7403
|
+
if (blockIndex < 0) return [];
|
|
7404
|
+
const offset = from.column - blocks[blockIndex].start;
|
|
7405
|
+
const cells = [];
|
|
7406
|
+
const pushRows = (column, startRow) => {
|
|
7407
|
+
for (let row = startRow; row >= 0 && row < grid.rows; row += direction) cells.push({
|
|
7408
|
+
column,
|
|
7409
|
+
row
|
|
7410
|
+
});
|
|
7411
|
+
};
|
|
7412
|
+
pushRows(from.column, from.row + direction);
|
|
7413
|
+
for (let index = blockIndex + direction; index >= 0 && index < blocks.length; index += direction) {
|
|
7414
|
+
const column = blocks[index].start + offset;
|
|
7415
|
+
if (column < blocks[index].end) pushRows(column, direction === 1 ? 0 : grid.rows - 1);
|
|
7416
|
+
}
|
|
7417
|
+
return cells;
|
|
7418
|
+
};
|
|
7419
|
+
/**
|
|
7420
|
+
* The first selectable cell in calendar reading order.
|
|
7421
|
+
*
|
|
7422
|
+
* @param grid - The grid.
|
|
7423
|
+
* @param blocks - The blocks, left to right.
|
|
7424
|
+
* @return The cell, or undefined when every slot is inert.
|
|
7425
|
+
*/
|
|
7426
|
+
const firstCalendarCell = (grid, blocks) => firstSelectable(grid, readingOrder(grid, blocks));
|
|
7427
|
+
/**
|
|
7428
|
+
* Steps through the blocks as pages of a calendar: Left/Right by one slot in reading
|
|
7429
|
+
* order, Up/Down by one row (into the neighboring block past the edge), Page Up/Down
|
|
7430
|
+
* to the same slot of the neighboring block or its nearest selectable cell.
|
|
7431
|
+
*
|
|
7432
|
+
* @param grid - The grid.
|
|
7433
|
+
* @param blocks - The blocks, left to right.
|
|
7434
|
+
* @param from - The selected cell.
|
|
7435
|
+
* @param key - The navigation key.
|
|
7436
|
+
* @return The next cell, or undefined to keep the selection.
|
|
7437
|
+
*/
|
|
7438
|
+
const stepCalendarCell = (grid, blocks, from, key) => {
|
|
7439
|
+
if (key === "ArrowRight" || key === "ArrowLeft") {
|
|
7440
|
+
const order = readingOrder(grid, blocks);
|
|
7441
|
+
const index = order.findIndex((cell) => cell.column === from.column && cell.row === from.row);
|
|
7442
|
+
if (index < 0) return;
|
|
7443
|
+
const ahead = key === "ArrowRight" ? order.slice(index + 1) : order.slice(0, index).reverse();
|
|
7444
|
+
return firstSelectable(grid, ahead);
|
|
7445
|
+
}
|
|
7446
|
+
if (key === "ArrowDown" || key === "ArrowUp") return firstSelectable(grid, columnOrderFrom(grid, blocks, from, key === "ArrowDown" ? 1 : -1));
|
|
7447
|
+
const blockIndex = blockOf(blocks, from.column);
|
|
7448
|
+
const target = blocks[blockIndex + (key === "PageDown" ? 1 : -1)];
|
|
7449
|
+
if (blockIndex < 0 || !target) return;
|
|
7450
|
+
const same = {
|
|
7451
|
+
column: target.start + (from.column - blocks[blockIndex].start),
|
|
7452
|
+
row: from.row
|
|
7453
|
+
};
|
|
7454
|
+
const page = readingOrder(grid, [target]);
|
|
7455
|
+
const at = page.findIndex((cell) => cell.row > same.row || cell.row === same.row && cell.column >= same.column);
|
|
7456
|
+
const after = at === -1 ? [] : page.slice(at);
|
|
7457
|
+
const before = page.slice(0, at === -1 ? page.length : at).reverse();
|
|
7458
|
+
return firstSelectable(grid, after) ?? firstSelectable(grid, before);
|
|
7459
|
+
};
|
|
7460
|
+
//#endregion
|
|
6690
7461
|
//#region src/charts/heatmap-chart/heatmap-chart.tsx
|
|
6691
7462
|
const CELL_MIX_FLOOR = .15;
|
|
6692
7463
|
const NO_ROW_LABELS = [];
|
|
6693
|
-
const
|
|
7464
|
+
const TOOLTIP_BOX_STYLES = {
|
|
7465
|
+
light: {
|
|
7466
|
+
..._visx_tooltip.defaultStyles,
|
|
7467
|
+
zIndex: 3
|
|
7468
|
+
},
|
|
7469
|
+
dark: { zIndex: 3 }
|
|
7470
|
+
};
|
|
7471
|
+
const cellName = (info) => info.cellLabel || [
|
|
7472
|
+
info.groupLabel,
|
|
7473
|
+
info.columnLabel,
|
|
7474
|
+
info.rowLabel
|
|
7475
|
+
].filter(Boolean).join(" ");
|
|
7476
|
+
const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipVariant = "light", tooltipStyle, children }) => {
|
|
6694
7477
|
const chartId = useChartId(providedChartId);
|
|
7478
|
+
const tooltipBoxStyle = tooltipStyle ? {
|
|
7479
|
+
...TOOLTIP_BOX_STYLES[tooltipVariant],
|
|
7480
|
+
...tooltipStyle
|
|
7481
|
+
} : TOOLTIP_BOX_STYLES[tooltipVariant];
|
|
6695
7482
|
const { getElementStyles, theme } = useGlobalChartsContext();
|
|
6696
7483
|
const scopeElement = useChartScopeElement();
|
|
6697
7484
|
const { heatmapChart: heatmapChartSettings } = theme;
|
|
6698
7485
|
const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
|
|
6699
|
-
const [
|
|
7486
|
+
const [selected, setSelected] = (0, react.useState)();
|
|
6700
7487
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
|
|
6701
7488
|
const standaloneScopeClass = useStandaloneScopeClass();
|
|
6702
7489
|
const containerRef = (0, react.useRef)(null);
|
|
@@ -6715,69 +7502,75 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6715
7502
|
}), [extent, primaryColorHex]);
|
|
6716
7503
|
const columns = data.length;
|
|
6717
7504
|
const rows = Math.max(0, ...data.map((column) => column.data.length));
|
|
6718
|
-
const
|
|
7505
|
+
const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
|
|
7506
|
+
const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
|
|
6719
7507
|
const drawValues = showValues ?? !compact;
|
|
6720
7508
|
const buildTooltipData = (0, react.useCallback)((columnIndex, rowIndex) => {
|
|
6721
7509
|
const cell = data[columnIndex]?.data[rowIndex];
|
|
7510
|
+
const group = groupLayout.columns[columnIndex]?.group;
|
|
6722
7511
|
return {
|
|
6723
7512
|
value: cell?.value ?? null,
|
|
6724
7513
|
rowLabel: rowLabels[rowIndex],
|
|
6725
7514
|
columnLabel: data[columnIndex]?.label,
|
|
7515
|
+
groupLabel: group === void 0 ? void 0 : groupLayout.groups[group]?.label,
|
|
6726
7516
|
cellLabel: cell?.label,
|
|
6727
7517
|
row: rowIndex,
|
|
6728
7518
|
column: columnIndex
|
|
6729
7519
|
};
|
|
6730
|
-
}, [
|
|
7520
|
+
}, [
|
|
7521
|
+
data,
|
|
7522
|
+
rowLabels,
|
|
7523
|
+
groupLayout
|
|
7524
|
+
]);
|
|
6731
7525
|
const onChartBlur = (0, react.useCallback)(() => {
|
|
6732
|
-
|
|
7526
|
+
setSelected(void 0);
|
|
6733
7527
|
hideTooltip();
|
|
6734
7528
|
}, [hideTooltip]);
|
|
6735
7529
|
const isCellInert = (0, react.useCallback)((col, row) => {
|
|
6736
7530
|
const cell = data[col]?.data[row];
|
|
6737
7531
|
return cell?.hidden === true || cell?.placeholder === true;
|
|
6738
7532
|
}, [data]);
|
|
7533
|
+
const blocks = (0, react.useMemo)(() => {
|
|
7534
|
+
const grouped = groupLayout.groups.map((group, index) => {
|
|
7535
|
+
const start = groupLayout.columns.findIndex((column) => column.group === index);
|
|
7536
|
+
return {
|
|
7537
|
+
start,
|
|
7538
|
+
end: start + group.span
|
|
7539
|
+
};
|
|
7540
|
+
});
|
|
7541
|
+
const groupedEnd = grouped.length ? grouped[grouped.length - 1].end : 0;
|
|
7542
|
+
return groupedEnd < columns ? [...grouped, {
|
|
7543
|
+
start: groupedEnd,
|
|
7544
|
+
end: columns
|
|
7545
|
+
}] : grouped;
|
|
7546
|
+
}, [groupLayout, columns]);
|
|
6739
7547
|
const onChartKeyDown = (0, react.useCallback)((event) => {
|
|
6740
|
-
if (![
|
|
6741
|
-
"ArrowLeft",
|
|
6742
|
-
"ArrowRight",
|
|
6743
|
-
"ArrowUp",
|
|
6744
|
-
"ArrowDown",
|
|
6745
|
-
"Escape",
|
|
6746
|
-
"Tab"
|
|
6747
|
-
].includes(event.key)) return;
|
|
6748
7548
|
if (event.key === "Tab" || event.key === "Escape") {
|
|
6749
|
-
|
|
7549
|
+
setSelected(void 0);
|
|
6750
7550
|
hideTooltip();
|
|
6751
7551
|
return;
|
|
6752
7552
|
}
|
|
7553
|
+
if (!isNavigationKey(event.key, keyboardNavigation)) return;
|
|
6753
7554
|
event.preventDefault();
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
7555
|
+
const grid = {
|
|
7556
|
+
columns,
|
|
7557
|
+
rows,
|
|
7558
|
+
isInert: isCellInert
|
|
7559
|
+
};
|
|
7560
|
+
if (selected === void 0) {
|
|
7561
|
+
setSelected(keyboardNavigation === "calendar" ? firstCalendarCell(grid, blocks) : firstGridCell(grid));
|
|
6759
7562
|
return;
|
|
6760
7563
|
}
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
if (event.key === "ArrowRight") stepCol = 1;
|
|
6764
|
-
else if (event.key === "ArrowLeft") stepCol = -1;
|
|
6765
|
-
else if (event.key === "ArrowDown") stepRow = 1;
|
|
6766
|
-
else if (event.key === "ArrowUp") stepRow = -1;
|
|
6767
|
-
let col = Math.floor(selectedIndex / rows);
|
|
6768
|
-
let row = selectedIndex % rows;
|
|
6769
|
-
do {
|
|
6770
|
-
col += stepCol;
|
|
6771
|
-
row += stepRow;
|
|
6772
|
-
} while (col >= 0 && col < columns && row >= 0 && row < rows && isCellInert(col, row));
|
|
6773
|
-
if (col < 0 || col >= columns || row < 0 || row >= rows) return;
|
|
6774
|
-
setSelectedIndex(col * rows + row);
|
|
7564
|
+
const next = keyboardNavigation === "calendar" ? stepCalendarCell(grid, blocks, selected, event.key) : stepGridCell(grid, selected, event.key);
|
|
7565
|
+
if (next) setSelected(next);
|
|
6775
7566
|
}, [
|
|
6776
7567
|
rows,
|
|
6777
7568
|
columns,
|
|
6778
|
-
|
|
7569
|
+
selected,
|
|
6779
7570
|
hideTooltip,
|
|
6780
|
-
isCellInert
|
|
7571
|
+
isCellInert,
|
|
7572
|
+
keyboardNavigation,
|
|
7573
|
+
blocks
|
|
6781
7574
|
]);
|
|
6782
7575
|
const handleCellMouseMove = (0, react.useCallback)((event) => {
|
|
6783
7576
|
if (!withTooltips) return;
|
|
@@ -6797,35 +7590,47 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6797
7590
|
buildTooltipData,
|
|
6798
7591
|
getTooltipOrigin
|
|
6799
7592
|
]);
|
|
7593
|
+
const getCellElement = (0, react.useCallback)((col, row) => typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null, [chartId]);
|
|
6800
7594
|
const handleCellMouseLeave = (0, react.useCallback)(() => {
|
|
6801
|
-
if (withTooltips &&
|
|
7595
|
+
if (withTooltips && selected === void 0) hideTooltip();
|
|
6802
7596
|
}, [
|
|
6803
7597
|
withTooltips,
|
|
6804
|
-
|
|
7598
|
+
selected,
|
|
6805
7599
|
hideTooltip
|
|
6806
7600
|
]);
|
|
6807
7601
|
(0, react.useEffect)(() => {
|
|
6808
|
-
if (
|
|
7602
|
+
if (selected && (selected.column >= columns || selected.row >= rows)) setSelected(void 0);
|
|
7603
|
+
}, [
|
|
7604
|
+
selected,
|
|
7605
|
+
columns,
|
|
7606
|
+
rows
|
|
7607
|
+
]);
|
|
7608
|
+
(0, react.useEffect)(() => {
|
|
7609
|
+
if (selected === void 0) return;
|
|
7610
|
+
getCellElement(selected.column, selected.row)?.scrollIntoView?.({
|
|
7611
|
+
block: "nearest",
|
|
7612
|
+
inline: "nearest"
|
|
7613
|
+
});
|
|
7614
|
+
}, [selected, getCellElement]);
|
|
7615
|
+
(0, react.useEffect)(() => {
|
|
7616
|
+
if (!withTooltips || selected === void 0) return;
|
|
6809
7617
|
const origin = getTooltipOrigin();
|
|
6810
7618
|
if (!origin) return;
|
|
6811
|
-
const
|
|
6812
|
-
const row = selectedIndex % rows;
|
|
6813
|
-
const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
|
|
7619
|
+
const rect = getCellElement(selected.column, selected.row)?.getBoundingClientRect();
|
|
6814
7620
|
showTooltip({
|
|
6815
7621
|
tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
|
|
6816
7622
|
tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
|
|
6817
|
-
tooltipData: buildTooltipData(
|
|
7623
|
+
tooltipData: buildTooltipData(selected.column, selected.row)
|
|
6818
7624
|
});
|
|
6819
7625
|
}, [
|
|
6820
|
-
|
|
7626
|
+
selected,
|
|
6821
7627
|
withTooltips,
|
|
6822
|
-
|
|
6823
|
-
chartId,
|
|
7628
|
+
getCellElement,
|
|
6824
7629
|
buildTooltipData,
|
|
6825
7630
|
showTooltip,
|
|
6826
7631
|
getTooltipOrigin
|
|
6827
7632
|
]);
|
|
6828
|
-
const defaultRenderTooltip = (0, react.useCallback)((info) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: info
|
|
7633
|
+
const defaultRenderTooltip = (0, react.useCallback)((info) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: cellName(info) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value) })] }), []);
|
|
6829
7634
|
if (!columns || !rows) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
6830
7635
|
className: (0, clsx.default)("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className),
|
|
6831
7636
|
style: {
|
|
@@ -6839,10 +7644,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6839
7644
|
});
|
|
6840
7645
|
const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
|
|
6841
7646
|
const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
|
|
7647
|
+
const hasColumnLabels = data.some((column) => Boolean(column.label));
|
|
7648
|
+
const hasGroups = groupLayout.groups.length > 0;
|
|
7649
|
+
const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
|
|
7650
|
+
const firstDataRow = hasColumnLabels ? 2 : 1;
|
|
7651
|
+
const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
|
|
7652
|
+
const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
|
|
6842
7653
|
const gridStyle = {
|
|
6843
7654
|
"--a8c-charts-color-heatmap-primary": primaryColorHex,
|
|
6844
|
-
gridTemplateColumns: `auto ${data.
|
|
6845
|
-
gridTemplateRows:
|
|
7655
|
+
gridTemplateColumns: `auto ${data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ")}`,
|
|
7656
|
+
gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
|
|
6846
7657
|
};
|
|
6847
7658
|
if (compact) {
|
|
6848
7659
|
gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
|
|
@@ -6855,7 +7666,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6855
7666
|
[heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
|
|
6856
7667
|
};
|
|
6857
7668
|
};
|
|
6858
|
-
const activeDescendant =
|
|
7669
|
+
const activeDescendant = selected ? `${chartId}-cell-${selected.column}-${selected.row}` : void 0;
|
|
6859
7670
|
const heightCapped = !compact && Boolean(maxCellHeight);
|
|
6860
7671
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
|
|
6861
7672
|
value: heatmapContext,
|
|
@@ -6875,7 +7686,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6875
7686
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6876
7687
|
ref: containerRef,
|
|
6877
7688
|
role: "grid",
|
|
6878
|
-
"aria-label": (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
|
|
7689
|
+
"aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
|
|
6879
7690
|
"aria-rowcount": rows,
|
|
6880
7691
|
"aria-colcount": columns,
|
|
6881
7692
|
"aria-activedescendant": activeDescendant,
|
|
@@ -6884,76 +7695,115 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
|
|
|
6884
7695
|
onKeyDown: onChartKeyDown,
|
|
6885
7696
|
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__grid"], {
|
|
6886
7697
|
[heatmap_chart_module_default["heatmap-chart__grid--compact"]]: compact,
|
|
7698
|
+
[heatmap_chart_module_default["heatmap-chart__grid--flex-gaps"]]: compact && hasGroups,
|
|
6887
7699
|
[heatmap_chart_module_default["heatmap-chart__grid--height-capped"]]: heightCapped
|
|
6888
7700
|
}),
|
|
6889
7701
|
style: gridStyle,
|
|
6890
|
-
children: [
|
|
6891
|
-
|
|
6892
|
-
"aria-hidden": "true",
|
|
6893
|
-
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
6894
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
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
|
-
}),
|
|
6899
|
-
children: column.label
|
|
6900
|
-
}, `col-${columnIndex}`))]
|
|
6901
|
-
}), Array.from({ length: rows }).map((_row, rowIndex) => {
|
|
6902
|
-
const labelVisible = !compact || rowIndex % 2 === 0;
|
|
6903
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7702
|
+
children: [
|
|
7703
|
+
hasColumnLabels && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6904
7704
|
role: "row",
|
|
6905
|
-
"aria-
|
|
7705
|
+
"aria-hidden": "true",
|
|
6906
7706
|
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
6907
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
7707
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
7708
|
+
gridColumn: 1,
|
|
7709
|
+
gridRow: 1
|
|
7710
|
+
} }), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7711
|
+
style: {
|
|
7712
|
+
gridColumn: columnLine(columnIndex),
|
|
7713
|
+
gridRow: 1
|
|
7714
|
+
},
|
|
7715
|
+
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
|
|
7716
|
+
[heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
|
|
7717
|
+
...summaryGaps(columnIndex)
|
|
7718
|
+
}),
|
|
7719
|
+
children: column.label
|
|
7720
|
+
}, `col-${columnIndex}`))]
|
|
7721
|
+
}),
|
|
7722
|
+
Array.from({ length: rows }).map((_row, rowIndex) => {
|
|
7723
|
+
const labelVisible = !compact || rowIndex % 2 === 0;
|
|
7724
|
+
const gridRow = firstDataRow + rowIndex;
|
|
7725
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7726
|
+
role: "row",
|
|
7727
|
+
"aria-rowindex": rowIndex + 1,
|
|
7728
|
+
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
7729
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6918
7730
|
"aria-hidden": "true",
|
|
6919
|
-
className:
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
[heatmap_chart_module_default["heatmap-chart__cell--
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
7731
|
+
className: heatmap_chart_module_default["heatmap-chart__row-label"],
|
|
7732
|
+
style: {
|
|
7733
|
+
gridColumn: 1,
|
|
7734
|
+
gridRow
|
|
7735
|
+
},
|
|
7736
|
+
children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
|
|
7737
|
+
}), data.map((column, columnIndex) => {
|
|
7738
|
+
const cell = column.data[rowIndex];
|
|
7739
|
+
const placement = {
|
|
7740
|
+
gridColumn: columnLine(columnIndex),
|
|
7741
|
+
gridRow
|
|
7742
|
+
};
|
|
7743
|
+
if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7744
|
+
"aria-hidden": "true",
|
|
7745
|
+
style: placement,
|
|
7746
|
+
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--hidden"])
|
|
7747
|
+
}, `cell-${columnIndex}-${rowIndex}`);
|
|
7748
|
+
if (cell?.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7749
|
+
"aria-hidden": "true",
|
|
7750
|
+
style: placement,
|
|
7751
|
+
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--placeholder"])
|
|
7752
|
+
}, `cell-${columnIndex}-${rowIndex}`);
|
|
7753
|
+
const value = cell?.value ?? null;
|
|
7754
|
+
const present = isPresent(value);
|
|
7755
|
+
const filled = present && !column.summary;
|
|
7756
|
+
const normalized = filled ? getNormalizedValue(value, extent) : 0;
|
|
7757
|
+
const info = buildTooltipData(columnIndex, rowIndex);
|
|
7758
|
+
const accessibleLabel = `${cellName(info)}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
|
|
7759
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7760
|
+
id: `${chartId}-cell-${columnIndex}-${rowIndex}`,
|
|
7761
|
+
role: "gridcell",
|
|
7762
|
+
tabIndex: -1,
|
|
7763
|
+
"aria-colindex": columnIndex + 1,
|
|
7764
|
+
"aria-label": accessibleLabel,
|
|
7765
|
+
"data-column": columnIndex,
|
|
7766
|
+
"data-row": rowIndex,
|
|
7767
|
+
className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
|
|
7768
|
+
[heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
|
|
7769
|
+
[heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
|
|
7770
|
+
[heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
|
|
7771
|
+
...summaryGaps(columnIndex),
|
|
7772
|
+
[heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
|
|
7773
|
+
}),
|
|
7774
|
+
style: {
|
|
7775
|
+
...placement,
|
|
7776
|
+
...filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : {}
|
|
7777
|
+
},
|
|
7778
|
+
onMouseMove: handleCellMouseMove,
|
|
7779
|
+
onMouseLeave: handleCellMouseLeave,
|
|
7780
|
+
children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7781
|
+
className: heatmap_chart_module_default["heatmap-chart__cell-value"],
|
|
7782
|
+
children: (0, _automattic_number_formatters.formatNumberCompact)(value)
|
|
7783
|
+
})
|
|
7784
|
+
}, `cell-${columnIndex}-${rowIndex}`);
|
|
7785
|
+
})]
|
|
7786
|
+
}, `row-${rowIndex}`);
|
|
7787
|
+
}),
|
|
7788
|
+
hasGroups && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7789
|
+
role: "row",
|
|
7790
|
+
"aria-hidden": "true",
|
|
7791
|
+
className: heatmap_chart_module_default["heatmap-chart__row"],
|
|
7792
|
+
children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7793
|
+
className: heatmap_chart_module_default["heatmap-chart__group-label"],
|
|
7794
|
+
style: {
|
|
7795
|
+
gridColumn: `${group.line} / span ${group.span}`,
|
|
7796
|
+
gridRow: firstDataRow + rows
|
|
7797
|
+
},
|
|
7798
|
+
children: group.label
|
|
7799
|
+
}, `group-${groupIndex}`))
|
|
7800
|
+
})
|
|
7801
|
+
]
|
|
6954
7802
|
}), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
|
|
6955
7803
|
top: tooltipTop,
|
|
6956
7804
|
left: tooltipLeft,
|
|
7805
|
+
className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
|
|
7806
|
+
style: tooltipBoxStyle,
|
|
6957
7807
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6958
7808
|
className: standaloneScopeClass,
|
|
6959
7809
|
role: "tooltip",
|
|
@@ -6989,6 +7839,43 @@ const KEY_OPTIONS = {
|
|
|
6989
7839
|
day: "2-digit"
|
|
6990
7840
|
};
|
|
6991
7841
|
const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
7842
|
+
/**
|
|
7843
|
+
* Label formatters for UTC proxies, so both calendar layouts name a day alike.
|
|
7844
|
+
*
|
|
7845
|
+
* `gregory` keeps a locale whose default calendar is not Gregorian (fa-IR) from
|
|
7846
|
+
* splitting one grid month across two names.
|
|
7847
|
+
*
|
|
7848
|
+
* @param locale - BCP 47 locale.
|
|
7849
|
+
* @return Weekday, month and full-day formatters.
|
|
7850
|
+
*/
|
|
7851
|
+
const civilLabelFormatters = (locale) => {
|
|
7852
|
+
const formatting = {
|
|
7853
|
+
locale,
|
|
7854
|
+
timeZone: "UTC"
|
|
7855
|
+
};
|
|
7856
|
+
return {
|
|
7857
|
+
formatWeekday: createDateFormatter({
|
|
7858
|
+
weekday: "short",
|
|
7859
|
+
calendar: "gregory"
|
|
7860
|
+
}, formatting),
|
|
7861
|
+
formatMonth: createDateFormatter({
|
|
7862
|
+
month: "short",
|
|
7863
|
+
calendar: "gregory"
|
|
7864
|
+
}, formatting),
|
|
7865
|
+
formatMonthYear: createDateFormatter({
|
|
7866
|
+
month: "short",
|
|
7867
|
+
year: "numeric",
|
|
7868
|
+
calendar: "gregory"
|
|
7869
|
+
}, formatting),
|
|
7870
|
+
formatDay: createDateFormatter({
|
|
7871
|
+
weekday: "short",
|
|
7872
|
+
month: "short",
|
|
7873
|
+
day: "numeric",
|
|
7874
|
+
year: "numeric",
|
|
7875
|
+
calendar: "gregory"
|
|
7876
|
+
}, formatting)
|
|
7877
|
+
};
|
|
7878
|
+
};
|
|
6992
7879
|
const pad = (value, length) => String(value).padStart(length, "0");
|
|
6993
7880
|
/**
|
|
6994
7881
|
* The day a UTC proxy stands for.
|
|
@@ -7154,18 +8041,7 @@ const buildCalendarHeatmapData = (series, options = {}) => {
|
|
|
7154
8041
|
const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
|
|
7155
8042
|
const requestedMinDate = civilDate(requestedMinDayKey);
|
|
7156
8043
|
const gridMaxDate = civilDate(gridMaxDayKey);
|
|
7157
|
-
const
|
|
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);
|
|
8044
|
+
const { formatWeekday, formatMonth, formatDay } = civilLabelFormatters(locale);
|
|
7169
8045
|
const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
|
|
7170
8046
|
const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
|
|
7171
8047
|
const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
|
|
@@ -7206,6 +8082,89 @@ const buildCalendarHeatmapData = (series, options = {}) => {
|
|
|
7206
8082
|
};
|
|
7207
8083
|
};
|
|
7208
8084
|
//#endregion
|
|
8085
|
+
//#region src/charts/heatmap-chart/build-month-calendar-data.ts
|
|
8086
|
+
/** Week rows per month, fixed so every month block is the same height. */
|
|
8087
|
+
const MONTH_ROWS = 6;
|
|
8088
|
+
const DAYS_PER_WEEK = 7;
|
|
8089
|
+
const startOfCivilMonth = (date) => {
|
|
8090
|
+
const first = new Date(date.getTime());
|
|
8091
|
+
first.setUTCDate(1);
|
|
8092
|
+
return first;
|
|
8093
|
+
};
|
|
8094
|
+
const startOfNextCivilMonth = (date) => {
|
|
8095
|
+
const next = startOfCivilMonth(date);
|
|
8096
|
+
next.setUTCMonth(next.getUTCMonth() + 1);
|
|
8097
|
+
return next;
|
|
8098
|
+
};
|
|
8099
|
+
/**
|
|
8100
|
+
* Lay a day-keyed map out as one month calendar per month, weekdays across and
|
|
8101
|
+
* weeks down, for `HeatmapChart` with `columnGroups`.
|
|
8102
|
+
*
|
|
8103
|
+
* @param valueByDay - Value per `yyyy-MM-dd`. A missing day is `null`.
|
|
8104
|
+
* @param range - The measured days; every month from `start`'s through `end`'s is drawn.
|
|
8105
|
+
* @param options - Week start and label locale.
|
|
8106
|
+
* @return Columns and one group per month; empty for an invalid or reversed range.
|
|
8107
|
+
*/
|
|
8108
|
+
const buildMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
|
|
8109
|
+
const start = civilDate(range.start);
|
|
8110
|
+
const end = civilDate(range.end);
|
|
8111
|
+
if (!start || !end || start.getTime() > end.getTime()) {
|
|
8112
|
+
warnOnce(`heatmap:monthRange:${range.start}:${range.end}`, `range ${JSON.stringify(range)} must be two \`yyyy-MM-dd\` days with start on or before end, so the calendar is empty.`);
|
|
8113
|
+
return {
|
|
8114
|
+
data: [],
|
|
8115
|
+
columnGroups: []
|
|
8116
|
+
};
|
|
8117
|
+
}
|
|
8118
|
+
const startKey = civilKey(start);
|
|
8119
|
+
const endKey = civilKey(end);
|
|
8120
|
+
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
8121
|
+
const { locale } = sanitizeFormatting({ locale: options.locale });
|
|
8122
|
+
for (const key of Object.keys(valueByDay)) if (!civilDate(key)) warnOnce(`heatmap:monthKey:${key}`, `${JSON.stringify(key)} is not a \`yyyy-MM-dd\` day, so its value is left out of the calendar.`);
|
|
8123
|
+
const readValue = (key) => {
|
|
8124
|
+
const value = valueByDay[key];
|
|
8125
|
+
return isPresent(value) ? value : null;
|
|
8126
|
+
};
|
|
8127
|
+
const { formatMonth, formatMonthYear, formatDay } = civilLabelFormatters(locale);
|
|
8128
|
+
const data = [];
|
|
8129
|
+
const columnGroups = [];
|
|
8130
|
+
const lastMonthStart = startOfCivilMonth(end);
|
|
8131
|
+
const formatGroup = (lastMonthStart.getUTCFullYear() - start.getUTCFullYear()) * 12 + lastMonthStart.getUTCMonth() - start.getUTCMonth() + 1 > 12 ? formatMonthYear : formatMonth;
|
|
8132
|
+
for (let monthStart = startOfCivilMonth(start); monthStart.getTime() <= lastMonthStart.getTime(); monthStart = startOfNextCivilMonth(monthStart)) {
|
|
8133
|
+
const monthStartKey = civilKey(monthStart);
|
|
8134
|
+
const monthEndKey = civilKey(addCivilDays(startOfNextCivilMonth(monthStart), -1));
|
|
8135
|
+
const gridStart = startOfCivilWeek(monthStart, weekStartsOn);
|
|
8136
|
+
for (let weekday = 0; weekday < DAYS_PER_WEEK; weekday++) {
|
|
8137
|
+
const cells = [];
|
|
8138
|
+
for (let week = 0; week < MONTH_ROWS; week++) {
|
|
8139
|
+
const day = addCivilDays(gridStart, week * DAYS_PER_WEEK + weekday);
|
|
8140
|
+
const key = civilKey(day);
|
|
8141
|
+
if (key < monthStartKey || key > monthEndKey) cells.push({
|
|
8142
|
+
value: null,
|
|
8143
|
+
hidden: true
|
|
8144
|
+
});
|
|
8145
|
+
else if (key < startKey || key > endKey) cells.push({
|
|
8146
|
+
label: formatDay(day),
|
|
8147
|
+
value: null,
|
|
8148
|
+
placeholder: true
|
|
8149
|
+
});
|
|
8150
|
+
else cells.push({
|
|
8151
|
+
label: formatDay(day),
|
|
8152
|
+
value: readValue(key)
|
|
8153
|
+
});
|
|
8154
|
+
}
|
|
8155
|
+
data.push({ data: cells });
|
|
8156
|
+
}
|
|
8157
|
+
columnGroups.push({
|
|
8158
|
+
label: formatGroup(monthStart),
|
|
8159
|
+
span: DAYS_PER_WEEK
|
|
8160
|
+
});
|
|
8161
|
+
}
|
|
8162
|
+
return {
|
|
8163
|
+
data,
|
|
8164
|
+
columnGroups
|
|
8165
|
+
};
|
|
8166
|
+
};
|
|
8167
|
+
//#endregion
|
|
7209
8168
|
//#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
|
|
7210
8169
|
/**
|
|
7211
8170
|
* `buildCalendarHeatmapData` with the locale and zone taken from
|
|
@@ -7242,6 +8201,36 @@ const useCalendarHeatmapData = (series, options = {}) => {
|
|
|
7242
8201
|
]);
|
|
7243
8202
|
};
|
|
7244
8203
|
//#endregion
|
|
8204
|
+
//#region src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts
|
|
8205
|
+
/**
|
|
8206
|
+
* `buildMonthCalendarHeatmapData` with the locale taken from `GlobalChartsProvider`
|
|
8207
|
+
* where the caller names none.
|
|
8208
|
+
*
|
|
8209
|
+
* @param valueByDay - Value per `yyyy-MM-dd`. Held by reference, so a caller that rebuilds it each render defeats the memo.
|
|
8210
|
+
* @param range - As for `buildMonthCalendarHeatmapData`.
|
|
8211
|
+
* @param options - As for `buildMonthCalendarHeatmapData`; `locale` wins over the provider.
|
|
8212
|
+
* @return Columns and groups for `HeatmapChart`.
|
|
8213
|
+
*/
|
|
8214
|
+
const useMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
|
|
8215
|
+
const formatting = useChartFormatting();
|
|
8216
|
+
const locale = options.locale ?? formatting.locale;
|
|
8217
|
+
const { weekStartsOn } = options;
|
|
8218
|
+
const { start, end } = range;
|
|
8219
|
+
return (0, react.useMemo)(() => buildMonthCalendarHeatmapData(valueByDay, {
|
|
8220
|
+
start,
|
|
8221
|
+
end
|
|
8222
|
+
}, {
|
|
8223
|
+
locale,
|
|
8224
|
+
weekStartsOn
|
|
8225
|
+
}), [
|
|
8226
|
+
valueByDay,
|
|
8227
|
+
start,
|
|
8228
|
+
end,
|
|
8229
|
+
locale,
|
|
8230
|
+
weekStartsOn
|
|
8231
|
+
]);
|
|
8232
|
+
};
|
|
8233
|
+
//#endregion
|
|
7245
8234
|
//#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
|
|
7246
8235
|
/*!
|
|
7247
8236
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
@@ -7605,7 +8594,24 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7605
8594
|
tslib_1.__exportStar(require_dist$1(), exports);
|
|
7606
8595
|
}));
|
|
7607
8596
|
//#endregion
|
|
7608
|
-
//#region ../../../node_modules/.pnpm/@wordpress+
|
|
8597
|
+
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/utils.mjs
|
|
8598
|
+
var logged = /* @__PURE__ */ new Set();
|
|
8599
|
+
//#endregion
|
|
8600
|
+
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/index.mjs
|
|
8601
|
+
function isDev() {
|
|
8602
|
+
return globalThis.SCRIPT_DEBUG === true;
|
|
8603
|
+
}
|
|
8604
|
+
function warning(message) {
|
|
8605
|
+
if (!isDev()) return;
|
|
8606
|
+
if (logged.has(message)) return;
|
|
8607
|
+
console.warn(message);
|
|
8608
|
+
try {
|
|
8609
|
+
throw Error(message);
|
|
8610
|
+
} catch {}
|
|
8611
|
+
logged.add(message);
|
|
8612
|
+
}
|
|
8613
|
+
//#endregion
|
|
8614
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
|
|
7609
8615
|
function useUpdateEffect(effect, deps) {
|
|
7610
8616
|
const mountedRef = (0, react.useRef)(false);
|
|
7611
8617
|
(0, react.useEffect)(() => {
|
|
@@ -9243,7 +10249,7 @@ _createEmotion.css;
|
|
|
9243
10249
|
_createEmotion.sheet;
|
|
9244
10250
|
_createEmotion.cache;
|
|
9245
10251
|
//#endregion
|
|
9246
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10252
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
|
|
9247
10253
|
var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
|
|
9248
10254
|
var useCx = () => {
|
|
9249
10255
|
const cache = __unsafe_useEmotionCache();
|
|
@@ -9350,7 +10356,7 @@ function memize(fn, options) {
|
|
|
9350
10356
|
return memoized;
|
|
9351
10357
|
}
|
|
9352
10358
|
//#endregion
|
|
9353
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10359
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
|
|
9354
10360
|
var white = "#fff";
|
|
9355
10361
|
var GRAY = {
|
|
9356
10362
|
900: "#1e1e1e",
|
|
@@ -9400,8 +10406,8 @@ var UI = {
|
|
|
9400
10406
|
border: THEME.gray[600],
|
|
9401
10407
|
borderHover: THEME.gray[700],
|
|
9402
10408
|
borderFocus: THEME.accent,
|
|
9403
|
-
borderDisabled:
|
|
9404
|
-
textDisabled:
|
|
10409
|
+
borderDisabled: `var(--wpds-color-stroke-interactive-neutral-disabled, #dbdbdb)`,
|
|
10410
|
+
textDisabled: `var(--wpds-color-foreground-interactive-neutral-disabled, #8d8d8d)`,
|
|
9405
10411
|
darkGrayPlaceholder: `color-mix(in srgb, ${THEME.foreground}, transparent 38%)`,
|
|
9406
10412
|
lightGrayPlaceholder: `color-mix(in srgb, ${THEME.background}, transparent 35%)`
|
|
9407
10413
|
};
|
|
@@ -9429,7 +10435,7 @@ var COLORS = Object.freeze({
|
|
|
9429
10435
|
ui: UI
|
|
9430
10436
|
});
|
|
9431
10437
|
//#endregion
|
|
9432
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10438
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/config-values.mjs
|
|
9433
10439
|
var CONTROL_HEIGHT = "36px";
|
|
9434
10440
|
var CONTROL_PROPS = {
|
|
9435
10441
|
controlPaddingX: 12,
|
|
@@ -9483,8 +10489,8 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
|
|
|
9483
10489
|
transitionTimingFunctionControl: "cubic-bezier(0.12, 0.8, 0.32, 1)"
|
|
9484
10490
|
});
|
|
9485
10491
|
//#endregion
|
|
9486
|
-
//#region ../../../node_modules/.pnpm
|
|
9487
|
-
var
|
|
10492
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
|
|
10493
|
+
var import_es6 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
9488
10494
|
module.exports = function equal(a, b) {
|
|
9489
10495
|
if (a === b) return true;
|
|
9490
10496
|
if (a && b && typeof a == "object" && typeof b == "object") {
|
|
@@ -9528,31 +10534,8 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9528
10534
|
}
|
|
9529
10535
|
return a !== a && b !== b;
|
|
9530
10536
|
};
|
|
9531
|
-
}));
|
|
9532
|
-
|
|
9533
|
-
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
|
|
9534
|
-
var logged = /* @__PURE__ */ new Set();
|
|
9535
|
-
//#endregion
|
|
9536
|
-
//#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
|
|
9537
|
-
function isDev() {
|
|
9538
|
-
return globalThis.SCRIPT_DEBUG === true;
|
|
9539
|
-
}
|
|
9540
|
-
function warning(message) {
|
|
9541
|
-
if (!isDev()) return;
|
|
9542
|
-
if (logged.has(message)) return;
|
|
9543
|
-
console.warn(message);
|
|
9544
|
-
try {
|
|
9545
|
-
throw Error(message);
|
|
9546
|
-
} catch {}
|
|
9547
|
-
logged.add(message);
|
|
9548
|
-
}
|
|
9549
|
-
//#endregion
|
|
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
|
|
9551
|
-
var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
|
|
9552
|
-
var ComponentsContext = (0, react.createContext)(
|
|
9553
|
-
/** @type {Record<string, any>} */
|
|
9554
|
-
{}
|
|
9555
|
-
);
|
|
10537
|
+
})))(), 1);
|
|
10538
|
+
var ComponentsContext = (0, react.createContext)({});
|
|
9556
10539
|
ComponentsContext.displayName = "ComponentsContext";
|
|
9557
10540
|
var useComponentsContext = () => (0, react.useContext)(ComponentsContext);
|
|
9558
10541
|
function useContextSystemBridge({ value }) {
|
|
@@ -9574,19 +10557,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
|
|
|
9574
10557
|
};
|
|
9575
10558
|
(0, react.memo)(BaseContextSystemProvider);
|
|
9576
10559
|
//#endregion
|
|
9577
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10560
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/constants.mjs
|
|
9578
10561
|
var COMPONENT_NAMESPACE = "data-wp-component";
|
|
9579
10562
|
var CONNECTED_NAMESPACE = "data-wp-c16t";
|
|
9580
10563
|
var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
|
|
9581
10564
|
//#endregion
|
|
9582
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10565
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
|
|
9583
10566
|
var import_dist = require_dist();
|
|
9584
10567
|
function getStyledClassName(namespace) {
|
|
9585
10568
|
return `components-${(0, import_dist.paramCase)(namespace)}`;
|
|
9586
10569
|
}
|
|
9587
10570
|
var getStyledClassNameFromKey = memize(getStyledClassName);
|
|
9588
10571
|
//#endregion
|
|
9589
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10572
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/context-connect.mjs
|
|
9590
10573
|
function contextConnect(Component, namespace) {
|
|
9591
10574
|
return _contextConnect(Component, namespace, { forwardsRef: true });
|
|
9592
10575
|
}
|
|
@@ -9603,7 +10586,7 @@ function _contextConnect(Component, namespace, options) {
|
|
|
9603
10586
|
});
|
|
9604
10587
|
}
|
|
9605
10588
|
//#endregion
|
|
9606
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10589
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/utils.mjs
|
|
9607
10590
|
function getNamespace(componentName) {
|
|
9608
10591
|
return { [COMPONENT_NAMESPACE]: componentName };
|
|
9609
10592
|
}
|
|
@@ -9611,7 +10594,7 @@ function getConnectedNamespace() {
|
|
|
9611
10594
|
return { [CONNECTED_NAMESPACE]: true };
|
|
9612
10595
|
}
|
|
9613
10596
|
//#endregion
|
|
9614
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10597
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
|
|
9615
10598
|
function useContextSystem(props, namespace) {
|
|
9616
10599
|
const contextSystemProps = useComponentsContext();
|
|
9617
10600
|
if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
|
|
@@ -9620,10 +10603,12 @@ function useContextSystem(props, namespace) {
|
|
|
9620
10603
|
...getConnectedNamespace(),
|
|
9621
10604
|
...getNamespace(namespace)
|
|
9622
10605
|
};
|
|
9623
|
-
const { _overrides
|
|
10606
|
+
const { _overrides, ...otherContextProps } = contextProps;
|
|
10607
|
+
const overrideProps = _overrides;
|
|
9624
10608
|
const initialMergedProps = Object.entries(otherContextProps).length ? Object.assign({}, otherContextProps, props) : props;
|
|
10609
|
+
const propsWithChildren = initialMergedProps;
|
|
9625
10610
|
const classes = useCx()(getStyledClassNameFromKey(namespace), props.className);
|
|
9626
|
-
const rendered = typeof
|
|
10611
|
+
const rendered = typeof propsWithChildren.renderChildren === "function" ? propsWithChildren.renderChildren(initialMergedProps) : propsWithChildren.children;
|
|
9627
10612
|
for (const key in initialMergedProps) finalComponentProps[key] = initialMergedProps[key];
|
|
9628
10613
|
for (const key in overrideProps) finalComponentProps[key] = overrideProps[key];
|
|
9629
10614
|
if (rendered !== void 0) finalComponentProps.children = rendered;
|
|
@@ -9631,7 +10616,7 @@ function useContextSystem(props, namespace) {
|
|
|
9631
10616
|
return finalComponentProps;
|
|
9632
10617
|
}
|
|
9633
10618
|
//#endregion
|
|
9634
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10619
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
|
|
9635
10620
|
var visuallyHidden = {
|
|
9636
10621
|
border: 0,
|
|
9637
10622
|
clip: "rect(1px, 1px, 1px, 1px)",
|
|
@@ -9647,7 +10632,7 @@ var visuallyHidden = {
|
|
|
9647
10632
|
wordBreak: "normal"
|
|
9648
10633
|
};
|
|
9649
10634
|
//#endregion
|
|
9650
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10635
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
|
|
9651
10636
|
var customAttributeRegExp = /^(data|aria|x)-/i;
|
|
9652
10637
|
var eventHandlerRegExp = /^on[A-Z]/;
|
|
9653
10638
|
var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
|
|
@@ -9737,7 +10722,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
|
|
|
9737
10722
|
}
|
|
9738
10723
|
var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
|
|
9739
10724
|
//#endregion
|
|
9740
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10725
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/view/component.mjs
|
|
9741
10726
|
function UnforwardedView({ css, ...restProps }, ref) {
|
|
9742
10727
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
|
|
9743
10728
|
ref,
|
|
@@ -9746,7 +10731,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
|
|
|
9746
10731
|
}
|
|
9747
10732
|
var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
|
|
9748
10733
|
//#endregion
|
|
9749
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10734
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
|
|
9750
10735
|
function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
9751
10736
|
const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
|
|
9752
10737
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
|
|
@@ -9761,7 +10746,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
|
|
|
9761
10746
|
}
|
|
9762
10747
|
var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
|
|
9763
10748
|
//#endregion
|
|
9764
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10749
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
|
|
9765
10750
|
var breakpoints = [
|
|
9766
10751
|
"40em",
|
|
9767
10752
|
"52em",
|
|
@@ -9795,7 +10780,7 @@ function useResponsiveValue(values, options = {}) {
|
|
|
9795
10780
|
return array[index >= array.length ? array.length - 1 : index];
|
|
9796
10781
|
}
|
|
9797
10782
|
//#endregion
|
|
9798
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10783
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/utils.mjs
|
|
9799
10784
|
var ALIGNMENTS = {
|
|
9800
10785
|
bottom: {
|
|
9801
10786
|
alignItems: "flex-end",
|
|
@@ -9843,7 +10828,7 @@ function getAlignmentProps(alignment) {
|
|
|
9843
10828
|
return alignment ? ALIGNMENTS[alignment] : {};
|
|
9844
10829
|
}
|
|
9845
10830
|
//#endregion
|
|
9846
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10831
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/hook.mjs
|
|
9847
10832
|
function useGrid(props) {
|
|
9848
10833
|
const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
|
|
9849
10834
|
const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
|
|
@@ -9885,7 +10870,7 @@ function useGrid(props) {
|
|
|
9885
10870
|
};
|
|
9886
10871
|
}
|
|
9887
10872
|
//#endregion
|
|
9888
|
-
//#region ../../../node_modules/.pnpm/@wordpress+components@40.
|
|
10873
|
+
//#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/component.mjs
|
|
9889
10874
|
function UnconnectedGrid(props, forwardedRef) {
|
|
9890
10875
|
const gridProps = useGrid(props);
|
|
9891
10876
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
|
|
@@ -11171,6 +12156,7 @@ exports.SparklineUnresponsive = SparklineUnresponsive;
|
|
|
11171
12156
|
exports.ThemeProvider = GlobalChartsProvider;
|
|
11172
12157
|
exports.TrendIndicator = TrendIndicator;
|
|
11173
12158
|
exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
|
|
12159
|
+
exports.buildMonthCalendarHeatmapData = buildMonthCalendarHeatmapData;
|
|
11174
12160
|
exports.defaultTheme = defaultTheme;
|
|
11175
12161
|
exports.formatMetricValue = formatMetricValue;
|
|
11176
12162
|
exports.formatPercentage = formatPercentage;
|
|
@@ -11195,6 +12181,7 @@ exports.useChartScopeElement = useChartScopeElement;
|
|
|
11195
12181
|
exports.useGlobalChartsContext = useGlobalChartsContext;
|
|
11196
12182
|
exports.useGlobalChartsTheme = useGlobalChartsTheme;
|
|
11197
12183
|
exports.useLeaderboardLegendItems = useLeaderboardLegendItems;
|
|
12184
|
+
exports.useMonthCalendarHeatmapData = useMonthCalendarHeatmapData;
|
|
11198
12185
|
exports.validateHexColor = validateHexColor;
|
|
11199
12186
|
|
|
11200
12187
|
//# sourceMappingURL=index.cjs.map
|