@mui/x-charts 8.14.1 → 8.15.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.
Files changed (125) hide show
  1. package/BarChart/BarChart.js +8 -0
  2. package/BarChart/BarChart.plugins.d.ts +2 -1
  3. package/BarChart/BarChart.plugins.js +2 -1
  4. package/BarChart/useBarChartProps.js +4 -2
  5. package/CHANGELOG.md +107 -0
  6. package/ChartContainer/ChartContainer.js +8 -0
  7. package/ChartContainer/useChartContainerProps.js +4 -2
  8. package/ChartsBrushOverlay/ChartsBrushOverlay.classes.d.ts +12 -0
  9. package/ChartsBrushOverlay/ChartsBrushOverlay.classes.js +9 -0
  10. package/ChartsBrushOverlay/ChartsBrushOverlay.d.ts +6 -0
  11. package/ChartsBrushOverlay/ChartsBrushOverlay.js +102 -0
  12. package/ChartsBrushOverlay/index.d.ts +4 -0
  13. package/ChartsBrushOverlay/index.js +19 -0
  14. package/ChartsReferenceLine/ChartsReferenceLine.js +1 -1
  15. package/ChartsReferenceLine/ChartsXReferenceLine.js +13 -8
  16. package/ChartsReferenceLine/ChartsYReferenceLine.js +13 -8
  17. package/ChartsReferenceLine/common.d.ts +3 -1
  18. package/ChartsReferenceLine/common.js +3 -1
  19. package/ChartsTooltip/ChartsTooltipContainer.js +20 -2
  20. package/ChartsXAxis/getVisibleLabels.js +45 -25
  21. package/LineChart/LineChart.js +8 -0
  22. package/LineChart/LineChart.plugins.d.ts +2 -1
  23. package/LineChart/LineChart.plugins.js +2 -1
  24. package/LineChart/useLineChartProps.js +4 -2
  25. package/ScatterChart/ScatterChart.js +8 -0
  26. package/ScatterChart/ScatterChart.plugins.d.ts +2 -1
  27. package/ScatterChart/ScatterChart.plugins.js +2 -1
  28. package/ScatterChart/useScatterChartProps.js +5 -3
  29. package/SparkLineChart/SparkLineChart.js +8 -0
  30. package/esm/BarChart/BarChart.js +8 -0
  31. package/esm/BarChart/BarChart.plugins.d.ts +2 -1
  32. package/esm/BarChart/BarChart.plugins.js +2 -1
  33. package/esm/BarChart/useBarChartProps.js +4 -2
  34. package/esm/ChartContainer/ChartContainer.js +8 -0
  35. package/esm/ChartContainer/useChartContainerProps.js +4 -2
  36. package/esm/ChartsBrushOverlay/ChartsBrushOverlay.classes.d.ts +12 -0
  37. package/esm/ChartsBrushOverlay/ChartsBrushOverlay.classes.js +2 -0
  38. package/esm/ChartsBrushOverlay/ChartsBrushOverlay.d.ts +6 -0
  39. package/esm/ChartsBrushOverlay/ChartsBrushOverlay.js +95 -0
  40. package/esm/ChartsBrushOverlay/index.d.ts +4 -0
  41. package/esm/ChartsBrushOverlay/index.js +2 -0
  42. package/esm/ChartsReferenceLine/ChartsReferenceLine.js +1 -1
  43. package/esm/ChartsReferenceLine/ChartsXReferenceLine.js +14 -9
  44. package/esm/ChartsReferenceLine/ChartsYReferenceLine.js +14 -9
  45. package/esm/ChartsReferenceLine/common.d.ts +3 -1
  46. package/esm/ChartsReferenceLine/common.js +2 -0
  47. package/esm/ChartsTooltip/ChartsTooltipContainer.js +20 -2
  48. package/esm/ChartsXAxis/getVisibleLabels.js +45 -25
  49. package/esm/LineChart/LineChart.js +8 -0
  50. package/esm/LineChart/LineChart.plugins.d.ts +2 -1
  51. package/esm/LineChart/LineChart.plugins.js +2 -1
  52. package/esm/LineChart/useLineChartProps.js +4 -2
  53. package/esm/ScatterChart/ScatterChart.js +8 -0
  54. package/esm/ScatterChart/ScatterChart.plugins.d.ts +2 -1
  55. package/esm/ScatterChart/ScatterChart.plugins.js +2 -1
  56. package/esm/ScatterChart/useScatterChartProps.js +5 -3
  57. package/esm/SparkLineChart/SparkLineChart.js +8 -0
  58. package/esm/hooks/index.d.ts +2 -1
  59. package/esm/hooks/index.js +2 -1
  60. package/esm/hooks/useBrush.d.ts +18 -0
  61. package/esm/hooks/useBrush.js +16 -0
  62. package/esm/index.d.ts +2 -1
  63. package/esm/index.js +2 -1
  64. package/esm/internals/domUtils.d.ts +9 -4
  65. package/esm/internals/domUtils.js +115 -52
  66. package/esm/internals/index.d.ts +1 -0
  67. package/esm/internals/index.js +1 -0
  68. package/esm/internals/plugins/allPlugins.d.ts +4 -3
  69. package/esm/internals/plugins/allPlugins.js +2 -1
  70. package/esm/internals/plugins/corePlugins/useChartInteractionListener/useChartInteractionListener.js +16 -10
  71. package/esm/internals/plugins/corePlugins/useChartInteractionListener/useChartInteractionListener.types.d.ts +2 -2
  72. package/esm/internals/plugins/featurePlugins/useChartBrush/index.d.ts +3 -0
  73. package/esm/internals/plugins/featurePlugins/useChartBrush/index.js +3 -0
  74. package/esm/internals/plugins/featurePlugins/useChartBrush/useChartBrush.d.ts +3 -0
  75. package/esm/internals/plugins/featurePlugins/useChartBrush/useChartBrush.js +109 -0
  76. package/esm/internals/plugins/featurePlugins/useChartBrush/useChartBrush.selectors.d.ts +82 -0
  77. package/esm/internals/plugins/featurePlugins/useChartBrush/useChartBrush.selectors.js +75 -0
  78. package/esm/internals/plugins/featurePlugins/useChartBrush/useChartBrush.types.d.ts +72 -0
  79. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/createZoomLookup.js +3 -2
  80. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeAxis.js +2 -2
  81. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeZoom.d.ts +2 -1
  82. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeZoom.js +8 -3
  83. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.ts +3 -1
  84. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianHighlight.selectors.d.ts +4 -4
  85. package/esm/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianHighlight.selectors.js +13 -6
  86. package/esm/internals/plugins/utils/selectors.d.ts +1 -1
  87. package/esm/locales/elGR.js +97 -99
  88. package/esm/tests/constants.js +1 -0
  89. package/hooks/index.d.ts +2 -1
  90. package/hooks/index.js +12 -0
  91. package/hooks/useBrush.d.ts +18 -0
  92. package/hooks/useBrush.js +21 -0
  93. package/index.d.ts +2 -1
  94. package/index.js +13 -1
  95. package/internals/domUtils.d.ts +9 -4
  96. package/internals/domUtils.js +119 -54
  97. package/internals/index.d.ts +1 -0
  98. package/internals/index.js +12 -0
  99. package/internals/plugins/allPlugins.d.ts +4 -3
  100. package/internals/plugins/allPlugins.js +2 -1
  101. package/internals/plugins/corePlugins/useChartInteractionListener/useChartInteractionListener.js +16 -10
  102. package/internals/plugins/corePlugins/useChartInteractionListener/useChartInteractionListener.types.d.ts +2 -2
  103. package/internals/plugins/featurePlugins/useChartBrush/index.d.ts +3 -0
  104. package/internals/plugins/featurePlugins/useChartBrush/index.js +38 -0
  105. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.d.ts +3 -0
  106. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.js +117 -0
  107. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.selectors.d.ts +82 -0
  108. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.selectors.js +82 -0
  109. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.types.d.ts +72 -0
  110. package/internals/plugins/featurePlugins/useChartBrush/useChartBrush.types.js +5 -0
  111. package/internals/plugins/featurePlugins/useChartCartesianAxis/createZoomLookup.js +3 -2
  112. package/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeAxis.js +2 -2
  113. package/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeZoom.d.ts +2 -1
  114. package/internals/plugins/featurePlugins/useChartCartesianAxis/defaultizeZoom.js +8 -3
  115. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.ts +3 -1
  116. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianHighlight.selectors.d.ts +4 -4
  117. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianHighlight.selectors.js +13 -6
  118. package/internals/plugins/utils/selectors.d.ts +1 -1
  119. package/locales/elGR.js +97 -99
  120. package/package.json +3 -3
  121. package/tests/constants.js +7 -0
  122. package/esm/internals/Flatbush.bench.js +0 -42
  123. package/internals/Flatbush.bench.d.ts +0 -1
  124. package/internals/Flatbush.bench.js +0 -44
  125. /package/esm/internals/{Flatbush.bench.d.ts → plugins/featurePlugins/useChartBrush/useChartBrush.types.js} +0 -0
@@ -1,6 +1,8 @@
1
1
  import { ChartsReferenceLineClasses } from "./chartsReferenceLineClasses.js";
2
2
  import { ChartsTextStyle } from "../ChartsText/index.js";
3
3
  import { AxisId } from "../models/axis.js";
4
+ export declare const DEFAULT_SPACING = 5;
5
+ export declare const DEFAULT_SPACING_MIDDLE_OTHER_AXIS = 0;
4
6
  export type CommonChartsReferenceLineProps = {
5
7
  /**
6
8
  * The alignment if the label is in the chart drawing area.
@@ -14,7 +16,7 @@ export type CommonChartsReferenceLineProps = {
14
16
  /**
15
17
  * Additional space around the label in px.
16
18
  * Can be a number or an object `{ x, y }` to distinguish space with the reference line and space with axes.
17
- * @default 5
19
+ * @default { x: 0, y: 5 } on a horizontal line and { x: 5, y: 0 } on a vertical line.
18
20
  */
19
21
  spacing?: number | {
20
22
  x?: number;
@@ -1,6 +1,8 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import { styled } from '@mui/material/styles';
3
3
  import { referenceLineClasses } from "./chartsReferenceLineClasses.js";
4
+ export const DEFAULT_SPACING = 5;
5
+ export const DEFAULT_SPACING_MIDDLE_OTHER_AXIS = 0;
4
6
  export const ReferenceLineRoot = styled('g')(({
5
7
  theme
6
8
  }) => ({
@@ -20,8 +20,10 @@ import { selectorChartsInteractionAxisTooltip } from "../internals/plugins/featu
20
20
  import { selectorChartsInteractionPolarAxisTooltip } from "../internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarInteraction.selectors.js";
21
21
  import { useAxisSystem } from "../hooks/useAxisSystem.js";
22
22
  import { useSvgRef } from "../hooks/index.js";
23
+ import { selectorBrushShouldPreventTooltip } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
24
+ import { createSelector } from "../internals/plugins/utils/selectors.js";
23
25
  import { jsx as _jsx } from "react/jsx-runtime";
24
- const noAxis = () => false;
26
+ const selectorReturnFalse = () => false;
25
27
  const ChartsTooltipRoot = styled(Popper, {
26
28
  name: 'MuiChartsTooltip',
27
29
  slot: 'Root'
@@ -31,6 +33,21 @@ const ChartsTooltipRoot = styled(Popper, {
31
33
  pointerEvents: 'none',
32
34
  zIndex: theme.zIndex.modal
33
35
  }));
36
+ const selectorSelectIsOpenSelector = createSelector([selectorBrushShouldPreventTooltip, (_, trigger) => trigger, (_, __, axisSystem) => axisSystem], (shouldPreventBecauseOfBrush, trigger, axisSystem) => {
37
+ if (shouldPreventBecauseOfBrush) {
38
+ return selectorReturnFalse;
39
+ }
40
+ if (trigger === 'item') {
41
+ return selectorChartsInteractionItemIsDefined;
42
+ }
43
+ if (axisSystem === 'polar') {
44
+ return selectorChartsInteractionPolarAxisTooltip;
45
+ }
46
+ if (axisSystem === 'cartesian') {
47
+ return selectorChartsInteractionAxisTooltip;
48
+ }
49
+ return selectorReturnFalse;
50
+ });
34
51
 
35
52
  /**
36
53
  * Demos:
@@ -63,7 +80,8 @@ function ChartsTooltipContainer(inProps) {
63
80
  }));
64
81
  const axisSystem = useAxisSystem();
65
82
  const store = useStore();
66
- const isOpen = useSelector(store, trigger === 'axis' ? axisSystem === 'polar' && selectorChartsInteractionPolarAxisTooltip || axisSystem === 'cartesian' && selectorChartsInteractionAxisTooltip || noAxis : selectorChartsInteractionItemIsDefined);
83
+ const isOpenSelector = useSelector(store, selectorSelectIsOpenSelector, [trigger, axisSystem]);
84
+ const isOpen = useSelector(store, isOpenSelector);
67
85
  React.useEffect(() => {
68
86
  const element = svgRef.current;
69
87
  if (element === null) {
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { getMinXTranslation } from "../internals/geometry.js";
4
- import { getWordsByLines } from "../internals/getWordsByLines.js";
4
+ import { batchMeasureStrings } from "../internals/domUtils.js";
5
5
 
6
6
  /* Returns a set of indices of the tick labels that should be visible. */
7
7
  export function getVisibleLabels(xTicks, {
@@ -12,23 +12,6 @@ export function getVisibleLabels(xTicks, {
12
12
  isMounted,
13
13
  isXInside
14
14
  }) {
15
- const getTickLabelSize = tick => {
16
- if (!isMounted || tick.formattedValue === undefined) {
17
- return {
18
- width: 0,
19
- height: 0
20
- };
21
- }
22
- const tickSizes = getWordsByLines({
23
- style,
24
- needsComputation: true,
25
- text: tick.formattedValue
26
- });
27
- return {
28
- width: Math.max(...tickSizes.map(size => size.width)),
29
- height: Math.max(tickSizes.length * tickSizes[0].height)
30
- };
31
- };
32
15
  if (typeof tickLabelInterval === 'function') {
33
16
  return new Set(xTicks.filter((item, index) => tickLabelInterval(item.value, index)));
34
17
  }
@@ -36,7 +19,7 @@ export function getVisibleLabels(xTicks, {
36
19
  // Filter label to avoid overlap
37
20
  let previousTextLimit = 0;
38
21
  const direction = reverse ? -1 : 1;
39
- return new Set(xTicks.filter((item, labelIndex) => {
22
+ const candidateTickLabels = xTicks.filter(item => {
40
23
  const {
41
24
  offset,
42
25
  labelOffset,
@@ -46,18 +29,25 @@ export function getVisibleLabels(xTicks, {
46
29
  return false;
47
30
  }
48
31
  const textPosition = offset + labelOffset;
32
+ return isXInside(textPosition);
33
+ });
34
+ const sizeMap = measureTickLabels(candidateTickLabels, style);
35
+ return new Set(candidateTickLabels.filter((item, labelIndex) => {
36
+ const {
37
+ offset,
38
+ labelOffset
39
+ } = item;
40
+ const textPosition = offset + labelOffset;
49
41
  if (labelIndex > 0 && direction * textPosition < direction * (previousTextLimit + tickLabelMinGap)) {
50
42
  return false;
51
43
  }
52
- if (!isXInside(textPosition)) {
53
- return false;
54
- }
55
-
56
- /* Measuring text width is expensive, so we need to delay it as much as possible to improve performance. */
57
44
  const {
58
45
  width,
59
46
  height
60
- } = getTickLabelSize(item);
47
+ } = isMounted ? getTickLabelSize(sizeMap, item) : {
48
+ width: 0,
49
+ height: 0
50
+ };
61
51
  const distance = getMinXTranslation(width, height, style?.angle);
62
52
  const currentTextLimit = textPosition - direction * distance / 2;
63
53
  if (labelIndex > 0 && direction * currentTextLimit < direction * (previousTextLimit + tickLabelMinGap)) {
@@ -68,4 +58,34 @@ export function getVisibleLabels(xTicks, {
68
58
  previousTextLimit = textPosition + direction * distance / 2;
69
59
  return true;
70
60
  }));
61
+ }
62
+ function getTickLabelSize(sizeMap, tick) {
63
+ if (tick.formattedValue === undefined) {
64
+ return {
65
+ width: 0,
66
+ height: 0
67
+ };
68
+ }
69
+ let width = 0;
70
+ let height = 0;
71
+ for (const line of tick.formattedValue.split('\n')) {
72
+ const lineSize = sizeMap.get(line);
73
+ if (lineSize) {
74
+ width = Math.max(width, lineSize.width);
75
+ height += lineSize.height;
76
+ }
77
+ }
78
+ return {
79
+ width,
80
+ height
81
+ };
82
+ }
83
+ function measureTickLabels(ticks, style) {
84
+ const strings = new Set();
85
+ for (const tick of ticks) {
86
+ if (tick.formattedValue) {
87
+ tick.formattedValue.split('\n').forEach(line => strings.add(line));
88
+ }
89
+ }
90
+ return batchMeasureStrings(strings, style);
71
91
  }
@@ -90,6 +90,14 @@ process.env.NODE_ENV !== "production" ? LineChart.propTypes = {
90
90
  x: PropTypes.oneOf(['band', 'line', 'none']),
91
91
  y: PropTypes.oneOf(['band', 'line', 'none'])
92
92
  }),
93
+ /**
94
+ * Configuration for the brush interaction.
95
+ */
96
+ brushConfig: PropTypes.shape({
97
+ enabled: PropTypes.bool,
98
+ preventHighlight: PropTypes.bool,
99
+ preventTooltip: PropTypes.bool
100
+ }),
93
101
  children: PropTypes.node,
94
102
  className: PropTypes.string,
95
103
  /**
@@ -4,5 +4,6 @@ import { UseChartInteractionSignature } from "../internals/plugins/featurePlugin
4
4
  import { UseChartHighlightSignature } from "../internals/plugins/featurePlugins/useChartHighlight/index.js";
5
5
  import { UseChartKeyboardNavigationSignature } from "../internals/plugins/featurePlugins/useChartKeyboardNavigation/index.js";
6
6
  import { ConvertSignaturesIntoPlugins } from "../internals/plugins/models/helpers.js";
7
- export type LineChartPluginSignatures = [UseChartZAxisSignature, UseChartCartesianAxisSignature<'line'>, UseChartInteractionSignature, UseChartHighlightSignature, UseChartKeyboardNavigationSignature];
7
+ import { UseChartBrushSignature } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
8
+ export type LineChartPluginSignatures = [UseChartZAxisSignature, UseChartBrushSignature, UseChartCartesianAxisSignature<'line'>, UseChartInteractionSignature, UseChartHighlightSignature, UseChartKeyboardNavigationSignature];
8
9
  export declare const LINE_CHART_PLUGINS: ConvertSignaturesIntoPlugins<LineChartPluginSignatures>;
@@ -3,4 +3,5 @@ import { useChartCartesianAxis } from "../internals/plugins/featurePlugins/useCh
3
3
  import { useChartInteraction } from "../internals/plugins/featurePlugins/useChartInteraction/index.js";
4
4
  import { useChartHighlight } from "../internals/plugins/featurePlugins/useChartHighlight/index.js";
5
5
  import { useChartKeyboardNavigation } from "../internals/plugins/featurePlugins/useChartKeyboardNavigation/index.js";
6
- export const LINE_CHART_PLUGINS = [useChartZAxis, useChartCartesianAxis, useChartInteraction, useChartHighlight, useChartKeyboardNavigation];
6
+ import { useChartBrush } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
7
+ export const LINE_CHART_PLUGINS = [useChartZAxis, useChartBrush, useChartCartesianAxis, useChartInteraction, useChartHighlight, useChartKeyboardNavigation];
@@ -2,7 +2,7 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
5
- const _excluded = ["xAxis", "yAxis", "series", "width", "height", "margin", "colors", "dataset", "sx", "onAreaClick", "onLineClick", "onMarkClick", "axisHighlight", "disableLineItemHighlight", "hideLegend", "grid", "children", "slots", "slotProps", "skipAnimation", "loading", "highlightedItem", "onHighlightChange", "className", "showToolbar"];
5
+ const _excluded = ["xAxis", "yAxis", "series", "width", "height", "margin", "colors", "dataset", "sx", "onAreaClick", "onLineClick", "onMarkClick", "axisHighlight", "disableLineItemHighlight", "hideLegend", "grid", "children", "slots", "slotProps", "skipAnimation", "loading", "highlightedItem", "onHighlightChange", "className", "showToolbar", "brushConfig"];
6
6
  import * as React from 'react';
7
7
  import useId from '@mui/utils/useId';
8
8
  import { DEFAULT_X_AXIS_KEY } from "../constants/index.js";
@@ -39,7 +39,8 @@ export const useLineChartProps = props => {
39
39
  loading,
40
40
  highlightedItem,
41
41
  onHighlightChange,
42
- className
42
+ className,
43
+ brushConfig
43
44
  } = props,
44
45
  other = _objectWithoutPropertiesLoose(props, _excluded);
45
46
  const id = useId();
@@ -68,6 +69,7 @@ export const useLineChartProps = props => {
68
69
  disableAxisListener: slotProps?.tooltip?.trigger !== 'axis' && axisHighlight?.x === 'none' && axisHighlight?.y === 'none',
69
70
  className,
70
71
  skipAnimation,
72
+ brushConfig,
71
73
  plugins: LINE_CHART_PLUGINS
72
74
  });
73
75
  const gridProps = {
@@ -80,6 +80,14 @@ process.env.NODE_ENV !== "production" ? ScatterChart.propTypes = {
80
80
  x: PropTypes.oneOf(['band', 'line', 'none']),
81
81
  y: PropTypes.oneOf(['band', 'line', 'none'])
82
82
  }),
83
+ /**
84
+ * Configuration for the brush interaction.
85
+ */
86
+ brushConfig: PropTypes.shape({
87
+ enabled: PropTypes.bool,
88
+ preventHighlight: PropTypes.bool,
89
+ preventTooltip: PropTypes.bool
90
+ }),
83
91
  children: PropTypes.node,
84
92
  className: PropTypes.string,
85
93
  /**
@@ -5,5 +5,6 @@ import { UseChartHighlightSignature } from "../internals/plugins/featurePlugins/
5
5
  import { ConvertSignaturesIntoPlugins } from "../internals/plugins/models/helpers.js";
6
6
  import { UseChartClosestPointSignature } from "../internals/plugins/featurePlugins/useChartClosestPoint/index.js";
7
7
  import { UseChartKeyboardNavigationSignature } from "../internals/plugins/featurePlugins/useChartKeyboardNavigation/index.js";
8
- export type ScatterChartPluginSignatures = [UseChartZAxisSignature, UseChartCartesianAxisSignature<'scatter'>, UseChartInteractionSignature, UseChartHighlightSignature, UseChartClosestPointSignature, UseChartKeyboardNavigationSignature];
8
+ import { UseChartBrushSignature } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
9
+ export type ScatterChartPluginSignatures = [UseChartZAxisSignature, UseChartBrushSignature, UseChartCartesianAxisSignature<'scatter'>, UseChartInteractionSignature, UseChartHighlightSignature, UseChartClosestPointSignature, UseChartKeyboardNavigationSignature];
9
10
  export declare const SCATTER_CHART_PLUGINS: ConvertSignaturesIntoPlugins<ScatterChartPluginSignatures>;
@@ -4,4 +4,5 @@ import { useChartInteraction } from "../internals/plugins/featurePlugins/useChar
4
4
  import { useChartHighlight } from "../internals/plugins/featurePlugins/useChartHighlight/index.js";
5
5
  import { useChartClosestPoint } from "../internals/plugins/featurePlugins/useChartClosestPoint/index.js";
6
6
  import { useChartKeyboardNavigation } from "../internals/plugins/featurePlugins/useChartKeyboardNavigation/index.js";
7
- export const SCATTER_CHART_PLUGINS = [useChartZAxis, useChartCartesianAxis, useChartInteraction, useChartHighlight, useChartClosestPoint, useChartKeyboardNavigation];
7
+ import { useChartBrush } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
8
+ export const SCATTER_CHART_PLUGINS = [useChartZAxis, useChartBrush, useChartCartesianAxis, useChartInteraction, useChartHighlight, useChartClosestPoint, useChartKeyboardNavigation];
@@ -2,7 +2,7 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
5
- const _excluded = ["xAxis", "yAxis", "zAxis", "series", "axisHighlight", "voronoiMaxRadius", "disableVoronoi", "hideLegend", "width", "height", "margin", "colors", "sx", "grid", "onItemClick", "children", "slots", "slotProps", "loading", "highlightedItem", "onHighlightChange", "className", "showToolbar", "renderer"];
5
+ const _excluded = ["xAxis", "yAxis", "zAxis", "series", "axisHighlight", "voronoiMaxRadius", "disableVoronoi", "hideLegend", "width", "height", "margin", "colors", "sx", "grid", "onItemClick", "children", "slots", "slotProps", "loading", "highlightedItem", "onHighlightChange", "className", "showToolbar", "renderer", "brushConfig"];
6
6
  import * as React from 'react';
7
7
  import { SCATTER_CHART_PLUGINS } from "./ScatterChart.plugins.js";
8
8
  /**
@@ -35,7 +35,8 @@ export const useScatterChartProps = props => {
35
35
  highlightedItem,
36
36
  onHighlightChange,
37
37
  className,
38
- renderer
38
+ renderer,
39
+ brushConfig
39
40
  } = props,
40
41
  other = _objectWithoutPropertiesLoose(props, _excluded);
41
42
  const seriesWithDefault = React.useMemo(() => series.map(s => _extends({
@@ -59,7 +60,8 @@ export const useScatterChartProps = props => {
59
60
  className,
60
61
  plugins: SCATTER_CHART_PLUGINS,
61
62
  slots,
62
- slotProps
63
+ slotProps,
64
+ brushConfig
63
65
  });
64
66
  const chartsAxisProps = {
65
67
  slots,
@@ -179,6 +179,14 @@ process.env.NODE_ENV !== "production" ? SparkLineChart.propTypes = {
179
179
  * @default 0
180
180
  */
181
181
  baseline: PropTypes.oneOfType([PropTypes.oneOf(['max', 'min']), PropTypes.number]),
182
+ /**
183
+ * Configuration for the brush interaction.
184
+ */
185
+ brushConfig: PropTypes.shape({
186
+ enabled: PropTypes.bool,
187
+ preventHighlight: PropTypes.bool,
188
+ preventTooltip: PropTypes.bool
189
+ }),
182
190
  children: PropTypes.node,
183
191
  className: PropTypes.string,
184
192
  /**
@@ -17,4 +17,5 @@ export * from "./useLegend.js";
17
17
  export { useChartGradientId, useChartGradientIdObjectBound } from "./useChartGradientId.js";
18
18
  export * from "./animation/index.js";
19
19
  export * from "./useChartRootRef.js";
20
- export * from "./useChartsLocalization.js";
20
+ export * from "./useChartsLocalization.js";
21
+ export * from "./useBrush.js";
@@ -17,4 +17,5 @@ export * from "./useLegend.js";
17
17
  export { useChartGradientId, useChartGradientIdObjectBound } from "./useChartGradientId.js";
18
18
  export * from "./animation/index.js";
19
19
  export * from "./useChartRootRef.js";
20
- export * from "./useChartsLocalization.js";
20
+ export * from "./useChartsLocalization.js";
21
+ export * from "./useBrush.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Get the current brush state.
3
+ *
4
+ * - `start` is the starting point of the brush selection.
5
+ * - `current` is the current point of the brush selection.
6
+ *
7
+ * @returns `{ start, current }` - The brush state.
8
+ */
9
+ export declare function useBrush(): {
10
+ start: {
11
+ x: number;
12
+ y: number;
13
+ };
14
+ current: {
15
+ x: number;
16
+ y: number;
17
+ };
18
+ } | null;
@@ -0,0 +1,16 @@
1
+ import { selectorBrushState } from "../internals/plugins/featurePlugins/useChartBrush/index.js";
2
+ import { useSelector } from "../internals/store/useSelector.js";
3
+ import { useStore } from "../internals/store/useStore.js";
4
+
5
+ /**
6
+ * Get the current brush state.
7
+ *
8
+ * - `start` is the starting point of the brush selection.
9
+ * - `current` is the current point of the brush selection.
10
+ *
11
+ * @returns `{ start, current }` - The brush state.
12
+ */
13
+ export function useBrush() {
14
+ const store = useStore();
15
+ return useSelector(store, selectorBrushState);
16
+ }
package/esm/index.d.ts CHANGED
@@ -28,4 +28,5 @@ export { ChartContainer } from "./ChartContainer/index.js";
28
28
  export type { ChartContainerProps } from "./ChartContainer/index.js";
29
29
  export * from "./ChartDataProvider/index.js";
30
30
  export * from "./Toolbar/index.js";
31
- export * from "./ChartsWrapper/index.js";
31
+ export * from "./ChartsWrapper/index.js";
32
+ export * from "./ChartsBrushOverlay/index.js";
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-charts v8.14.1
2
+ * @mui/x-charts v8.15.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -35,6 +35,7 @@ export { ChartContainer } from "./ChartContainer/index.js";
35
35
  export * from "./ChartDataProvider/index.js";
36
36
  export * from "./Toolbar/index.js";
37
37
  export * from "./ChartsWrapper/index.js";
38
+ export * from "./ChartsBrushOverlay/index.js";
38
39
 
39
40
  // Locales should be imported from `@mui/x-charts/locales`
40
41
  // export * from './locales';
@@ -1,10 +1,11 @@
1
- export declare const MEASUREMENT_SPAN_ID = "mui_measurement_span";
1
+ import * as React from 'react';
2
+ export declare function clearStringMeasurementCache(): void;
2
3
  /**
3
- *
4
+ * Converts a style object into a string to be used as a cache key
4
5
  * @param style React style object
5
6
  * @returns CSS styling string
6
7
  */
7
- export declare const getStyleString: (style: React.CSSProperties) => string;
8
+ export declare function getStyleString(style: React.CSSProperties): string;
8
9
  /**
9
10
  *
10
11
  * @param text The string to estimate
@@ -14,4 +15,8 @@ export declare const getStyleString: (style: React.CSSProperties) => string;
14
15
  export declare const getStringSize: (text: string | number, style?: React.CSSProperties) => {
15
16
  width: number;
16
17
  height: number;
17
- };
18
+ };
19
+ export declare function batchMeasureStrings(texts: Iterable<string | number>, style?: React.CSSProperties): Map<string | number, {
20
+ width: number;
21
+ height: number;
22
+ }>;
@@ -1,60 +1,58 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
- // DOM utils taken from
2
+ // DOM utils adapted from
3
3
  // https://github.com/recharts/recharts/blob/master/src/util/DOMUtils.ts
4
4
 
5
5
  function isSsr() {
6
6
  return typeof window === 'undefined';
7
7
  }
8
8
  const stringCache = new Map();
9
+ export function clearStringMeasurementCache() {
10
+ stringCache.clear();
11
+ }
9
12
  const MAX_CACHE_NUM = 2000;
10
- const SPAN_STYLE = {
11
- position: 'absolute',
12
- top: '-20000px',
13
- left: 0,
14
- padding: 0,
15
- margin: 0,
16
- border: 'none',
17
- whiteSpace: 'pre'
18
- };
19
- const STYLE_LIST = ['minWidth', 'maxWidth', 'width', 'minHeight', 'maxHeight', 'height', 'top', 'left', 'fontSize', 'padding', 'margin', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom'];
20
- export const MEASUREMENT_SPAN_ID = 'mui_measurement_span';
13
+ const PIXEL_STYLES = new Set(['minWidth', 'maxWidth', 'width', 'minHeight', 'maxHeight', 'height', 'top', 'left', 'fontSize', 'padding', 'margin', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom']);
21
14
 
22
15
  /**
23
- *
16
+ * Convert number value to pixel value for certain CSS properties
24
17
  * @param name CSS property name
25
18
  * @param value
26
19
  * @returns add 'px' for distance properties
27
20
  */
28
- function autoCompleteStyle(name, value) {
29
- if (STYLE_LIST.indexOf(name) >= 0 && value === +value) {
21
+ function convertPixelValue(name, value) {
22
+ if (PIXEL_STYLES.has(name) && value === +value) {
30
23
  return `${value}px`;
31
24
  }
32
25
  return value;
33
26
  }
34
27
 
35
28
  /**
36
- *
29
+ * Converts camelcase to dash-case
37
30
  * @param text camelcase css property
38
- * @returns css property
39
31
  */
40
- function camelToMiddleLine(text) {
41
- const strs = text.split('');
42
- const formatStrs = strs.reduce((result, entry) => {
43
- if (entry === entry.toUpperCase()) {
44
- return [...result, '-', entry.toLowerCase()];
45
- }
46
- return [...result, entry];
47
- }, []);
48
- return formatStrs.join('');
32
+ const AZ = /([A-Z])/g;
33
+ function camelCaseToDashCase(text) {
34
+ return String(text).replace(AZ, match => `-${match.toLowerCase()}`);
49
35
  }
50
36
 
51
37
  /**
52
- *
38
+ * Converts a style object into a string to be used as a cache key
53
39
  * @param style React style object
54
40
  * @returns CSS styling string
55
41
  */
56
- export const getStyleString = style => Object.keys(style).sort().reduce((result, s) => `${result}${camelToMiddleLine(s)}:${autoCompleteStyle(s, style[s])};`, '');
57
- let domCleanTimeout;
42
+ export function getStyleString(style) {
43
+ let result = '';
44
+ for (const key in style) {
45
+ if (Object.hasOwn(style, key)) {
46
+ const k = key;
47
+ const value = style[k];
48
+ if (value === undefined) {
49
+ continue;
50
+ }
51
+ result += `${camelCaseToDashCase(k)}:${convertPixelValue(k, value)};`;
52
+ }
53
+ }
54
+ return result;
55
+ }
58
56
 
59
57
  /**
60
58
  *
@@ -69,7 +67,7 @@ export const getStringSize = (text, style = {}) => {
69
67
  height: 0
70
68
  };
71
69
  }
72
- const str = `${text}`;
70
+ const str = String(text);
73
71
  const styleString = getStyleString(style);
74
72
  const cacheKey = `${str}-${styleString}`;
75
73
  const size = stringCache.get(cacheKey);
@@ -77,22 +75,18 @@ export const getStringSize = (text, style = {}) => {
77
75
  return size;
78
76
  }
79
77
  try {
80
- let measurementSpan = document.getElementById(MEASUREMENT_SPAN_ID);
81
- if (measurementSpan === null) {
82
- measurementSpan = document.createElement('span');
83
- measurementSpan.setAttribute('id', MEASUREMENT_SPAN_ID);
84
- measurementSpan.setAttribute('aria-hidden', 'true');
85
- document.body.appendChild(measurementSpan);
86
- }
78
+ const measurementSpanContainer = getMeasurementContainer();
79
+ const measurementElem = document.createElementNS('http://www.w3.org/2000/svg', 'text');
80
+
87
81
  // Need to use CSS Object Model (CSSOM) to be able to comply with Content Security Policy (CSP)
88
82
  // https://en.wikipedia.org/wiki/Content_Security_Policy
89
- const measurementSpanStyle = _extends({}, SPAN_STYLE, style);
90
- Object.keys(measurementSpanStyle).map(styleKey => {
91
- measurementSpan.style[camelToMiddleLine(styleKey)] = autoCompleteStyle(styleKey, measurementSpanStyle[styleKey]);
83
+ Object.keys(style).map(styleKey => {
84
+ measurementElem.style[camelCaseToDashCase(styleKey)] = convertPixelValue(styleKey, style[styleKey]);
92
85
  return styleKey;
93
86
  });
94
- measurementSpan.textContent = str;
95
- const rect = measurementSpan.getBoundingClientRect();
87
+ measurementElem.textContent = str;
88
+ measurementSpanContainer.replaceChildren(measurementElem);
89
+ const rect = measurementElem.getBoundingClientRect();
96
90
  const result = {
97
91
  width: rect.width,
98
92
  height: rect.height
@@ -103,15 +97,7 @@ export const getStringSize = (text, style = {}) => {
103
97
  }
104
98
  if (process.env.NODE_ENV === 'test') {
105
99
  // In test environment, we clean the measurement span immediately
106
- measurementSpan.textContent = '';
107
- } else {
108
- if (domCleanTimeout) {
109
- clearTimeout(domCleanTimeout);
110
- }
111
- domCleanTimeout = setTimeout(() => {
112
- // Limit node cleaning to once per render cycle
113
- measurementSpan.textContent = '';
114
- }, 0);
100
+ measurementSpanContainer.replaceChildren();
115
101
  }
116
102
  return result;
117
103
  } catch {
@@ -120,4 +106,81 @@ export const getStringSize = (text, style = {}) => {
120
106
  height: 0
121
107
  };
122
108
  }
123
- };
109
+ };
110
+ export function batchMeasureStrings(texts, style = {}) {
111
+ if (isSsr()) {
112
+ return new Map(Array.from(texts).map(text => [text, {
113
+ width: 0,
114
+ height: 0
115
+ }]));
116
+ }
117
+ const sizeMap = new Map();
118
+ const textToMeasure = [];
119
+ const styleString = getStyleString(style);
120
+ for (const text of texts) {
121
+ const cacheKey = `${text}-${styleString}`;
122
+ const size = stringCache.get(cacheKey);
123
+ if (size) {
124
+ sizeMap.set(text, size);
125
+ } else {
126
+ textToMeasure.push(text);
127
+ }
128
+ }
129
+ const measurementContainer = getMeasurementContainer();
130
+ // Need to use CSS Object Model (CSSOM) to be able to comply with Content Security Policy (CSP)
131
+ // https://en.wikipedia.org/wiki/Content_Security_Policy
132
+ const measurementSpanStyle = _extends({}, style);
133
+ Object.keys(measurementSpanStyle).map(styleKey => {
134
+ measurementContainer.style[camelCaseToDashCase(styleKey)] = convertPixelValue(styleKey, measurementSpanStyle[styleKey]);
135
+ return styleKey;
136
+ });
137
+ const measurementElems = [];
138
+ for (const string of textToMeasure) {
139
+ const measurementElem = document.createElementNS('http://www.w3.org/2000/svg', 'text');
140
+ measurementElem.textContent = `${string}`;
141
+ measurementElems.push(measurementElem);
142
+ }
143
+ measurementContainer.replaceChildren(...measurementElems);
144
+ for (let i = 0; i < textToMeasure.length; i += 1) {
145
+ const text = textToMeasure[i];
146
+ const measurementSpan = measurementContainer.children[i];
147
+ const rect = measurementSpan.getBoundingClientRect();
148
+ const result = {
149
+ width: rect.width,
150
+ height: rect.height
151
+ };
152
+ const cacheKey = `${text}-${styleString}`;
153
+ stringCache.set(cacheKey, result);
154
+ sizeMap.set(text, result);
155
+ }
156
+ if (stringCache.size + 1 > MAX_CACHE_NUM) {
157
+ stringCache.clear();
158
+ }
159
+ if (process.env.NODE_ENV === 'test') {
160
+ // In test environment, we clean the measurement span immediately
161
+ measurementContainer.replaceChildren();
162
+ }
163
+ return sizeMap;
164
+ }
165
+ let measurementContainer = null;
166
+
167
+ /**
168
+ * Get (or create) a hidden span element to measure text size.
169
+ */
170
+ function getMeasurementContainer() {
171
+ if (measurementContainer === null) {
172
+ measurementContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
173
+ measurementContainer.setAttribute('aria-hidden', 'true');
174
+ measurementContainer.style.position = 'absolute';
175
+ measurementContainer.style.top = '-20000px';
176
+ measurementContainer.style.left = '0';
177
+ measurementContainer.style.padding = '0';
178
+ measurementContainer.style.margin = '0';
179
+ measurementContainer.style.border = 'none';
180
+ measurementContainer.style.pointerEvents = 'none';
181
+ measurementContainer.style.visibility = 'hidden';
182
+ measurementContainer.style.contain = 'strict';
183
+ document.body.appendChild(measurementContainer);
184
+ }
185
+ return measurementContainer;
186
+ }