@automattic/charts 1.7.0 → 1.8.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 +8 -0
- package/dist/index.cjs +358 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +6 -4
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +358 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/charts/bar-chart/bar-chart.tsx +196 -42
- package/src/charts/bar-chart/private/comparison-bars-geometry.ts +70 -0
- package/src/charts/bar-chart/private/comparison-bars.tsx +154 -0
- package/src/charts/bar-chart/private/comparison-constants.ts +33 -0
- package/src/charts/bar-chart/private/index.ts +9 -0
- package/src/charts/bar-chart/private/test/comparison-bars-geometry.test.ts +47 -0
- package/src/charts/bar-chart/private/test/comparison-bars.test.tsx +183 -0
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +46 -5
- package/src/charts/bar-chart/test/bar-chart.test.tsx +191 -1
- package/src/charts/geo-chart/geo-chart.tsx +5 -9
- package/src/charts/pie-chart/pie-chart.module.scss +0 -4
- package/src/charts/pie-chart/pie-chart.tsx +3 -8
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +0 -5
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +3 -8
- package/src/charts/private/center/center.module.scss +4 -0
- package/src/charts/private/center/center.tsx +33 -0
- package/src/charts/private/center/index.ts +2 -0
- package/src/charts/private/center/test/center.test.tsx +60 -0
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +0 -2
- package/src/charts/private/svg-empty-state/svg-empty-state.tsx +2 -4
- package/src/providers/chart-context/global-charts-provider.tsx +2 -0
- package/src/providers/chart-context/test/chart-context.test.tsx +13 -1
- package/src/providers/chart-context/themes.ts +8 -0
- package/src/providers/chart-context/types.ts +2 -0
- package/src/types.ts +16 -0
- package/src/utils/get-styles.ts +36 -13
- package/src/utils/index.ts +6 -1
- package/src/utils/test/get-styles.test.ts +47 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.8.0] - 2026-06-24
|
|
9
|
+
### Added
|
|
10
|
+
- Add comparison mode to the Bar Chart — a translucent shadow bar (standard slot width, 50% opacity) rendered behind each primary bar, paired by group. Primary bars are narrowed to 1/widthFactor of the slot (default widthFactor 1.5 → ~67% width, centered), with widthFactor as the single control. [#49676]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Add an internal Center layout primitive and use it for centered chart wrappers. [#49164]
|
|
14
|
+
|
|
8
15
|
## [1.7.0] - 2026-06-23
|
|
9
16
|
### Added
|
|
10
17
|
- Leaderboard interactive rows gain an opt-in `ariaLabel` for image-only labels, and the hover affordance now shrinks each bar in proportion to its length so small-share rows stay consistent. [#49812]
|
|
@@ -878,6 +885,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
878
885
|
- Fixed lints following ESLint rule changes for TS [#40584]
|
|
879
886
|
- Fixing a bug in Chart storybook data. [#40640]
|
|
880
887
|
|
|
888
|
+
[1.8.0]: https://github.com/Automattic/charts/compare/v1.7.0...v1.8.0
|
|
881
889
|
[1.7.0]: https://github.com/Automattic/charts/compare/v1.6.0...v1.7.0
|
|
882
890
|
[1.6.0]: https://github.com/Automattic/charts/compare/v1.5.3...v1.6.0
|
|
883
891
|
[1.5.3]: https://github.com/Automattic/charts/compare/v1.5.2...v1.5.3
|
package/dist/index.cjs
CHANGED
|
@@ -266,6 +266,20 @@ function getSeriesLineStyles(seriesData, index, providerTheme) {
|
|
|
266
266
|
return seriesData.options?.seriesLineStyle ?? themeSemanticLineStyle ?? themeSeriesLineStyle ?? {};
|
|
267
267
|
}
|
|
268
268
|
/**
|
|
269
|
+
* Utility to get consolidated bar styles for a series by semantic type.
|
|
270
|
+
* Mirrors getSeriesLineStyles: a series with `options.type` (e.g. 'comparison')
|
|
271
|
+
* resolves to `theme.barChart.barStyles[ type ]`.
|
|
272
|
+
*
|
|
273
|
+
* @param {SeriesData} seriesData - The series data containing styling options
|
|
274
|
+
* @param {number} index - The index of the series in the data array
|
|
275
|
+
* @param {ChartTheme} providerTheme - The chart theme configuration
|
|
276
|
+
* @return {BarStyles} The consolidated bar styles for the series
|
|
277
|
+
*/
|
|
278
|
+
function getSeriesBarStyles(seriesData, index, providerTheme) {
|
|
279
|
+
const type = seriesData.options?.type;
|
|
280
|
+
return (type && providerTheme?.barChart?.barStyles?.[type]) ?? {};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
269
283
|
* Utility function to get shape styles for a legend item
|
|
270
284
|
*
|
|
271
285
|
* @param {SeriesData} series - The series data containing styling options
|
|
@@ -277,13 +291,17 @@ function getSeriesLineStyles(seriesData, index, providerTheme) {
|
|
|
277
291
|
function getItemShapeStyles(series, index, theme, legendShape) {
|
|
278
292
|
const seriesShapeStyles = series.options?.legendShapeStyle ?? {};
|
|
279
293
|
const lineStyles = legendShape === "line" ? getSeriesLineStyles(series, index, theme) : {};
|
|
294
|
+
const barOpacity = legendShape !== "line" ? getSeriesBarStyles(series, index, theme).opacity : void 0;
|
|
295
|
+
const barShapeStyles = barOpacity !== void 0 ? { opacity: barOpacity } : {};
|
|
280
296
|
const themeShapeStyles = theme.legend?.shapeStyles?.[index];
|
|
281
|
-
const
|
|
297
|
+
const explicitStyles = {
|
|
282
298
|
...seriesShapeStyles,
|
|
283
299
|
...lineStyles
|
|
284
300
|
};
|
|
285
|
-
|
|
286
|
-
|
|
301
|
+
return {
|
|
302
|
+
...Object.values(explicitStyles).some((value) => value !== void 0 && value !== null && value !== "") ? explicitStyles : themeShapeStyles ?? {},
|
|
303
|
+
...barShapeStyles
|
|
304
|
+
};
|
|
287
305
|
}
|
|
288
306
|
//#endregion
|
|
289
307
|
//#region src/utils/is-safari.ts
|
|
@@ -729,6 +747,10 @@ const defaultTheme = {
|
|
|
729
747
|
strokeDasharray: "4 4",
|
|
730
748
|
strokeLinecap: "square"
|
|
731
749
|
} } },
|
|
750
|
+
barChart: { barStyles: { comparison: {
|
|
751
|
+
widthFactor: 1.5,
|
|
752
|
+
opacity: .5
|
|
753
|
+
} } },
|
|
732
754
|
sparkline: {
|
|
733
755
|
margin: {
|
|
734
756
|
top: 2,
|
|
@@ -835,6 +857,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
|
|
|
835
857
|
overrideColor: overrideColor || isSeriesData && data?.options?.stroke || isPointPercentageData && data?.color
|
|
836
858
|
}),
|
|
837
859
|
lineStyles: isSeriesData ? getSeriesLineStyles(data, index, providerTheme) : {},
|
|
860
|
+
barStyles: isSeriesData ? getSeriesBarStyles(data, index, providerTheme) : {},
|
|
838
861
|
glyph: providerTheme.glyphs?.[index],
|
|
839
862
|
shapeStyles: isSeriesData ? getItemShapeStyles(data, index, providerTheme, legendShape) : {}
|
|
840
863
|
};
|
|
@@ -2639,6 +2662,32 @@ const DefaultGlyph = (props) => {
|
|
|
2639
2662
|
});
|
|
2640
2663
|
};
|
|
2641
2664
|
//#endregion
|
|
2665
|
+
//#region src/charts/private/center/center.module.scss
|
|
2666
|
+
var center_module_default = { "center": "a8ccharts-w3qxlG-center" };
|
|
2667
|
+
//#endregion
|
|
2668
|
+
//#region src/charts/private/center/center.tsx
|
|
2669
|
+
/**
|
|
2670
|
+
* Centers its children on both axes and fills its parent.
|
|
2671
|
+
*
|
|
2672
|
+
* A thin wrapper around `Stack` with `align="center"` and `justify="center"`
|
|
2673
|
+
* defaults (both overridable) plus `width: 100%; height: 100%`. Reads more
|
|
2674
|
+
* honestly than a `Stack` with both axes centered, and lets call sites drop
|
|
2675
|
+
* ad-hoc `*__centering` classes. Forwards its ref and spreads remaining props
|
|
2676
|
+
* onto the underlying `Stack`.
|
|
2677
|
+
*
|
|
2678
|
+
* @param props - Stack props; `align`/`justify` default to `"center"`.
|
|
2679
|
+
* @param ref - Forwarded to the underlying element.
|
|
2680
|
+
* @return The centered layout element.
|
|
2681
|
+
*/
|
|
2682
|
+
const Center = (0, react$1.forwardRef)(({ align = "center", justify = "center", className, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stack, {
|
|
2683
|
+
ref,
|
|
2684
|
+
align,
|
|
2685
|
+
justify,
|
|
2686
|
+
className: (0, clsx.default)(center_module_default.center, className),
|
|
2687
|
+
...props
|
|
2688
|
+
}));
|
|
2689
|
+
Center.displayName = "Center";
|
|
2690
|
+
//#endregion
|
|
2642
2691
|
//#region src/charts/private/svg-empty-state/svg-empty-state.module.scss
|
|
2643
2692
|
var svg_empty_state_module_default = { "svg-empty-state": "a8ccharts-udGPVq-svg-empty-state" };
|
|
2644
2693
|
//#endregion
|
|
@@ -2663,9 +2712,7 @@ const SvgEmptyState = ({ x, y, width, height, children }) => {
|
|
|
2663
2712
|
y: y - height / 2,
|
|
2664
2713
|
width,
|
|
2665
2714
|
height,
|
|
2666
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2667
|
-
align: "center",
|
|
2668
|
-
justify: "center",
|
|
2715
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
2669
2716
|
className: svg_empty_state_module_default["svg-empty-state"],
|
|
2670
2717
|
children
|
|
2671
2718
|
})
|
|
@@ -4345,6 +4392,10 @@ const TruncatedXTickComponent = createTruncatedTickComponent("x");
|
|
|
4345
4392
|
const TruncatedYTickComponent = createTruncatedTickComponent("y");
|
|
4346
4393
|
//#endregion
|
|
4347
4394
|
//#region src/charts/bar-chart/private/use-bar-chart-options.ts
|
|
4395
|
+
/** Outer padding of the category band scale (space at the chart edges). */
|
|
4396
|
+
const BASE_BAND_PADDING = .2;
|
|
4397
|
+
/** Inner padding of the category band scale (the base gap between ticks). */
|
|
4398
|
+
const BASE_BAND_PADDING_INNER = .1;
|
|
4348
4399
|
const formatDateTick = (timestamp) => {
|
|
4349
4400
|
return new Date(timestamp).toLocaleDateString(void 0, {
|
|
4350
4401
|
month: "short",
|
|
@@ -4372,8 +4423,8 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4372
4423
|
const defaultOptions = (0, react$1.useMemo)(() => {
|
|
4373
4424
|
const bandScale = {
|
|
4374
4425
|
type: "band",
|
|
4375
|
-
padding:
|
|
4376
|
-
paddingInner:
|
|
4426
|
+
padding: BASE_BAND_PADDING,
|
|
4427
|
+
paddingInner: BASE_BAND_PADDING_INNER
|
|
4377
4428
|
};
|
|
4378
4429
|
const linearScale = {
|
|
4379
4430
|
type: "linear",
|
|
@@ -4412,13 +4463,29 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4412
4463
|
}, [data]);
|
|
4413
4464
|
return (0, react$1.useMemo)(() => {
|
|
4414
4465
|
const { xTickFormat, yTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
|
|
4466
|
+
let valueScaleDomainOverride = {};
|
|
4467
|
+
if (data.some((s) => s.options?.type === "comparison")) {
|
|
4468
|
+
if (!(!horizontal ? options.yScale?.domain : options.xScale?.domain)) {
|
|
4469
|
+
const allValues = [];
|
|
4470
|
+
data.forEach((series) => {
|
|
4471
|
+
series.data.forEach((d) => {
|
|
4472
|
+
const enhanced = d;
|
|
4473
|
+
const v = enhanced.visualValue !== void 0 ? enhanced.visualValue : d.value;
|
|
4474
|
+
if (typeof v === "number" && Number.isFinite(v)) allValues.push(v);
|
|
4475
|
+
});
|
|
4476
|
+
});
|
|
4477
|
+
if (allValues.length > 0) valueScaleDomainOverride = { domain: [Math.min(...allValues), Math.max(...allValues)] };
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4415
4480
|
const xScale = {
|
|
4416
4481
|
...baseXScale,
|
|
4417
|
-
...options.xScale || {}
|
|
4482
|
+
...options.xScale || {},
|
|
4483
|
+
...horizontal ? valueScaleDomainOverride : {}
|
|
4418
4484
|
};
|
|
4419
4485
|
const yScale = {
|
|
4420
4486
|
...baseYScale,
|
|
4421
|
-
...options.yScale || {}
|
|
4487
|
+
...options.yScale || {},
|
|
4488
|
+
...!horizontal ? valueScaleDomainOverride : {}
|
|
4422
4489
|
};
|
|
4423
4490
|
const providedToolTipLabelFormatter = horizontal ? options.axis?.y?.tickFormat : options.axis?.x?.tickFormat;
|
|
4424
4491
|
const { labelOverflow: xLabelOverflow, ...xAxisOptions } = options.axis?.x || {};
|
|
@@ -4453,10 +4520,143 @@ function useBarChartOptions(data, horizontal, options = {}) {
|
|
|
4453
4520
|
}, [
|
|
4454
4521
|
defaultOptions,
|
|
4455
4522
|
options,
|
|
4456
|
-
horizontal
|
|
4523
|
+
horizontal,
|
|
4524
|
+
data
|
|
4457
4525
|
]);
|
|
4458
4526
|
}
|
|
4459
4527
|
//#endregion
|
|
4528
|
+
//#region src/charts/bar-chart/private/comparison-bars-geometry.ts
|
|
4529
|
+
/**
|
|
4530
|
+
* Output position of a value scale's baseline: zero if in-domain, else the
|
|
4531
|
+
* nearest range edge. Mirrors visx's getScaleBaseline so comparison shadows
|
|
4532
|
+
* sit on the same baseline as primary bars.
|
|
4533
|
+
*
|
|
4534
|
+
* @param {ValueScale} scale - The continuous value scale.
|
|
4535
|
+
* @return {number} The baseline output position in pixels.
|
|
4536
|
+
*/
|
|
4537
|
+
function getValueScaleBaseline(scale) {
|
|
4538
|
+
const [a, b] = scale.range().map((r) => Number(r) || 0);
|
|
4539
|
+
const isDescending = b < a;
|
|
4540
|
+
const maybeZero = scale(0);
|
|
4541
|
+
const [minOutput, maxOutput] = isDescending ? [b, a] : [a, b];
|
|
4542
|
+
if (isDescending) return Number.isFinite(maybeZero) ? Math.min(Math.max(minOutput, maybeZero), maxOutput) : maxOutput;
|
|
4543
|
+
return Number.isFinite(maybeZero) ? Math.min(Math.max(maybeZero, minOutput), maxOutput) : minOutput;
|
|
4544
|
+
}
|
|
4545
|
+
/**
|
|
4546
|
+
* Compute the rect for a comparison "shadow" bar, centered on the paired
|
|
4547
|
+
* primary bar slot and scaled by `widthFactor`.
|
|
4548
|
+
*
|
|
4549
|
+
* @param {object} params - Geometry inputs.
|
|
4550
|
+
* @param {boolean} params.horizontal - True for a horizontal bar chart, false for vertical.
|
|
4551
|
+
* @param {number} params.bandPosition - bandScale(category): start px of the category band.
|
|
4552
|
+
* @param {number} params.slotOffset - groupScale(primaryKey): offset of the primary slot within the band.
|
|
4553
|
+
* @param {number} params.slotThickness - groupScale.bandwidth(): primary bar thickness in px.
|
|
4554
|
+
* @param {number} params.valuePosition - valueScale(value): output px for the bar's data value.
|
|
4555
|
+
* @param {number} params.baseline - getValueScaleBaseline(valueScale): zero-line output px.
|
|
4556
|
+
* @param {number} params.widthFactor - Shadow thickness multiplier, e.g. 1.5 for 150% width.
|
|
4557
|
+
* @return {ComparisonRect} The {x, y, width, height} of the shadow rect.
|
|
4558
|
+
*/
|
|
4559
|
+
function computeComparisonRect(params) {
|
|
4560
|
+
const { horizontal, bandPosition, slotOffset, slotThickness, valuePosition, baseline, widthFactor } = params;
|
|
4561
|
+
const slotStart = bandPosition + slotOffset;
|
|
4562
|
+
const shadowThickness = slotThickness * widthFactor;
|
|
4563
|
+
const shadowStart = slotStart + slotThickness / 2 - shadowThickness / 2;
|
|
4564
|
+
const valueStart = Math.min(valuePosition, baseline);
|
|
4565
|
+
const valueLength = Math.abs(baseline - valuePosition);
|
|
4566
|
+
if (horizontal) return {
|
|
4567
|
+
x: valueStart,
|
|
4568
|
+
y: shadowStart,
|
|
4569
|
+
width: valueLength,
|
|
4570
|
+
height: shadowThickness
|
|
4571
|
+
};
|
|
4572
|
+
return {
|
|
4573
|
+
x: shadowStart,
|
|
4574
|
+
y: valueStart,
|
|
4575
|
+
width: shadowThickness,
|
|
4576
|
+
height: valueLength
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Fraction of each per-series step left as a gap between bars within a single tick.
|
|
4581
|
+
* Larger = more space between adjacent series; the shadow spans `1 - COMPARISON_INNER_GAP` of the step.
|
|
4582
|
+
*/
|
|
4583
|
+
const COMPARISON_INNER_GAP = .1;
|
|
4584
|
+
/**
|
|
4585
|
+
* Upper clamp on the computed group padding, so bars can never collapse to zero width
|
|
4586
|
+
* even at very large `widthFactor` values.
|
|
4587
|
+
*/
|
|
4588
|
+
const MAX_GROUP_PADDING = .9;
|
|
4589
|
+
/**
|
|
4590
|
+
* Factor applied to the category band's `paddingInner` in comparison mode to tighten the
|
|
4591
|
+
* gap between ticks. `0.75` = a 25% reduction of the tick-gap padding.
|
|
4592
|
+
*/
|
|
4593
|
+
const COMPARISON_TICK_GAP_FACTOR = .75;
|
|
4594
|
+
//#endregion
|
|
4595
|
+
//#region src/charts/bar-chart/private/comparison-bars.tsx
|
|
4596
|
+
const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizontal, xAccessor, yAccessor, getElementStyles, resolveFill }) => {
|
|
4597
|
+
const context = (0, react$1.useContext)(_visx_xychart.DataContext);
|
|
4598
|
+
const xScale = context?.xScale;
|
|
4599
|
+
const yScale = context?.yScale;
|
|
4600
|
+
if (!xScale || !yScale || primaryKeys.length === 0) return null;
|
|
4601
|
+
const bandScale = horizontal ? yScale : xScale;
|
|
4602
|
+
const valueScale = horizontal ? xScale : yScale;
|
|
4603
|
+
const bandwidth = bandScale.bandwidth ? bandScale.bandwidth() : 0;
|
|
4604
|
+
if (!bandwidth) return null;
|
|
4605
|
+
const groupScale = (0, _visx_scale.scaleBand)({
|
|
4606
|
+
domain: primaryKeys,
|
|
4607
|
+
range: [0, bandwidth],
|
|
4608
|
+
padding: groupPadding
|
|
4609
|
+
});
|
|
4610
|
+
const slotThickness = groupScale.bandwidth();
|
|
4611
|
+
const baseline = getValueScaleBaseline(valueScale);
|
|
4612
|
+
const bandAccessor = horizontal ? yAccessor : xAccessor;
|
|
4613
|
+
const valueAccessor = horizontal ? xAccessor : yAccessor;
|
|
4614
|
+
const rects = [];
|
|
4615
|
+
comparisonEntries.forEach((entry) => {
|
|
4616
|
+
const { series, index, primaryKey } = entry;
|
|
4617
|
+
const slotOffset = groupScale(primaryKey);
|
|
4618
|
+
if (slotOffset == null || !Number.isFinite(slotOffset)) return;
|
|
4619
|
+
const { barStyles } = getElementStyles({
|
|
4620
|
+
data: series,
|
|
4621
|
+
index
|
|
4622
|
+
});
|
|
4623
|
+
const opacity = barStyles?.opacity ?? .5;
|
|
4624
|
+
const widthFactor = barStyles?.widthFactor ?? 1.5;
|
|
4625
|
+
const fill = resolveFill(entry);
|
|
4626
|
+
series.data.forEach((datum, i) => {
|
|
4627
|
+
const bandPosition = Number(bandScale(bandAccessor(datum)));
|
|
4628
|
+
const valuePosition = Number(valueScale(Number(valueAccessor(datum))));
|
|
4629
|
+
if (!Number.isFinite(bandPosition) || !Number.isFinite(valuePosition)) {
|
|
4630
|
+
if (process.env.NODE_ENV !== "production" && !Number.isFinite(bandPosition)) console.warn(`[Charts] ComparisonBars: datum key "${String(bandAccessor(datum))}" did not match any primary category. Shadow will not be rendered. Ensure comparison series data uses the same label/date keys as the primary series.`);
|
|
4631
|
+
return;
|
|
4632
|
+
}
|
|
4633
|
+
const rect = computeComparisonRect({
|
|
4634
|
+
horizontal,
|
|
4635
|
+
bandPosition,
|
|
4636
|
+
slotOffset,
|
|
4637
|
+
slotThickness,
|
|
4638
|
+
valuePosition,
|
|
4639
|
+
baseline,
|
|
4640
|
+
widthFactor
|
|
4641
|
+
});
|
|
4642
|
+
rects.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
4643
|
+
x: rect.x,
|
|
4644
|
+
y: rect.y,
|
|
4645
|
+
width: rect.width,
|
|
4646
|
+
height: rect.height,
|
|
4647
|
+
fill,
|
|
4648
|
+
opacity
|
|
4649
|
+
}, `${index}-${i}`));
|
|
4650
|
+
});
|
|
4651
|
+
});
|
|
4652
|
+
if (rects.length === 0) return null;
|
|
4653
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
4654
|
+
className: "bar-chart__comparison-bars",
|
|
4655
|
+
pointerEvents: "none",
|
|
4656
|
+
children: rects
|
|
4657
|
+
});
|
|
4658
|
+
};
|
|
4659
|
+
//#endregion
|
|
4460
4660
|
//#region src/charts/bar-chart/bar-chart.tsx
|
|
4461
4661
|
const validateData$2 = (data) => {
|
|
4462
4662
|
if (!data?.length) return "No data available";
|
|
@@ -4485,13 +4685,14 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4485
4685
|
}, [height]);
|
|
4486
4686
|
const [selectedIndex, setSelectedIndex] = (0, react$1.useState)(void 0);
|
|
4487
4687
|
const [isNavigating, setIsNavigating] = (0, react$1.useState)(false);
|
|
4688
|
+
const primarySeriesForNav = dataWithVisibleZeros.filter((s) => s.options?.type !== "comparison");
|
|
4488
4689
|
const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
|
|
4489
4690
|
selectedIndex,
|
|
4490
4691
|
setSelectedIndex,
|
|
4491
4692
|
isNavigating,
|
|
4492
4693
|
setIsNavigating,
|
|
4493
4694
|
chartRef,
|
|
4494
|
-
totalPoints: Math.max(0, ...
|
|
4695
|
+
totalPoints: Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length
|
|
4495
4696
|
});
|
|
4496
4697
|
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
4497
4698
|
const seriesWithVisibility = (0, react$1.useMemo)(() => {
|
|
@@ -4514,6 +4715,69 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4514
4715
|
const allSeriesHidden = (0, react$1.useMemo)(() => {
|
|
4515
4716
|
return seriesWithVisibility.every(({ isVisible }) => !isVisible);
|
|
4516
4717
|
}, [seriesWithVisibility]);
|
|
4718
|
+
const primaryEntries = (0, react$1.useMemo)(() => seriesWithVisibility.filter(({ isVisible, series }) => isVisible && series.options?.type !== "comparison"), [seriesWithVisibility]);
|
|
4719
|
+
const primaryKeys = (0, react$1.useMemo)(() => primaryEntries.map(({ series }) => series.label), [primaryEntries]);
|
|
4720
|
+
const comparisonEntries = (0, react$1.useMemo)(() => {
|
|
4721
|
+
const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
|
|
4722
|
+
label: series.label,
|
|
4723
|
+
index
|
|
4724
|
+
}]));
|
|
4725
|
+
const entries = [];
|
|
4726
|
+
seriesWithVisibility.forEach(({ series, index, isVisible }) => {
|
|
4727
|
+
if (!isVisible || series.options?.type !== "comparison") return;
|
|
4728
|
+
const primary = primaryByGroup.get(series.group) ?? (primaryEntries.length === 1 ? {
|
|
4729
|
+
label: primaryEntries[0].series.label,
|
|
4730
|
+
index: primaryEntries[0].index
|
|
4731
|
+
} : void 0);
|
|
4732
|
+
if (!primary || !primaryKeys.includes(primary.label)) return;
|
|
4733
|
+
entries.push({
|
|
4734
|
+
series,
|
|
4735
|
+
index,
|
|
4736
|
+
primaryKey: primary.label,
|
|
4737
|
+
primaryIndex: primary.index
|
|
4738
|
+
});
|
|
4739
|
+
});
|
|
4740
|
+
return entries;
|
|
4741
|
+
}, [
|
|
4742
|
+
seriesWithVisibility,
|
|
4743
|
+
primaryEntries,
|
|
4744
|
+
primaryKeys
|
|
4745
|
+
]);
|
|
4746
|
+
const comparisonWidthFactor = (0, react$1.useMemo)(() => {
|
|
4747
|
+
if (comparisonEntries.length === 0) return void 0;
|
|
4748
|
+
return getElementStyles({
|
|
4749
|
+
data: comparisonEntries[0].series,
|
|
4750
|
+
index: comparisonEntries[0].index
|
|
4751
|
+
}).barStyles?.widthFactor ?? 1.5;
|
|
4752
|
+
}, [comparisonEntries, getElementStyles]);
|
|
4753
|
+
const groupPadding = (0, react$1.useMemo)(() => {
|
|
4754
|
+
const basePadding = chartOptions.barGroup.padding;
|
|
4755
|
+
if (!comparisonWidthFactor || comparisonWidthFactor <= 1) return basePadding;
|
|
4756
|
+
const p = 1 - (1 - COMPARISON_INNER_GAP) / comparisonWidthFactor;
|
|
4757
|
+
return Math.min(Math.max(p, basePadding), MAX_GROUP_PADDING);
|
|
4758
|
+
}, [chartOptions.barGroup.padding, comparisonWidthFactor]);
|
|
4759
|
+
const { xScale, yScale } = (0, react$1.useMemo)(() => {
|
|
4760
|
+
if (comparisonEntries.length === 0) return {
|
|
4761
|
+
xScale: chartOptions.xScale,
|
|
4762
|
+
yScale: chartOptions.yScale
|
|
4763
|
+
};
|
|
4764
|
+
const tighten = (scale) => ({
|
|
4765
|
+
...scale,
|
|
4766
|
+
paddingInner: (scale.paddingInner ?? .1) * COMPARISON_TICK_GAP_FACTOR
|
|
4767
|
+
});
|
|
4768
|
+
return horizontal ? {
|
|
4769
|
+
xScale: chartOptions.xScale,
|
|
4770
|
+
yScale: tighten(chartOptions.yScale)
|
|
4771
|
+
} : {
|
|
4772
|
+
xScale: tighten(chartOptions.xScale),
|
|
4773
|
+
yScale: chartOptions.yScale
|
|
4774
|
+
};
|
|
4775
|
+
}, [
|
|
4776
|
+
comparisonEntries.length,
|
|
4777
|
+
chartOptions.xScale,
|
|
4778
|
+
chartOptions.yScale,
|
|
4779
|
+
horizontal
|
|
4780
|
+
]);
|
|
4517
4781
|
const getBarBackground = (0, react$1.useCallback)((index) => () => withPatterns ? `url(#${getPatternId(chartId, index)})` : getElementStyles({
|
|
4518
4782
|
data: dataSorted[index],
|
|
4519
4783
|
index
|
|
@@ -4523,26 +4787,70 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4523
4787
|
dataSorted,
|
|
4524
4788
|
chartId
|
|
4525
4789
|
]);
|
|
4790
|
+
const resolveComparisonFill = (0, react$1.useCallback)((entry) => withPatterns ? `url(#${getPatternId(chartId, entry.primaryIndex)})` : getElementStyles({
|
|
4791
|
+
data: entry.series,
|
|
4792
|
+
index: entry.index
|
|
4793
|
+
}).color, [
|
|
4794
|
+
withPatterns,
|
|
4795
|
+
chartId,
|
|
4796
|
+
getElementStyles
|
|
4797
|
+
]);
|
|
4526
4798
|
const renderDefaultTooltip = (0, react$1.useCallback)(({ tooltipData }) => {
|
|
4527
4799
|
const nearestDatum = tooltipData?.nearestDatum?.datum;
|
|
4528
4800
|
if (!nearestDatum) return null;
|
|
4801
|
+
const primaryKey = tooltipData?.nearestDatum?.key;
|
|
4802
|
+
const categoryLabel = chartOptions.tooltip.labelFormatter(nearestDatum.label || (nearestDatum.date ? nearestDatum.date.getTime() : 0), 0, []);
|
|
4803
|
+
const comparisonEntry = comparisonEntries.find((entry) => entry.primaryKey === primaryKey);
|
|
4804
|
+
const comparisonDatum = comparisonEntry?.series.data.find((point) => {
|
|
4805
|
+
const p = point;
|
|
4806
|
+
return nearestDatum.label != null ? p.label === nearestDatum.label : !!nearestDatum.date && !!p.date && p.date.getTime() === nearestDatum.date.getTime();
|
|
4807
|
+
});
|
|
4808
|
+
if (comparisonEntry && comparisonDatum && comparisonDatum.value != null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4809
|
+
className: bar_chart_module_default["bar-chart__tooltip"],
|
|
4810
|
+
children: [
|
|
4811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4812
|
+
className: bar_chart_module_default["bar-chart__tooltip-header"],
|
|
4813
|
+
children: categoryLabel
|
|
4814
|
+
}),
|
|
4815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4816
|
+
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
4817
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4818
|
+
className: bar_chart_module_default["bar-chart__tooltip-label"],
|
|
4819
|
+
children: [primaryKey, ":"]
|
|
4820
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4821
|
+
className: bar_chart_module_default["bar-chart__tooltip-value"],
|
|
4822
|
+
children: (0, _automattic_number_formatters.formatNumber)(nearestDatum.value)
|
|
4823
|
+
})]
|
|
4824
|
+
}),
|
|
4825
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4826
|
+
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
4827
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4828
|
+
className: bar_chart_module_default["bar-chart__tooltip-label"],
|
|
4829
|
+
children: [comparisonEntry.series.label, ":"]
|
|
4830
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4831
|
+
className: bar_chart_module_default["bar-chart__tooltip-value"],
|
|
4832
|
+
children: (0, _automattic_number_formatters.formatNumber)(comparisonDatum.value)
|
|
4833
|
+
})]
|
|
4834
|
+
})
|
|
4835
|
+
]
|
|
4836
|
+
});
|
|
4529
4837
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4530
4838
|
className: bar_chart_module_default["bar-chart__tooltip"],
|
|
4531
4839
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4532
4840
|
className: bar_chart_module_default["bar-chart__tooltip-header"],
|
|
4533
|
-
children:
|
|
4841
|
+
children: primaryKey
|
|
4534
4842
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4535
4843
|
className: bar_chart_module_default["bar-chart__tooltip-row"],
|
|
4536
4844
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4537
4845
|
className: bar_chart_module_default["bar-chart__tooltip-label"],
|
|
4538
|
-
children: [
|
|
4846
|
+
children: [categoryLabel, ":"]
|
|
4539
4847
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4540
4848
|
className: bar_chart_module_default["bar-chart__tooltip-value"],
|
|
4541
4849
|
children: (0, _automattic_number_formatters.formatNumber)(nearestDatum.value)
|
|
4542
4850
|
})]
|
|
4543
4851
|
})]
|
|
4544
4852
|
});
|
|
4545
|
-
}, [chartOptions.tooltip]);
|
|
4853
|
+
}, [chartOptions.tooltip, comparisonEntries]);
|
|
4546
4854
|
const renderPattern = (0, react$1.useCallback)((index, color) => {
|
|
4547
4855
|
const patternType = index % 4;
|
|
4548
4856
|
const id = getPatternId(chartId, index);
|
|
@@ -4579,8 +4887,10 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4579
4887
|
}
|
|
4580
4888
|
}, [chartId]);
|
|
4581
4889
|
const createPatternBorderStyle = (0, react$1.useCallback)((index, color) => {
|
|
4890
|
+
const patternId = getPatternId(chartId, index);
|
|
4582
4891
|
return `
|
|
4583
|
-
.visx-bar[fill="url(#${
|
|
4892
|
+
.visx-bar[fill="url(#${patternId})"],
|
|
4893
|
+
.bar-chart__comparison-bars rect[fill="url(#${patternId})"] {
|
|
4584
4894
|
stroke: ${color};
|
|
4585
4895
|
stroke-width: 1;
|
|
4586
4896
|
}
|
|
@@ -4588,11 +4898,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4588
4898
|
}, [chartId]);
|
|
4589
4899
|
const createKeyboardHighlightStyle = (0, react$1.useCallback)(() => {
|
|
4590
4900
|
if (selectedIndex === void 0) return "";
|
|
4591
|
-
const
|
|
4592
|
-
const
|
|
4593
|
-
const
|
|
4594
|
-
|
|
4595
|
-
if (dataPointIndex >=
|
|
4901
|
+
const primaryCount = primaryEntries.length;
|
|
4902
|
+
const maxDataPoints = Math.max(...primaryEntries.map((e) => e.series.data.length));
|
|
4903
|
+
const dataPointIndex = Math.floor(selectedIndex / primaryCount);
|
|
4904
|
+
const seriesIndex = selectedIndex % primaryCount;
|
|
4905
|
+
if (dataPointIndex >= maxDataPoints || seriesIndex >= primaryCount) return "";
|
|
4906
|
+
const seriesData = primaryEntries[seriesIndex]?.series;
|
|
4907
|
+
if (!seriesData || dataPointIndex >= seriesData.data.length) return "";
|
|
4596
4908
|
return `
|
|
4597
4909
|
.bar-chart[data-chart-id="bar-chart-${chartId}"] .visx-bar-group .visx-bar:nth-child(${seriesIndex * maxDataPoints + dataPointIndex + 1}) {
|
|
4598
4910
|
stroke: #005fcc;
|
|
@@ -4601,7 +4913,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4601
4913
|
`;
|
|
4602
4914
|
}, [
|
|
4603
4915
|
selectedIndex,
|
|
4604
|
-
|
|
4916
|
+
primaryEntries,
|
|
4605
4917
|
chartId
|
|
4606
4918
|
]);
|
|
4607
4919
|
const error = validateData$2(dataSorted);
|
|
@@ -4674,8 +4986,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4674
4986
|
...defaultMargin,
|
|
4675
4987
|
...margin
|
|
4676
4988
|
},
|
|
4677
|
-
xScale
|
|
4678
|
-
yScale
|
|
4989
|
+
xScale,
|
|
4990
|
+
yScale,
|
|
4679
4991
|
horizontal,
|
|
4680
4992
|
pointerEventsDataKey: "nearest",
|
|
4681
4993
|
children: [
|
|
@@ -4699,18 +5011,25 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
|
|
|
4699
5011
|
height: chartHeight,
|
|
4700
5012
|
children: (0, _wordpress_i18n.__)("All series are hidden. Click legend items to show data.", "jetpack-charts")
|
|
4701
5013
|
}) : null,
|
|
5014
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComparisonBars, {
|
|
5015
|
+
comparisonEntries,
|
|
5016
|
+
primaryKeys,
|
|
5017
|
+
groupPadding,
|
|
5018
|
+
horizontal,
|
|
5019
|
+
xAccessor: chartOptions.accessors.xAccessor,
|
|
5020
|
+
yAccessor: chartOptions.accessors.yAccessor,
|
|
5021
|
+
getElementStyles,
|
|
5022
|
+
resolveFill: resolveComparisonFill
|
|
5023
|
+
}),
|
|
4702
5024
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.BarGroup, {
|
|
4703
|
-
padding:
|
|
4704
|
-
children:
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
colorAccessor: getBarBackground(index)
|
|
4712
|
-
}, seriesData?.label);
|
|
4713
|
-
})
|
|
5025
|
+
padding: groupPadding,
|
|
5026
|
+
children: primaryEntries.map(({ series: seriesData, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.BarSeries, {
|
|
5027
|
+
dataKey: seriesData?.label,
|
|
5028
|
+
data: seriesData.data,
|
|
5029
|
+
yAccessor: chartOptions.accessors.yAccessor,
|
|
5030
|
+
xAccessor: chartOptions.accessors.xAccessor,
|
|
5031
|
+
colorAccessor: getBarBackground(index)
|
|
5032
|
+
}, seriesData?.label))
|
|
4714
5033
|
}),
|
|
4715
5034
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
|
|
4716
5035
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y }),
|
|
@@ -5323,9 +5642,7 @@ const DEFAULT_BACKGROUND_COLOR = "#ffffff";
|
|
|
5323
5642
|
*/
|
|
5324
5643
|
const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", renderPlaceholder }) => {
|
|
5325
5644
|
const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
|
|
5326
|
-
const loadingPlaceholder = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5327
|
-
align: "center",
|
|
5328
|
-
justify: "center",
|
|
5645
|
+
const loadingPlaceholder = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
5329
5646
|
className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
|
|
5330
5647
|
style: {
|
|
5331
5648
|
width,
|
|
@@ -5383,9 +5700,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
|
|
|
5383
5700
|
defaultFillColorHex,
|
|
5384
5701
|
sanitizedData.hasHtmlTooltips
|
|
5385
5702
|
]);
|
|
5386
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5387
|
-
align: "center",
|
|
5388
|
-
justify: "center",
|
|
5703
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
5389
5704
|
className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
|
|
5390
5705
|
style: {
|
|
5391
5706
|
width,
|
|
@@ -8432,7 +8747,6 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
|
|
|
8432
8747
|
//#region src/charts/pie-chart/pie-chart.module.scss
|
|
8433
8748
|
var pie_chart_module_default = {
|
|
8434
8749
|
"pie-chart": "a8ccharts-gnszbG-pie-chart",
|
|
8435
|
-
"pie-chart__centering": "a8ccharts-gnszbG-pie-chart__centering",
|
|
8436
8750
|
"pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
|
|
8437
8751
|
};
|
|
8438
8752
|
//#endregion
|
|
@@ -8597,11 +8911,8 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
|
|
|
8597
8911
|
const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
|
|
8598
8912
|
const maxCornerRadius = (outerRadius - innerRadius) / 2;
|
|
8599
8913
|
const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
|
|
8600
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
8914
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
8601
8915
|
ref: containerRef,
|
|
8602
|
-
align: "center",
|
|
8603
|
-
justify: "center",
|
|
8604
|
-
className: pie_chart_module_default["pie-chart__centering"],
|
|
8605
8916
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
8606
8917
|
viewBox: `0 0 ${width} ${height}`,
|
|
8607
8918
|
preserveAspectRatio: "xMidYMid meet",
|
|
@@ -8714,7 +9025,6 @@ var pie_semi_circle_chart_module_default = {
|
|
|
8714
9025
|
"label": "a8ccharts-YtTOxW-label",
|
|
8715
9026
|
"note": "a8ccharts-YtTOxW-note",
|
|
8716
9027
|
"pie-semi-circle-chart": "a8ccharts-YtTOxW-pie-semi-circle-chart",
|
|
8717
|
-
"pie-semi-circle-chart__centering": "a8ccharts-YtTOxW-pie-semi-circle-chart__centering",
|
|
8718
9028
|
"pie-semi-circle-chart--responsive": "a8ccharts-YtTOxW-pie-semi-circle-chart--responsive"
|
|
8719
9029
|
};
|
|
8720
9030
|
//#endregion
|
|
@@ -8894,11 +9204,8 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
|
|
|
8894
9204
|
const height = width / 2;
|
|
8895
9205
|
const radius = height;
|
|
8896
9206
|
const innerRadius = radius * (1 - thickness);
|
|
8897
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
9207
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
|
|
8898
9208
|
ref: containerRef,
|
|
8899
|
-
align: "center",
|
|
8900
|
-
justify: "center",
|
|
8901
|
-
className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__centering"],
|
|
8902
9209
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
8903
9210
|
width,
|
|
8904
9211
|
height,
|