@mui/x-charts 9.10.0 → 9.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/BarChart/seriesConfig/bar/keyboardFocusHandler.js +3 -1
  2. package/BarChart/seriesConfig/bar/keyboardFocusHandler.mjs +3 -1
  3. package/CHANGELOG.md +106 -0
  4. package/ChartsAxisHighlight/getSampledBandHighlight.d.mts +1 -1
  5. package/ChartsAxisHighlight/getSampledBandHighlight.d.ts +1 -1
  6. package/ChartsAxisHighlight/getSampledBandHighlight.js +15 -3
  7. package/ChartsAxisHighlight/getSampledBandHighlight.mjs +15 -3
  8. package/LineChart/seriesConfig/keyboardFocusHandler.js +3 -1
  9. package/LineChart/seriesConfig/keyboardFocusHandler.mjs +3 -1
  10. package/RadarChart/RadarAxis/RadarAxis.utils.d.mts +1 -1
  11. package/RadarChart/RadarAxis/RadarAxis.utils.d.ts +1 -1
  12. package/RadarChart/seriesConfig/keyboardFocusHandler.js +3 -1
  13. package/RadarChart/seriesConfig/keyboardFocusHandler.mjs +3 -1
  14. package/hooks/animation/useAnimatePieArc.js +2 -1
  15. package/hooks/animation/useAnimatePieArc.mjs +2 -1
  16. package/index.js +1 -1
  17. package/index.mjs +1 -1
  18. package/internals/commonNextFocusItem.d.mts +2 -2
  19. package/internals/commonNextFocusItem.d.ts +2 -2
  20. package/internals/commonNextFocusItem.js +4 -4
  21. package/internals/commonNextFocusItem.mjs +4 -4
  22. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.js +1 -0
  23. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.mjs +1 -0
  24. package/internals/createGetBarDimensions.js +10 -5
  25. package/internals/createGetBarDimensions.mjs +10 -5
  26. package/internals/getSampledBucketRegion.d.mts +12 -0
  27. package/internals/getSampledBucketRegion.d.ts +12 -0
  28. package/internals/getSampledBucketRegion.js +23 -0
  29. package/internals/getSampledBucketRegion.mjs +17 -0
  30. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/findVisibleDataIndex.d.mts +3 -0
  31. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/findVisibleDataIndex.d.ts +3 -0
  32. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/findVisibleDataIndex.js +3 -0
  33. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/findVisibleDataIndex.mjs +3 -0
  34. package/internals/snapEndAngleToFullCircle.d.mts +20 -0
  35. package/internals/snapEndAngleToFullCircle.d.ts +20 -0
  36. package/internals/snapEndAngleToFullCircle.js +47 -0
  37. package/internals/snapEndAngleToFullCircle.mjs +41 -0
  38. package/package.json +4 -4
@@ -6,5 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _createCommonKeyboardFocusHandler = require("../../../internals/createCommonKeyboardFocusHandler");
8
8
  var _composition = require("../../../models/seriesType/composition");
9
- const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(_composition.composableCartesianSeriesTypes);
9
+ const allowCycles = false;
10
+ const useCurrentSeriesMaxLength = false;
11
+ const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(_composition.composableCartesianSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
10
12
  var _default = exports.default = keyboardFocusHandler;
@@ -1,4 +1,6 @@
1
1
  import { createCommonKeyboardFocusHandler } from "../../../internals/createCommonKeyboardFocusHandler.mjs";
2
2
  import { composableCartesianSeriesTypes } from "../../../models/seriesType/composition.mjs";
3
- const keyboardFocusHandler = createCommonKeyboardFocusHandler(composableCartesianSeriesTypes);
3
+ const allowCycles = false;
4
+ const useCurrentSeriesMaxLength = false;
5
+ const keyboardFocusHandler = createCommonKeyboardFocusHandler(composableCartesianSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
4
6
  export default keyboardFocusHandler;
package/CHANGELOG.md CHANGED
@@ -1,5 +1,111 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.10.1
4
+
5
+ _Jul 23, 2026_
6
+
7
+ We'd like to extend a big thank you to the 11 contributors who made this release possible. Here are some highlights ✨:
8
+
9
+ - 🦮 Improve accessibility of the Charts and Pickers components.
10
+ - 🐞 Bugfixes
11
+ - 📚 Documentation improvements
12
+
13
+ Special thanks go out to these community members for their valuable contributions:
14
+ @kevincorizi-sbt, @mustafajw07, @SamanPandey-in
15
+
16
+ The following team members contributed to this release:
17
+ @brijeshb42, @hasdfa, @JCQuintas, @LukasTy, @michelengelen, @noraleonte, @rita-codes, @silviuaavram
18
+
19
+ ### Data Grid
20
+
21
+ #### `@mui/x-data-grid@9.10.1`
22
+
23
+ - [data grid] Fix `getColumn` return type not reflecting that it can return undefined (#23165) @JCQuintas
24
+
25
+ #### `@mui/x-data-grid-pro@9.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
26
+
27
+ Same changes as in `@mui/x-data-grid@9.10.1`.
28
+
29
+ #### `@mui/x-data-grid-premium@9.10.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
30
+
31
+ Same changes as in `@mui/x-data-grid-pro@9.10.1`.
32
+
33
+ ### Date and Time Pickers
34
+
35
+ #### `@mui/x-date-pickers@9.10.1`
36
+
37
+ - [pickers] Fix format placeholder brightness with a start adornment (#23189) @LukasTy
38
+
39
+ #### `@mui/x-date-pickers-pro@9.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
40
+
41
+ Same changes as in `@mui/x-date-pickers@9.10.1`, plus:
42
+
43
+ - [DateRangePicker] Fix broken active range position underline in single input range fields (#23166) @LukasTy
44
+
45
+ ### Charts
46
+
47
+ #### `@mui/x-charts@9.10.1`
48
+
49
+ - [charts] Fix pie chart disappearing when an arc spans almost the full circle (#23145) @JCQuintas
50
+ - [charts] Set `aria-hidden` on accessibility proxy divs at initialization (#23186) @kevincorizi-sbt
51
+ - [charts] Fix keyboard navigation on series with different lengths (#23182) @JCQuintas
52
+ - [charts] Fix out of bounds keyboard navigation (#23180) @silviuaavram
53
+
54
+ #### `@mui/x-charts-pro@9.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
55
+
56
+ Same changes as in `@mui/x-charts@9.10.1`, plus:
57
+
58
+ - [charts-pro] Fix highlight shifting on sampled bar charts (#23190) @noraleonte
59
+ - [charts-pro] Sampled band highlight falls back to single-band width for Date/object values (#23024) @SamanPandey-in
60
+
61
+ #### `@mui/x-charts-premium@9.10.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
62
+
63
+ Same changes as in `@mui/x-charts-pro@9.10.1`.
64
+
65
+ ### Tree View
66
+
67
+ #### `@mui/x-tree-view@9.10.1`
68
+
69
+ - [TreeView] Fix selection propagation to exclude disabled items (#23012) @michelengelen
70
+
71
+ #### `@mui/x-tree-view-pro@9.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
72
+
73
+ Same changes as in `@mui/x-tree-view@9.10.1`.
74
+
75
+ ### Scheduler
76
+
77
+ #### `@mui/x-scheduler@9.0.0-beta.7`
78
+
79
+ - [scheduler] Polish `EventCalendar` rendering for multi-resource events (#23161) @mustafajw07
80
+ - [scheduler] Responsive header (#22954) @noraleonte
81
+
82
+ #### `@mui/x-scheduler-premium@9.0.0-beta.7` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
83
+
84
+ Same changes as in `@mui/x-scheduler@9.0.0-beta.7`, plus:
85
+
86
+ - [scheduler-premium] Add the dependencies data model (#23117) @rita-codes
87
+ - [scheduler-premium] Dependencies - Render the FS arrow (#23162) @rita-codes
88
+
89
+ ### Codemod
90
+
91
+ #### `@mui/x-codemod@9.10.1`
92
+
93
+ Internal changes.
94
+
95
+ ### Docs
96
+
97
+ - [docs] Fix clearing, layout shift, and prop leak in Pickers demos (#23191) @LukasTy
98
+
99
+ ### Core
100
+
101
+ - [code-infra] Validate working of specific code-infra canary (#23207) @brijeshb42
102
+
103
+ ### Miscellaneous
104
+
105
+ - [chat] Normalize message part link URLs (#23187) @hasdfa
106
+ - [x-chat] Fix object URL leaks for submitted attachments (#23198) @hasdfa
107
+ - [internals] Make disposable types self-contained without `esnext.disposable` lib (#23164) @JCQuintas
108
+
3
109
  ## 9.10.0
4
110
 
5
111
  _Jul 17, 2026_
@@ -2,7 +2,7 @@ import type { D3OrdinalScale } from "../models/axis.mjs";
2
2
  interface SampledBandHighlightParams {
3
3
  scale: D3OrdinalScale;
4
4
  /** The highlighted value. */
5
- value: number | Date;
5
+ value: string | number | Date;
6
6
  /** The data index of the highlighted value, when known (O(1) lookup). */
7
7
  dataIndex: number | undefined;
8
8
  /** The axis data. */
@@ -2,7 +2,7 @@ import type { D3OrdinalScale } from "../models/axis.js";
2
2
  interface SampledBandHighlightParams {
3
3
  scale: D3OrdinalScale;
4
4
  /** The highlighted value. */
5
- value: number | Date;
5
+ value: string | number | Date;
6
6
  /** The data index of the highlighted value, when known (O(1) lookup). */
7
7
  dataIndex: number | undefined;
8
8
  /** The axis data. */
@@ -4,6 +4,14 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getSampledBandHighlight = getSampledBandHighlight;
7
+ var _getSampledBucketRegion = require("../internals/getSampledBucketRegion");
8
+ function findDataIndex(data, value) {
9
+ if (value instanceof Date) {
10
+ const time = value.getTime();
11
+ return data.findIndex(item => item instanceof Date && item.getTime() === time);
12
+ }
13
+ return data.indexOf(value);
14
+ }
7
15
  /**
8
16
  * Computes the band highlight rectangle along the ordinal axis.
9
17
  * When the series is sampled (`bucketSize > 1`), the band is widened to cover the whole merged bucket.
@@ -20,12 +28,16 @@ function getSampledBandHighlight({
20
28
  let bandStart = scale(value) - halfPadding;
21
29
  let bandSize = step;
22
30
  if (bucketSize > 1 && data) {
23
- const index = dataIndex ?? data.indexOf(value);
31
+ const index = dataIndex ?? findDataIndex(data, value);
24
32
  if (index >= 0) {
25
33
  const bucketStart = Math.floor(index / bucketSize) * bucketSize;
26
34
  const bucketEnd = Math.min(bucketStart + bucketSize - 1, data.length - 1);
27
- bandStart = scale(data[bucketStart]) - halfPadding;
28
- bandSize = (bucketEnd - bucketStart + 1) * step;
35
+ const {
36
+ regionStart,
37
+ regionSize
38
+ } = (0, _getSampledBucketRegion.getSampledBucketRegion)(scale, data, bucketStart, bucketEnd);
39
+ bandStart = regionStart;
40
+ bandSize = regionSize;
29
41
  }
30
42
  }
31
43
  return {
@@ -1,3 +1,11 @@
1
+ import { getSampledBucketRegion } from "../internals/getSampledBucketRegion.mjs";
2
+ function findDataIndex(data, value) {
3
+ if (value instanceof Date) {
4
+ const time = value.getTime();
5
+ return data.findIndex(item => item instanceof Date && item.getTime() === time);
6
+ }
7
+ return data.indexOf(value);
8
+ }
1
9
  /**
2
10
  * Computes the band highlight rectangle along the ordinal axis.
3
11
  * When the series is sampled (`bucketSize > 1`), the band is widened to cover the whole merged bucket.
@@ -14,12 +22,16 @@ export function getSampledBandHighlight({
14
22
  let bandStart = scale(value) - halfPadding;
15
23
  let bandSize = step;
16
24
  if (bucketSize > 1 && data) {
17
- const index = dataIndex ?? data.indexOf(value);
25
+ const index = dataIndex ?? findDataIndex(data, value);
18
26
  if (index >= 0) {
19
27
  const bucketStart = Math.floor(index / bucketSize) * bucketSize;
20
28
  const bucketEnd = Math.min(bucketStart + bucketSize - 1, data.length - 1);
21
- bandStart = scale(data[bucketStart]) - halfPadding;
22
- bandSize = (bucketEnd - bucketStart + 1) * step;
29
+ const {
30
+ regionStart,
31
+ regionSize
32
+ } = getSampledBucketRegion(scale, data, bucketStart, bucketEnd);
33
+ bandStart = regionStart;
34
+ bandSize = regionSize;
23
35
  }
24
36
  }
25
37
  return {
@@ -6,5 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _createCommonKeyboardFocusHandler = require("../../internals/createCommonKeyboardFocusHandler");
8
8
  var _composition = require("../../models/seriesType/composition");
9
- const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(_composition.composableCartesianSeriesTypes);
9
+ const allowCycles = false;
10
+ const useCurrentSeriesMaxLength = false;
11
+ const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(_composition.composableCartesianSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
10
12
  var _default = exports.default = keyboardFocusHandler;
@@ -1,4 +1,6 @@
1
1
  import { createCommonKeyboardFocusHandler } from "../../internals/createCommonKeyboardFocusHandler.mjs";
2
2
  import { composableCartesianSeriesTypes } from "../../models/seriesType/composition.mjs";
3
- const keyboardFocusHandler = createCommonKeyboardFocusHandler(composableCartesianSeriesTypes);
3
+ const allowCycles = false;
4
+ const useCurrentSeriesMaxLength = false;
5
+ const keyboardFocusHandler = createCommonKeyboardFocusHandler(composableCartesianSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
4
6
  export default keyboardFocusHandler;
@@ -5,11 +5,11 @@ interface GetLabelAttributesParams extends Required<Pick<RadarAxisProps, 'labelO
5
5
  angle: number;
6
6
  }
7
7
  export declare function getLabelAttributes(params: GetLabelAttributesParams): {
8
- transform?: undefined;
9
8
  x: number;
10
9
  y: number;
11
10
  textAnchor: "end" | "inherit" | "middle" | "start" | undefined;
12
11
  dominantBaseline: "alphabetic" | "auto" | "central" | "hanging" | "ideographic" | "inherit" | "mathematical" | "middle" | "no-change" | "reset-size" | "text-after-edge" | "text-before-edge" | "use-script" | undefined;
12
+ transform?: undefined;
13
13
  } | {
14
14
  x: number;
15
15
  y: number;
@@ -5,11 +5,11 @@ interface GetLabelAttributesParams extends Required<Pick<RadarAxisProps, 'labelO
5
5
  angle: number;
6
6
  }
7
7
  export declare function getLabelAttributes(params: GetLabelAttributesParams): {
8
- transform?: undefined;
9
8
  x: number;
10
9
  y: number;
11
10
  textAnchor: "end" | "inherit" | "middle" | "start" | undefined;
12
11
  dominantBaseline: "alphabetic" | "auto" | "central" | "hanging" | "ideographic" | "inherit" | "mathematical" | "middle" | "no-change" | "reset-size" | "text-after-edge" | "text-before-edge" | "use-script" | undefined;
12
+ transform?: undefined;
13
13
  } | {
14
14
  x: number;
15
15
  y: number;
@@ -6,5 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _createCommonKeyboardFocusHandler = require("../../internals/createCommonKeyboardFocusHandler");
8
8
  const outSeriesTypes = new Set(['radar']);
9
- const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(outSeriesTypes, true);
9
+ const allowCycles = true;
10
+ const useCurrentSeriesMaxLength = false;
11
+ const keyboardFocusHandler = (0, _createCommonKeyboardFocusHandler.createCommonKeyboardFocusHandler)(outSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
10
12
  var _default = exports.default = keyboardFocusHandler;
@@ -1,4 +1,6 @@
1
1
  import { createCommonKeyboardFocusHandler } from "../../internals/createCommonKeyboardFocusHandler.mjs";
2
2
  const outSeriesTypes = new Set(['radar']);
3
- const keyboardFocusHandler = createCommonKeyboardFocusHandler(outSeriesTypes, true);
3
+ const allowCycles = true;
4
+ const useCurrentSeriesMaxLength = false;
5
+ const keyboardFocusHandler = createCommonKeyboardFocusHandler(outSeriesTypes, allowCycles, useCurrentSeriesMaxLength);
4
6
  export default keyboardFocusHandler;
@@ -7,6 +7,7 @@ exports.useAnimatePieArc = useAnimatePieArc;
7
7
  var _d3Shape = require("@mui/x-charts-vendor/d3-shape");
8
8
  var _d3Interpolate = require("@mui/x-charts-vendor/d3-interpolate");
9
9
  var _useAnimate = require("./useAnimate");
10
+ var _snapEndAngleToFullCircle = require("../../internals/snapEndAngleToFullCircle");
10
11
  function pieArcPropsInterpolator(from, to) {
11
12
  const interpolateStartAngle = (0, _d3Interpolate.interpolateNumber)(from.startAngle, to.startAngle);
12
13
  const interpolateEndAngle = (0, _d3Interpolate.interpolateNumber)(from.endAngle, to.endAngle);
@@ -53,7 +54,7 @@ function useAnimatePieArc(props) {
53
54
  innerRadius: p.innerRadius,
54
55
  outerRadius: p.outerRadius,
55
56
  startAngle: p.startAngle,
56
- endAngle: p.endAngle
57
+ endAngle: (0, _snapEndAngleToFullCircle.snapEndAngleToFullCircle)(p.startAngle, p.endAngle, p.outerRadius, p.paddingAngle)
57
58
  }),
58
59
  visibility: p.startAngle === p.endAngle ? 'hidden' : 'visible'
59
60
  }),
@@ -1,6 +1,7 @@
1
1
  import { arc as d3Arc } from '@mui/x-charts-vendor/d3-shape';
2
2
  import { interpolateNumber } from '@mui/x-charts-vendor/d3-interpolate';
3
3
  import { useAnimate } from "./useAnimate.mjs";
4
+ import { snapEndAngleToFullCircle } from "../../internals/snapEndAngleToFullCircle.mjs";
4
5
  function pieArcPropsInterpolator(from, to) {
5
6
  const interpolateStartAngle = interpolateNumber(from.startAngle, to.startAngle);
6
7
  const interpolateEndAngle = interpolateNumber(from.endAngle, to.endAngle);
@@ -47,7 +48,7 @@ export function useAnimatePieArc(props) {
47
48
  innerRadius: p.innerRadius,
48
49
  outerRadius: p.outerRadius,
49
50
  startAngle: p.startAngle,
50
- endAngle: p.endAngle
51
+ endAngle: snapEndAngleToFullCircle(p.startAngle, p.endAngle, p.outerRadius, p.paddingAngle)
51
52
  }),
52
53
  visibility: p.startAngle === p.endAngle ? 'hidden' : 'visible'
53
54
  }),
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-charts v9.10.0
2
+ * @mui/x-charts v9.10.1
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-charts v9.10.0
2
+ * @mui/x-charts v9.10.1
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -28,7 +28,7 @@ compatibleSeriesTypes: Set<OutSeriesType>,
28
28
  */
29
29
  allowCycles?: boolean,
30
30
  /**
31
- * If true, series max index is defined by the current series length and not all series.
31
+ * If true (default), series max index is defined by the current series length and not all series.
32
32
  */
33
33
  useCurrentSeriesMaxLength?: boolean): (currentItem: WorkingItem | null, state: StateParameters<InSeriesType>) => ReturnedItem<OutSeriesType>;
34
34
  export declare function createGetPreviousIndexFocusedItem<InSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'>, OutSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'> = InSeriesType>(
@@ -41,7 +41,7 @@ compatibleSeriesTypes: Set<OutSeriesType>,
41
41
  */
42
42
  allowCycles?: boolean,
43
43
  /**
44
- * If true, series max index is defined by the current series length and not all series.
44
+ * If true (default), series max index is defined by the current series length and not all series.
45
45
  */
46
46
  useCurrentSeriesMaxLength?: boolean): (currentItem: WorkingItem | null, state: StateParameters<InSeriesType>) => ReturnedItem<OutSeriesType>;
47
47
  export declare function createGetNextSeriesFocusedItem<InSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'>, OutSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'> = InSeriesType>(
@@ -28,7 +28,7 @@ compatibleSeriesTypes: Set<OutSeriesType>,
28
28
  */
29
29
  allowCycles?: boolean,
30
30
  /**
31
- * If true, series max index is defined by the current series length and not all series.
31
+ * If true (default), series max index is defined by the current series length and not all series.
32
32
  */
33
33
  useCurrentSeriesMaxLength?: boolean): (currentItem: WorkingItem | null, state: StateParameters<InSeriesType>) => ReturnedItem<OutSeriesType>;
34
34
  export declare function createGetPreviousIndexFocusedItem<InSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'>, OutSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'> = InSeriesType>(
@@ -41,7 +41,7 @@ compatibleSeriesTypes: Set<OutSeriesType>,
41
41
  */
42
42
  allowCycles?: boolean,
43
43
  /**
44
- * If true, series max index is defined by the current series length and not all series.
44
+ * If true (default), series max index is defined by the current series length and not all series.
45
45
  */
46
46
  useCurrentSeriesMaxLength?: boolean): (currentItem: WorkingItem | null, state: StateParameters<InSeriesType>) => ReturnedItem<OutSeriesType>;
47
47
  export declare function createGetNextSeriesFocusedItem<InSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'>, OutSeriesType extends Exclude<ChartSeriesType, 'sankey' | 'heatmap'> = InSeriesType>(
@@ -33,9 +33,9 @@ compatibleSeriesTypes,
33
33
  */
34
34
  allowCycles = false,
35
35
  /**
36
- * If true, series max index is defined by the current series length and not all series.
36
+ * If true (default), series max index is defined by the current series length and not all series.
37
37
  */
38
- useCurrentSeriesMaxLength = false) {
38
+ useCurrentSeriesMaxLength = true) {
39
39
  return function getNextIndexFocusedItem(currentItem, state) {
40
40
  const processedSeries = (0, _useChartSeries.selectorChartSeriesProcessed)(state);
41
41
  let seriesId = currentItem?.seriesId;
@@ -84,9 +84,9 @@ compatibleSeriesTypes,
84
84
  */
85
85
  allowCycles = false,
86
86
  /**
87
- * If true, series max index is defined by the current series length and not all series.
87
+ * If true (default), series max index is defined by the current series length and not all series.
88
88
  */
89
- useCurrentSeriesMaxLength = false) {
89
+ useCurrentSeriesMaxLength = true) {
90
90
  return function getPreviousIndexFocusedItem(currentItem, state) {
91
91
  const processedSeries = (0, _useChartSeries.selectorChartSeriesProcessed)(state);
92
92
  let seriesId = currentItem?.seriesId;
@@ -25,9 +25,9 @@ compatibleSeriesTypes,
25
25
  */
26
26
  allowCycles = false,
27
27
  /**
28
- * If true, series max index is defined by the current series length and not all series.
28
+ * If true (default), series max index is defined by the current series length and not all series.
29
29
  */
30
- useCurrentSeriesMaxLength = false) {
30
+ useCurrentSeriesMaxLength = true) {
31
31
  return function getNextIndexFocusedItem(currentItem, state) {
32
32
  const processedSeries = selectorChartSeriesProcessed(state);
33
33
  let seriesId = currentItem?.seriesId;
@@ -76,9 +76,9 @@ compatibleSeriesTypes,
76
76
  */
77
77
  allowCycles = false,
78
78
  /**
79
- * If true, series max index is defined by the current series length and not all series.
79
+ * If true (default), series max index is defined by the current series length and not all series.
80
80
  */
81
- useCurrentSeriesMaxLength = false) {
81
+ useCurrentSeriesMaxLength = true) {
82
82
  return function getPreviousIndexFocusedItem(currentItem, state) {
83
83
  const processedSeries = selectorChartSeriesProcessed(state);
84
84
  let seriesId = currentItem?.seriesId;
@@ -60,6 +60,7 @@ function ChartsAccessibilityProxy() {
60
60
  div.setAttribute('tabindex', '0');
61
61
  }
62
62
  div.setAttribute('role', 'img');
63
+ div.setAttribute('aria-hidden', 'true');
63
64
  div.setAttribute('aria-labelledby', i === 0 ? `voiceover-${chartId}-1` : `voiceover-${chartId}-2`);
64
65
  div.style.width = '100%';
65
66
  div.style.height = '100%';
@@ -56,6 +56,7 @@ export function ChartsAccessibilityProxy() {
56
56
  div.setAttribute('tabindex', '0');
57
57
  }
58
58
  div.setAttribute('role', 'img');
59
+ div.setAttribute('aria-hidden', 'true');
59
60
  div.setAttribute('aria-labelledby', i === 0 ? `voiceover-${chartId}-1` : `voiceover-${chartId}-2`);
60
61
  div.style.width = '100%';
61
62
  div.style.height = '100%';
@@ -7,6 +7,7 @@ exports.createGetBarDimensions = createGetBarDimensions;
7
7
  exports.createGetBucketBarDimensions = createGetBucketBarDimensions;
8
8
  var _findMinMax = require("./findMinMax");
9
9
  var _getBandSize = require("./getBandSize");
10
+ var _getSampledBucketRegion = require("./getSampledBucketRegion");
10
11
  /** Minimum on-screen gap (px) kept between merged (sampled) bars so they stay distinguishable. */
11
12
  const MIN_SAMPLED_BAR_GAP_PX = 2;
12
13
  function shouldInvertStartCoordinate(verticalLayout, baseValue, reverse) {
@@ -78,9 +79,12 @@ function createGetBucketBarDimensions(params) {
78
79
  const step = baseScale.step();
79
80
  const valueScale = verticalLayout ? yAxisConfig.scale : xAxisConfig.scale;
80
81
  return function getBucketBarDimensions(startIndex, endIndex, min, max, groupIndex) {
81
- const spanStart = baseScale(baseScaleConfig.data[startIndex]);
82
+ // Region matches the axis highlight (see `getSampledBandHighlight`) so bar and highlight stay in sync.
83
+ const {
84
+ regionStart,
85
+ regionSize: bucketStride
86
+ } = (0, _getSampledBucketRegion.getSampledBucketRegion)(baseScale, baseScaleConfig.data, startIndex, endIndex);
82
87
  const bucketCount = endIndex - startIndex + 1;
83
- const bucketStride = bucketCount * step;
84
88
 
85
89
  // Keep the gap between buckets proportional to the bucket (same ratio as an unsampled chart),
86
90
  // but never thinner than MIN_SAMPLED_BAR_GAP_PX so it stays visible at every level and zoom.
@@ -89,7 +93,8 @@ function createGetBucketBarDimensions(params) {
89
93
  barWidth,
90
94
  offset
91
95
  } = (0, _getBandSize.getBandSize)(bucketStride - gap, numberOfGroups, baseScaleConfig.barGapRatio);
92
- const barOffset = groupIndex * (barWidth + offset);
96
+ // Center the bars in the region (split the gap to both sides) so they sit under the highlight.
97
+ const barOffset = gap / 2 + groupIndex * (barWidth + offset);
93
98
  const valueCoordinates = [valueScale(min), valueScale(max)];
94
99
  const [minValueCoord, maxValueCoord] = (0, _findMinMax.findMinMax)(valueCoordinates).map(v => Math.round(v));
95
100
  let barSize = maxValueCoord - minValueCoord;
@@ -97,8 +102,8 @@ function createGetBucketBarDimensions(params) {
97
102
  barSize = Math.max(series.minBarSize, barSize);
98
103
  }
99
104
  return {
100
- x: verticalLayout ? spanStart + barOffset : minValueCoord,
101
- y: verticalLayout ? minValueCoord : spanStart + barOffset,
105
+ x: verticalLayout ? regionStart + barOffset : minValueCoord,
106
+ y: verticalLayout ? minValueCoord : regionStart + barOffset,
102
107
  height: verticalLayout ? barSize : barWidth,
103
108
  width: verticalLayout ? barWidth : barSize
104
109
  };
@@ -1,5 +1,6 @@
1
1
  import { findMinMax } from "./findMinMax.mjs";
2
2
  import { getBandSize } from "./getBandSize.mjs";
3
+ import { getSampledBucketRegion } from "./getSampledBucketRegion.mjs";
3
4
 
4
5
  /** Minimum on-screen gap (px) kept between merged (sampled) bars so they stay distinguishable. */
5
6
  const MIN_SAMPLED_BAR_GAP_PX = 2;
@@ -72,9 +73,12 @@ export function createGetBucketBarDimensions(params) {
72
73
  const step = baseScale.step();
73
74
  const valueScale = verticalLayout ? yAxisConfig.scale : xAxisConfig.scale;
74
75
  return function getBucketBarDimensions(startIndex, endIndex, min, max, groupIndex) {
75
- const spanStart = baseScale(baseScaleConfig.data[startIndex]);
76
+ // Region matches the axis highlight (see `getSampledBandHighlight`) so bar and highlight stay in sync.
77
+ const {
78
+ regionStart,
79
+ regionSize: bucketStride
80
+ } = getSampledBucketRegion(baseScale, baseScaleConfig.data, startIndex, endIndex);
76
81
  const bucketCount = endIndex - startIndex + 1;
77
- const bucketStride = bucketCount * step;
78
82
 
79
83
  // Keep the gap between buckets proportional to the bucket (same ratio as an unsampled chart),
80
84
  // but never thinner than MIN_SAMPLED_BAR_GAP_PX so it stays visible at every level and zoom.
@@ -83,7 +87,8 @@ export function createGetBucketBarDimensions(params) {
83
87
  barWidth,
84
88
  offset
85
89
  } = getBandSize(bucketStride - gap, numberOfGroups, baseScaleConfig.barGapRatio);
86
- const barOffset = groupIndex * (barWidth + offset);
90
+ // Center the bars in the region (split the gap to both sides) so they sit under the highlight.
91
+ const barOffset = gap / 2 + groupIndex * (barWidth + offset);
87
92
  const valueCoordinates = [valueScale(min), valueScale(max)];
88
93
  const [minValueCoord, maxValueCoord] = findMinMax(valueCoordinates).map(v => Math.round(v));
89
94
  let barSize = maxValueCoord - minValueCoord;
@@ -91,8 +96,8 @@ export function createGetBucketBarDimensions(params) {
91
96
  barSize = Math.max(series.minBarSize, barSize);
92
97
  }
93
98
  return {
94
- x: verticalLayout ? spanStart + barOffset : minValueCoord,
95
- y: verticalLayout ? minValueCoord : spanStart + barOffset,
99
+ x: verticalLayout ? regionStart + barOffset : minValueCoord,
100
+ y: verticalLayout ? minValueCoord : regionStart + barOffset,
96
101
  height: verticalLayout ? barSize : barWidth,
97
102
  width: verticalLayout ? barWidth : barSize
98
103
  };
@@ -0,0 +1,12 @@
1
+ import type { D3OrdinalScale } from "../models/axis.mjs";
2
+ /**
3
+ * Computes the band region covered by a sampled bucket spanning `data[startIndex..endIndex]`.
4
+ *
5
+ * The region is anchored at its left-most slot and spans whole steps, so both the rendered bar
6
+ * (see `createGetBucketBarDimensions`) and the axis highlight (see `getSampledBandHighlight`)
7
+ * stay in sync — on a reversed axis `startIndex` maps to the right, hence the `Math.min`.
8
+ */
9
+ export declare function getSampledBucketRegion(scale: D3OrdinalScale, data: readonly any[], startIndex: number, endIndex: number): {
10
+ regionStart: number;
11
+ regionSize: number;
12
+ };
@@ -0,0 +1,12 @@
1
+ import type { D3OrdinalScale } from "../models/axis.js";
2
+ /**
3
+ * Computes the band region covered by a sampled bucket spanning `data[startIndex..endIndex]`.
4
+ *
5
+ * The region is anchored at its left-most slot and spans whole steps, so both the rendered bar
6
+ * (see `createGetBucketBarDimensions`) and the axis highlight (see `getSampledBandHighlight`)
7
+ * stay in sync — on a reversed axis `startIndex` maps to the right, hence the `Math.min`.
8
+ */
9
+ export declare function getSampledBucketRegion(scale: D3OrdinalScale, data: readonly any[], startIndex: number, endIndex: number): {
10
+ regionStart: number;
11
+ regionSize: number;
12
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getSampledBucketRegion = getSampledBucketRegion;
7
+ /**
8
+ * Computes the band region covered by a sampled bucket spanning `data[startIndex..endIndex]`.
9
+ *
10
+ * The region is anchored at its left-most slot and spans whole steps, so both the rendered bar
11
+ * (see `createGetBucketBarDimensions`) and the axis highlight (see `getSampledBandHighlight`)
12
+ * stay in sync — on a reversed axis `startIndex` maps to the right, hence the `Math.min`.
13
+ */
14
+ function getSampledBucketRegion(scale, data, startIndex, endIndex) {
15
+ const step = scale.step();
16
+ const halfPadding = (step - scale.bandwidth()) / 2;
17
+ const startPos = scale(data[startIndex]);
18
+ const endPos = scale(data[endIndex]);
19
+ return {
20
+ regionStart: Math.min(startPos, endPos) - halfPadding,
21
+ regionSize: (endIndex - startIndex + 1) * step
22
+ };
23
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Computes the band region covered by a sampled bucket spanning `data[startIndex..endIndex]`.
3
+ *
4
+ * The region is anchored at its left-most slot and spans whole steps, so both the rendered bar
5
+ * (see `createGetBucketBarDimensions`) and the axis highlight (see `getSampledBandHighlight`)
6
+ * stay in sync — on a reversed axis `startIndex` maps to the right, hence the `Math.min`.
7
+ */
8
+ export function getSampledBucketRegion(scale, data, startIndex, endIndex) {
9
+ const step = scale.step();
10
+ const halfPadding = (step - scale.bandwidth()) / 2;
11
+ const startPos = scale(data[startIndex]);
12
+ const endPos = scale(data[endIndex]);
13
+ return {
14
+ regionStart: Math.min(startPos, endPos) - halfPadding,
15
+ regionSize: (endIndex - startIndex + 1) * step
16
+ };
17
+ }
@@ -5,6 +5,9 @@ import type { ProcessedSeries } from "../../../corePlugins/useChartSeries/useCha
5
5
  * Walk forward (or backward) from `startIndex` and return the first dataIndex
6
6
  * whose data point is visible. Returns `null` if every traversed index is hidden.
7
7
  *
8
+ * Shared-axis types (line, bar) opt out of `useCurrentSeriesMaxLength`, so `dataLength`
9
+ * can exceed the focused series length to focus an index with no value.
10
+ *
8
11
  * Series-level hidden flags are filtered upstream (see `getNonEmptySeriesArray`),
9
12
  * so only per-data-point hidden flags are relevant here. Today only Pie sets a
10
13
  * `hidden` flag on individual data items; for other series types the data
@@ -5,6 +5,9 @@ import type { ProcessedSeries } from "../../../corePlugins/useChartSeries/useCha
5
5
  * Walk forward (or backward) from `startIndex` and return the first dataIndex
6
6
  * whose data point is visible. Returns `null` if every traversed index is hidden.
7
7
  *
8
+ * Shared-axis types (line, bar) opt out of `useCurrentSeriesMaxLength`, so `dataLength`
9
+ * can exceed the focused series length to focus an index with no value.
10
+ *
8
11
  * Series-level hidden flags are filtered upstream (see `getNonEmptySeriesArray`),
9
12
  * so only per-data-point hidden flags are relevant here. Today only Pie sets a
10
13
  * `hidden` flag on individual data items; for other series types the data
@@ -8,6 +8,9 @@ exports.findVisibleDataIndex = findVisibleDataIndex;
8
8
  * Walk forward (or backward) from `startIndex` and return the first dataIndex
9
9
  * whose data point is visible. Returns `null` if every traversed index is hidden.
10
10
  *
11
+ * Shared-axis types (line, bar) opt out of `useCurrentSeriesMaxLength`, so `dataLength`
12
+ * can exceed the focused series length to focus an index with no value.
13
+ *
11
14
  * Series-level hidden flags are filtered upstream (see `getNonEmptySeriesArray`),
12
15
  * so only per-data-point hidden flags are relevant here. Today only Pie sets a
13
16
  * `hidden` flag on individual data items; for other series types the data
@@ -2,6 +2,9 @@
2
2
  * Walk forward (or backward) from `startIndex` and return the first dataIndex
3
3
  * whose data point is visible. Returns `null` if every traversed index is hidden.
4
4
  *
5
+ * Shared-axis types (line, bar) opt out of `useCurrentSeriesMaxLength`, so `dataLength`
6
+ * can exceed the focused series length to focus an index with no value.
7
+ *
5
8
  * Series-level hidden flags are filtered upstream (see `getNonEmptySeriesArray`),
6
9
  * so only per-data-point hidden flags are relevant here. Today only Pie sets a
7
10
  * `hidden` flag on individual data items; for other series types the data
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Snaps the end angle of an arc to create a full circle when the arc spans almost, but not
3
+ * exactly, the full circle.
4
+ *
5
+ * When an arc spans almost the full circle (e.g., when the remaining slices of a pie are
6
+ * extremely small compared to the largest one), its start and end points are so close that they
7
+ * can round to the same path coordinates. SVG omits arcs whose end point equals their start
8
+ * point, which makes the quasi-full-circle arc invisible.
9
+ * See https://github.com/mui/mui-x/issues/14167
10
+ *
11
+ * The end angle is only snapped when the gap left by the arc is smaller than the precision of
12
+ * the serialized path, so the snapping is visually lossless.
13
+ *
14
+ * @param startAngle The start angle of the arc, in radians.
15
+ * @param endAngle The end angle of the arc, in radians.
16
+ * @param outerRadius The outer radius of the arc.
17
+ * @param padAngle The pad angle of the arc, in radians.
18
+ * @returns The end angle, snapped to `startAngle ± 2 * Math.PI` when applicable.
19
+ */
20
+ export declare function snapEndAngleToFullCircle(startAngle: number, endAngle: number, outerRadius: number, padAngle?: number): number;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Snaps the end angle of an arc to create a full circle when the arc spans almost, but not
3
+ * exactly, the full circle.
4
+ *
5
+ * When an arc spans almost the full circle (e.g., when the remaining slices of a pie are
6
+ * extremely small compared to the largest one), its start and end points are so close that they
7
+ * can round to the same path coordinates. SVG omits arcs whose end point equals their start
8
+ * point, which makes the quasi-full-circle arc invisible.
9
+ * See https://github.com/mui/mui-x/issues/14167
10
+ *
11
+ * The end angle is only snapped when the gap left by the arc is smaller than the precision of
12
+ * the serialized path, so the snapping is visually lossless.
13
+ *
14
+ * @param startAngle The start angle of the arc, in radians.
15
+ * @param endAngle The end angle of the arc, in radians.
16
+ * @param outerRadius The outer radius of the arc.
17
+ * @param padAngle The pad angle of the arc, in radians.
18
+ * @returns The end angle, snapped to `startAngle ± 2 * Math.PI` when applicable.
19
+ */
20
+ export declare function snapEndAngleToFullCircle(startAngle: number, endAngle: number, outerRadius: number, padAngle?: number): number;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.snapEndAngleToFullCircle = snapEndAngleToFullCircle;
7
+ const TAU = 2 * Math.PI;
8
+
9
+ /* `d3-shape` serializes paths with a precision of 3 digits, i.e., a resolution of 1e-3. */
10
+ const PATH_PRECISION = 1e-3;
11
+ /* If the distance between the start and end points of an arc is at least twice the path
12
+ * precision, at least one of their coordinates is guaranteed to round to a different value. */
13
+ const MIN_DISTINCT_CHORD_LENGTH = 2 * PATH_PRECISION;
14
+
15
+ /**
16
+ * Snaps the end angle of an arc to create a full circle when the arc spans almost, but not
17
+ * exactly, the full circle.
18
+ *
19
+ * When an arc spans almost the full circle (e.g., when the remaining slices of a pie are
20
+ * extremely small compared to the largest one), its start and end points are so close that they
21
+ * can round to the same path coordinates. SVG omits arcs whose end point equals their start
22
+ * point, which makes the quasi-full-circle arc invisible.
23
+ * See https://github.com/mui/mui-x/issues/14167
24
+ *
25
+ * The end angle is only snapped when the gap left by the arc is smaller than the precision of
26
+ * the serialized path, so the snapping is visually lossless.
27
+ *
28
+ * @param startAngle The start angle of the arc, in radians.
29
+ * @param endAngle The end angle of the arc, in radians.
30
+ * @param outerRadius The outer radius of the arc.
31
+ * @param padAngle The pad angle of the arc, in radians.
32
+ * @returns The end angle, snapped to `startAngle ± 2 * Math.PI` when applicable.
33
+ */
34
+ function snapEndAngleToFullCircle(startAngle, endAngle, outerRadius, padAngle = 0) {
35
+ const sweep = Math.abs(endAngle - startAngle);
36
+ if (sweep === 0 || sweep >= TAU) {
37
+ return endAngle;
38
+ }
39
+
40
+ // Chord between the start and end points of the arc, once the padding is applied.
41
+ // For such small angles, the chord length is approximately `radius * angle`.
42
+ const gapChordLength = (TAU - sweep + Math.abs(padAngle)) * outerRadius;
43
+ if (gapChordLength >= MIN_DISTINCT_CHORD_LENGTH) {
44
+ return endAngle;
45
+ }
46
+ return startAngle + Math.sign(endAngle - startAngle) * TAU;
47
+ }
@@ -0,0 +1,41 @@
1
+ const TAU = 2 * Math.PI;
2
+
3
+ /* `d3-shape` serializes paths with a precision of 3 digits, i.e., a resolution of 1e-3. */
4
+ const PATH_PRECISION = 1e-3;
5
+ /* If the distance between the start and end points of an arc is at least twice the path
6
+ * precision, at least one of their coordinates is guaranteed to round to a different value. */
7
+ const MIN_DISTINCT_CHORD_LENGTH = 2 * PATH_PRECISION;
8
+
9
+ /**
10
+ * Snaps the end angle of an arc to create a full circle when the arc spans almost, but not
11
+ * exactly, the full circle.
12
+ *
13
+ * When an arc spans almost the full circle (e.g., when the remaining slices of a pie are
14
+ * extremely small compared to the largest one), its start and end points are so close that they
15
+ * can round to the same path coordinates. SVG omits arcs whose end point equals their start
16
+ * point, which makes the quasi-full-circle arc invisible.
17
+ * See https://github.com/mui/mui-x/issues/14167
18
+ *
19
+ * The end angle is only snapped when the gap left by the arc is smaller than the precision of
20
+ * the serialized path, so the snapping is visually lossless.
21
+ *
22
+ * @param startAngle The start angle of the arc, in radians.
23
+ * @param endAngle The end angle of the arc, in radians.
24
+ * @param outerRadius The outer radius of the arc.
25
+ * @param padAngle The pad angle of the arc, in radians.
26
+ * @returns The end angle, snapped to `startAngle ± 2 * Math.PI` when applicable.
27
+ */
28
+ export function snapEndAngleToFullCircle(startAngle, endAngle, outerRadius, padAngle = 0) {
29
+ const sweep = Math.abs(endAngle - startAngle);
30
+ if (sweep === 0 || sweep >= TAU) {
31
+ return endAngle;
32
+ }
33
+
34
+ // Chord between the start and end points of the arc, once the padding is applied.
35
+ // For such small angles, the chord length is approximately `radius * angle`.
36
+ const gapChordLength = (TAU - sweep + Math.abs(padAngle)) * outerRadius;
37
+ if (gapChordLength >= MIN_DISTINCT_CHORD_LENGTH) {
38
+ return endAngle;
39
+ }
40
+ return startAngle + Math.sign(endAngle - startAngle) * TAU;
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-charts",
3
- "version": "9.10.0",
3
+ "version": "9.10.1",
4
4
  "author": "MUI Team",
5
5
  "description": "The community edition of MUI X Charts components.",
6
6
  "license": "MIT",
@@ -34,9 +34,9 @@
34
34
  "prop-types": "^15.8.1",
35
35
  "reselect": "^5.2.0",
36
36
  "use-sync-external-store": "^1.6.0",
37
- "@mui/x-charts-vendor": "^9.4.0",
38
- "@mui/x-internals": "^9.10.0",
39
- "@mui/x-internal-gestures": "^9.10.0"
37
+ "@mui/x-internals": "^9.10.1",
38
+ "@mui/x-internal-gestures": "^9.10.0",
39
+ "@mui/x-charts-vendor": "^9.4.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@emotion/react": "^11.9.0",