diff --git a/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.js b/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.js
new file mode 100644
index 0000000000000..1fe5ac62c302f
--- /dev/null
+++ b/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.js
@@ -0,0 +1,181 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Slider from '@mui/material/Slider';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Switch from '@mui/material/Switch';
+import Typography from '@mui/material/Typography';
+import { Chance } from 'chance';
+import { BarChart } from '@mui/x-charts/BarChart';
+
+const chance = new Chance(42);
+
+const produce = [
+ 'Apple',
+ 'Apricot',
+ 'Artichoke',
+ 'Arugula',
+ 'Asparagus',
+ 'Avocado',
+ 'Banana',
+ 'Beet',
+ 'Bell pepper',
+ 'Blackberry',
+ 'Blueberry',
+ 'Bok choy',
+ 'Broccoli',
+ 'Brussels sprout',
+ 'Cabbage',
+ 'Cantaloupe',
+ 'Carrot',
+ 'Cauliflower',
+ 'Celery',
+ 'Cherry',
+ 'Chickpea',
+ 'Chili pepper',
+ 'Clementine',
+ 'Coconut',
+ 'Collard greens',
+ 'Corn',
+ 'Cranberry',
+ 'Cucumber',
+ 'Currant',
+ 'Daikon',
+ 'Date',
+ 'Dragonfruit',
+ 'Durian',
+ 'Edamame',
+ 'Eggplant',
+ 'Elderberry',
+ 'Endive (red)',
+ 'Endive',
+ 'Fennel',
+ 'Fig',
+ 'Garlic',
+ 'Ginger',
+ 'Gooseberry',
+ 'Grape',
+ 'Grapefruit',
+ 'Green bean',
+ 'Guava',
+ 'Honeydew',
+ 'Jackfruit',
+ 'Jalapeño',
+ 'Jicama',
+ 'Kale',
+ 'Kiwi',
+ 'Kohlrabi',
+ 'Kumquat',
+ 'Leek',
+ 'Lemon',
+ 'Lentil',
+ 'Lettuce',
+ 'Lime',
+ 'Lychee',
+ 'Mandarin',
+ 'Mango',
+ 'Mizuna',
+ 'Mulberry',
+ 'Mushroom',
+ 'Mustard greens',
+ 'Nectarine',
+ 'Okra',
+ 'Olive',
+ 'Onion',
+ 'Orange',
+ 'Papaya',
+ 'Parsnip',
+ 'Passionfruit',
+ 'Pea',
+ 'Peach',
+ 'Pear',
+ 'Persian lime',
+ 'Persimmon',
+ 'Pineapple',
+ 'Plantain',
+ 'Plum',
+ 'Pomegranate',
+ 'Pomelo',
+ 'Potato',
+ 'Pumpkin',
+ 'Quince',
+ 'Radicchio',
+ 'Radish',
+ 'Raisin',
+ 'Rambutan',
+ 'Raspberry',
+ 'Redcurrant',
+ 'Rhubarb',
+ 'Rutabaga',
+ 'Salak',
+ 'Salsify',
+ 'Scallion',
+ 'Shallot',
+ 'Snow pea',
+ 'Soursop',
+ 'Spinach',
+ 'Squash',
+ 'Starfruit',
+ 'Strawberry',
+ 'Sweet potato',
+ 'Swiss chard',
+ 'Tamarind',
+ 'Tangerine',
+ 'Taro',
+ 'Tatsoi',
+ 'Tomato',
+ 'Turnip',
+ 'Ugli',
+ 'Watercress',
+ 'Watermelon',
+ 'Yam',
+ 'Yuzu',
+ 'Zucchini',
+];
+
+const dataset = produce.map((name) => ({
+ item: name,
+ sales: chance.integer({ min: 100, max: 1000 }),
+}));
+
+export default function ResponsiveTickAdjustment() {
+ const [widthPct, setWidthPct] = React.useState(40);
+ const [enabled, setEnabled] = React.useState(true);
+
+ return (
+
+
+
+ Chart width: {widthPct}%
+
+ setWidthPct(value)}
+ valueLabelDisplay="auto"
+ min={20}
+ max={100}
+ step={5}
+ aria-labelledby="chart-width"
+ />
+ setEnabled(event.target.checked)}
+ />
+ }
+ label="experimentalFeatures.useNewDefaultTickSpacing"
+ />
+
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.tsx b/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.tsx
new file mode 100644
index 0000000000000..a96a6a832a315
--- /dev/null
+++ b/docs/data/charts/axis-ticks/ResponsiveTickAdjustment.tsx
@@ -0,0 +1,181 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Slider from '@mui/material/Slider';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Switch from '@mui/material/Switch';
+import Typography from '@mui/material/Typography';
+import { Chance } from 'chance';
+import { BarChart } from '@mui/x-charts/BarChart';
+
+const chance = new Chance(42);
+
+const produce = [
+ 'Apple',
+ 'Apricot',
+ 'Artichoke',
+ 'Arugula',
+ 'Asparagus',
+ 'Avocado',
+ 'Banana',
+ 'Beet',
+ 'Bell pepper',
+ 'Blackberry',
+ 'Blueberry',
+ 'Bok choy',
+ 'Broccoli',
+ 'Brussels sprout',
+ 'Cabbage',
+ 'Cantaloupe',
+ 'Carrot',
+ 'Cauliflower',
+ 'Celery',
+ 'Cherry',
+ 'Chickpea',
+ 'Chili pepper',
+ 'Clementine',
+ 'Coconut',
+ 'Collard greens',
+ 'Corn',
+ 'Cranberry',
+ 'Cucumber',
+ 'Currant',
+ 'Daikon',
+ 'Date',
+ 'Dragonfruit',
+ 'Durian',
+ 'Edamame',
+ 'Eggplant',
+ 'Elderberry',
+ 'Endive (red)',
+ 'Endive',
+ 'Fennel',
+ 'Fig',
+ 'Garlic',
+ 'Ginger',
+ 'Gooseberry',
+ 'Grape',
+ 'Grapefruit',
+ 'Green bean',
+ 'Guava',
+ 'Honeydew',
+ 'Jackfruit',
+ 'Jalapeño',
+ 'Jicama',
+ 'Kale',
+ 'Kiwi',
+ 'Kohlrabi',
+ 'Kumquat',
+ 'Leek',
+ 'Lemon',
+ 'Lentil',
+ 'Lettuce',
+ 'Lime',
+ 'Lychee',
+ 'Mandarin',
+ 'Mango',
+ 'Mizuna',
+ 'Mulberry',
+ 'Mushroom',
+ 'Mustard greens',
+ 'Nectarine',
+ 'Okra',
+ 'Olive',
+ 'Onion',
+ 'Orange',
+ 'Papaya',
+ 'Parsnip',
+ 'Passionfruit',
+ 'Pea',
+ 'Peach',
+ 'Pear',
+ 'Persian lime',
+ 'Persimmon',
+ 'Pineapple',
+ 'Plantain',
+ 'Plum',
+ 'Pomegranate',
+ 'Pomelo',
+ 'Potato',
+ 'Pumpkin',
+ 'Quince',
+ 'Radicchio',
+ 'Radish',
+ 'Raisin',
+ 'Rambutan',
+ 'Raspberry',
+ 'Redcurrant',
+ 'Rhubarb',
+ 'Rutabaga',
+ 'Salak',
+ 'Salsify',
+ 'Scallion',
+ 'Shallot',
+ 'Snow pea',
+ 'Soursop',
+ 'Spinach',
+ 'Squash',
+ 'Starfruit',
+ 'Strawberry',
+ 'Sweet potato',
+ 'Swiss chard',
+ 'Tamarind',
+ 'Tangerine',
+ 'Taro',
+ 'Tatsoi',
+ 'Tomato',
+ 'Turnip',
+ 'Ugli',
+ 'Watercress',
+ 'Watermelon',
+ 'Yam',
+ 'Yuzu',
+ 'Zucchini',
+];
+
+const dataset = produce.map((name) => ({
+ item: name,
+ sales: chance.integer({ min: 100, max: 1000 }),
+}));
+
+export default function ResponsiveTickAdjustment() {
+ const [widthPct, setWidthPct] = React.useState(40);
+ const [enabled, setEnabled] = React.useState(true);
+
+ return (
+
+
+
+ Chart width: {widthPct}%
+
+ setWidthPct(value as number)}
+ valueLabelDisplay="auto"
+ min={20}
+ max={100}
+ step={5}
+ aria-labelledby="chart-width"
+ />
+ setEnabled(event.target.checked)}
+ />
+ }
+ label="experimentalFeatures.useNewDefaultTickSpacing"
+ />
+
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/axis-ticks/axis-ticks.md b/docs/data/charts/axis-ticks/axis-ticks.md
index 33be43d833fc5..1ecedf3b39f86 100644
--- a/docs/data/charts/axis-ticks/axis-ticks.md
+++ b/docs/data/charts/axis-ticks/axis-ticks.md
@@ -56,6 +56,28 @@ This property defaults to 0 and is only available for ordinal axes, that is, axe
{{"demo": "TickSpacing.js"}}
+### Responsive tick adjustment
+
+:::warning
+This feature is experimental—not because its behavior is unstable, but because enabling it by default would change the look of existing charts. It's opt-in for now and is expected to become the default in a future major release.
+:::
+
+Enable `useNewDefaultTickSpacing` on any cartesian chart (BarChart, LineChart, ScatterChart, Heatmap, and their Pro/Premium variants) to let ordinal axes (`band` and `point` scales) thin out ticks automatically based on the chart's rendered size, so labels don't pile up on narrow charts.
+
+The feature applies a 50-pixel default `tickSpacing` derived from the drawing area. It never overrides an explicit `tickSpacing`, `tickNumber`, or `tickInterval` set by your code—continuous axes are already size-aware through their default `tickNumber`.
+
+```jsx
+
+```
+
+Drag the slider in the demo below to shrink the chart and toggle the feature to see ticks adapt.
+
+{{"demo": "ResponsiveTickAdjustment.js"}}
+
### Fixed tick position
If you want more control over the tick position, you can use the `tickInterval` property.
diff --git a/docs/pages/x/api/charts/bar-chart-premium.json b/docs/pages/x/api/charts/bar-chart-premium.json
index 70a4513d4a223..34e9c7331d983 100644
--- a/docs/pages/x/api/charts/bar-chart-premium.json
+++ b/docs/pages/x/api/charts/bar-chart-premium.json
@@ -30,7 +30,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/bar-chart-pro.json b/docs/pages/x/api/charts/bar-chart-pro.json
index ad8b2ef1bcb74..2fbd8b40aab9e 100644
--- a/docs/pages/x/api/charts/bar-chart-pro.json
+++ b/docs/pages/x/api/charts/bar-chart-pro.json
@@ -30,7 +30,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/bar-chart.json b/docs/pages/x/api/charts/bar-chart.json
index de827ff2e1618..69ac53bddcbbb 100644
--- a/docs/pages/x/api/charts/bar-chart.json
+++ b/docs/pages/x/api/charts/bar-chart.json
@@ -30,7 +30,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/funnel-chart.json b/docs/pages/x/api/charts/funnel-chart.json
index 829ed7f77fa26..4cc952fd4ac22 100644
--- a/docs/pages/x/api/charts/funnel-chart.json
+++ b/docs/pages/x/api/charts/funnel-chart.json
@@ -28,7 +28,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"gap": { "type": { "name": "number" }, "default": "0" },
"height": { "type": { "name": "number" } },
"hiddenItems": {
diff --git a/docs/pages/x/api/charts/heatmap-premium.json b/docs/pages/x/api/charts/heatmap-premium.json
index dac3d27327dd3..ec19e4e877450 100644
--- a/docs/pages/x/api/charts/heatmap-premium.json
+++ b/docs/pages/x/api/charts/heatmap-premium.json
@@ -27,7 +27,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"height": { "type": { "name": "number" } },
"hideLegend": { "type": { "name": "bool" } },
"highlightedItem": {
diff --git a/docs/pages/x/api/charts/heatmap.json b/docs/pages/x/api/charts/heatmap.json
index 42b154c233248..19e471df621ed 100644
--- a/docs/pages/x/api/charts/heatmap.json
+++ b/docs/pages/x/api/charts/heatmap.json
@@ -27,7 +27,9 @@
"desc": { "type": { "name": "string" } },
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"height": { "type": { "name": "number" } },
"hideLegend": { "type": { "name": "bool" } },
"highlightedItem": {
diff --git a/docs/pages/x/api/charts/line-chart-pro.json b/docs/pages/x/api/charts/line-chart-pro.json
index fe2c7e2989a4b..50482499f77ab 100644
--- a/docs/pages/x/api/charts/line-chart-pro.json
+++ b/docs/pages/x/api/charts/line-chart-pro.json
@@ -32,7 +32,10 @@
"disableKeyboardNavigation": { "type": { "name": "bool" } },
"disableLineItemHighlight": { "type": { "name": "bool" } },
"experimentalFeatures": {
- "type": { "name": "shape", "description": "{ enablePositionBasedPointerInteraction?: bool }" }
+ "type": {
+ "name": "shape",
+ "description": "{ enablePositionBasedPointerInteraction?: bool, useNewDefaultTickSpacing?: bool }"
+ }
},
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
diff --git a/docs/pages/x/api/charts/line-chart.json b/docs/pages/x/api/charts/line-chart.json
index 7048ba59719da..04e41c163b83f 100644
--- a/docs/pages/x/api/charts/line-chart.json
+++ b/docs/pages/x/api/charts/line-chart.json
@@ -32,7 +32,10 @@
"disableKeyboardNavigation": { "type": { "name": "bool" } },
"disableLineItemHighlight": { "type": { "name": "bool" } },
"experimentalFeatures": {
- "type": { "name": "shape", "description": "{ enablePositionBasedPointerInteraction?: bool }" }
+ "type": {
+ "name": "shape",
+ "description": "{ enablePositionBasedPointerInteraction?: bool, useNewDefaultTickSpacing?: bool }"
+ }
},
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
diff --git a/docs/pages/x/api/charts/scatter-chart-premium.json b/docs/pages/x/api/charts/scatter-chart-premium.json
index 223450df24bc0..f74d546be856b 100644
--- a/docs/pages/x/api/charts/scatter-chart-premium.json
+++ b/docs/pages/x/api/charts/scatter-chart-premium.json
@@ -43,7 +43,9 @@
"deprecated": true,
"deprecationInfo": "Use disableHitArea instead."
},
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/scatter-chart-pro.json b/docs/pages/x/api/charts/scatter-chart-pro.json
index c321b85f7af71..560535d00aada 100644
--- a/docs/pages/x/api/charts/scatter-chart-pro.json
+++ b/docs/pages/x/api/charts/scatter-chart-pro.json
@@ -43,7 +43,9 @@
"deprecated": true,
"deprecationInfo": "Use disableHitArea instead."
},
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/scatter-chart.json b/docs/pages/x/api/charts/scatter-chart.json
index e97a56c7fe3de..738e70327f18a 100644
--- a/docs/pages/x/api/charts/scatter-chart.json
+++ b/docs/pages/x/api/charts/scatter-chart.json
@@ -43,7 +43,9 @@
"deprecated": true,
"deprecationInfo": "Use disableHitArea instead."
},
- "experimentalFeatures": { "type": { "name": "object" } },
+ "experimentalFeatures": {
+ "type": { "name": "shape", "description": "{ useNewDefaultTickSpacing?: bool }" }
+ },
"grid": {
"type": { "name": "shape", "description": "{ horizontal?: bool, vertical?: bool }" }
},
diff --git a/docs/pages/x/api/charts/spark-line-chart.json b/docs/pages/x/api/charts/spark-line-chart.json
index 1c5167129c6e8..83e6bec4e4ca3 100644
--- a/docs/pages/x/api/charts/spark-line-chart.json
+++ b/docs/pages/x/api/charts/spark-line-chart.json
@@ -36,7 +36,10 @@
"disableHitArea": { "type": { "name": "bool" } },
"disableKeyboardNavigation": { "type": { "name": "bool" } },
"experimentalFeatures": {
- "type": { "name": "shape", "description": "{ enablePositionBasedPointerInteraction?: bool }" }
+ "type": {
+ "name": "shape",
+ "description": "{ enablePositionBasedPointerInteraction?: bool, useNewDefaultTickSpacing?: bool }"
+ }
},
"height": { "type": { "name": "number" } },
"hiddenItems": {
diff --git a/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx b/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx
index fa451c09046de..b9bfb6bbb78a2 100644
--- a/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx
+++ b/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx
@@ -240,7 +240,9 @@ BarChartPremium.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx b/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx
index a9ea8840aa4ac..4bb38a320495c 100644
--- a/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx
+++ b/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx
@@ -144,7 +144,9 @@ HeatmapPremium.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* The height of the chart in px. If not defined, it takes the height of the parent element.
*/
diff --git a/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx b/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx
index ed17bea4d8c7a..b0fe8490c68b4 100644
--- a/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx
+++ b/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx
@@ -246,7 +246,9 @@ ScatterChartPremium.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
index 1e478247c9999..c378e1df2aabf 100644
--- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
+++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
@@ -197,7 +197,9 @@ BarChartPro.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx b/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx
index cd05f95517236..5ca3c44fba2d4 100644
--- a/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx
+++ b/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx
@@ -258,7 +258,9 @@ FunnelChart.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* The gap, in pixels, between funnel sections.
* @default 0
diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
index 1d88b66a2474f..255c55810c880 100644
--- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
+++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
@@ -239,7 +239,9 @@ Heatmap.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* The height of the chart in px. If not defined, it takes the height of the parent element.
*/
diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
index 911a6d78ea5ae..75ee941eea812 100644
--- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
+++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
@@ -208,6 +208,7 @@ LineChartPro.propTypes = {
*/
experimentalFeatures: PropTypes.shape({
enablePositionBasedPointerInteraction: PropTypes.bool,
+ useNewDefaultTickSpacing: PropTypes.bool,
}),
/**
* Option to display a cartesian grid in the background.
diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
index aaac4e6962f73..65adb26aaa28b 100644
--- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
+++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
@@ -221,7 +221,9 @@ ScatterChartPro.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx
index 99d2274822558..701a485b64e6a 100644
--- a/packages/x-charts/src/BarChart/BarChart.tsx
+++ b/packages/x-charts/src/BarChart/BarChart.tsx
@@ -224,7 +224,9 @@ BarChart.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts/src/LineChart/AreaElement.tsx b/packages/x-charts/src/LineChart/AreaElement.tsx
index 91f9e185b5965..534889f8c40e7 100644
--- a/packages/x-charts/src/LineChart/AreaElement.tsx
+++ b/packages/x-charts/src/LineChart/AreaElement.tsx
@@ -76,7 +76,8 @@ function AreaElement(props: AreaElementProps) {
const store = useStore();
const enablePositionBasedPointerInteraction = store.use(
selectorChartExperimentalFeaturesState,
- )?.enablePositionBasedPointerInteraction;
+ 'enablePositionBasedPointerInteraction',
+ );
const identifier = React.useMemo(() => ({ type: 'line' as const, seriesId }), [seriesId]);
const interactionProps = useInteractionItemProps(identifier);
const highlightState = useItemHighlightState(identifier);
diff --git a/packages/x-charts/src/LineChart/CircleMarkElement.tsx b/packages/x-charts/src/LineChart/CircleMarkElement.tsx
index 327e41353c2a8..880652211851b 100644
--- a/packages/x-charts/src/LineChart/CircleMarkElement.tsx
+++ b/packages/x-charts/src/LineChart/CircleMarkElement.tsx
@@ -84,7 +84,8 @@ function CircleMarkElement(props: CircleMarkElementProps) {
const store = useStore();
const enablePositionBasedPointerInteraction = store.use(
selectorChartExperimentalFeaturesState,
- )?.enablePositionBasedPointerInteraction;
+ 'enablePositionBasedPointerInteraction',
+ );
const interactionProps = useInteractionItemProps({ type: 'line', seriesId, dataIndex });
const theme = useTheme();
diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx
index 3d69c78e5ffe2..cb1542acfd812 100644
--- a/packages/x-charts/src/LineChart/LineChart.tsx
+++ b/packages/x-charts/src/LineChart/LineChart.tsx
@@ -273,6 +273,7 @@ LineChart.propTypes = {
*/
experimentalFeatures: PropTypes.shape({
enablePositionBasedPointerInteraction: PropTypes.bool,
+ useNewDefaultTickSpacing: PropTypes.bool,
}),
/**
* Option to display a cartesian grid in the background.
diff --git a/packages/x-charts/src/LineChart/LineElement.tsx b/packages/x-charts/src/LineChart/LineElement.tsx
index e1d6e7c01e663..bbed68f473f86 100644
--- a/packages/x-charts/src/LineChart/LineElement.tsx
+++ b/packages/x-charts/src/LineChart/LineElement.tsx
@@ -81,7 +81,8 @@ function LineElement(props: LineElementProps) {
const store = useStore();
const enablePositionBasedPointerInteraction = store.use(
selectorChartExperimentalFeaturesState,
- )?.enablePositionBasedPointerInteraction;
+ 'enablePositionBasedPointerInteraction',
+ );
const identifier = React.useMemo(() => ({ type: 'line' as const, seriesId }), [seriesId]);
const interactionProps = useInteractionItemProps(identifier);
diff --git a/packages/x-charts/src/LineChart/MarkElement.tsx b/packages/x-charts/src/LineChart/MarkElement.tsx
index 5419c9bf75d51..7751df50ac19f 100644
--- a/packages/x-charts/src/LineChart/MarkElement.tsx
+++ b/packages/x-charts/src/LineChart/MarkElement.tsx
@@ -89,7 +89,8 @@ function MarkElement(props: MarkElementProps) {
const store = useStore();
const enablePositionBasedPointerInteraction = store.use(
selectorChartExperimentalFeaturesState,
- )?.enablePositionBasedPointerInteraction;
+ 'enablePositionBasedPointerInteraction',
+ );
const interactionProps = useInteractionItemProps({ type: 'line', seriesId, dataIndex });
const ownerState = {
diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
index 1a670d6bc51e0..0fbf527063885 100644
--- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx
+++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
@@ -265,7 +265,9 @@ ScatterChart.propTypes = {
/**
* Options to enable features planned for the next major.
*/
- experimentalFeatures: PropTypes.object,
+ experimentalFeatures: PropTypes.shape({
+ useNewDefaultTickSpacing: PropTypes.bool,
+ }),
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
index 462d88924a492..8cb9e9a08e72c 100644
--- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
+++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
@@ -430,6 +430,7 @@ SparkLineChart.propTypes = {
*/
experimentalFeatures: PropTypes.shape({
enablePositionBasedPointerInteraction: PropTypes.bool,
+ useNewDefaultTickSpacing: PropTypes.bool,
}),
/**
* The height of the chart in px. If not defined, it takes the height of the parent element.
diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.selectors.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.selectors.ts
index 268d38f8e8ae3..139c67ca98752 100644
--- a/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.selectors.ts
+++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.selectors.ts
@@ -1,7 +1,20 @@
import type { ChartSeriesType } from '../../../../models/seriesType/config';
-import { type ChartRootSelector } from '../../utils/selectors';
-import type { UseChartExperimentalFeaturesSignature } from './useChartExperimentalFeature.types';
+import { type ChartState } from '../../models/chart';
+import type {
+ ChartExperimentalFeatures,
+ UseChartExperimentalFeaturesSignature,
+} from './useChartExperimentalFeature.types';
-export const selectorChartExperimentalFeaturesState: ChartRootSelector<
- UseChartExperimentalFeaturesSignature
-> = (state) => state.experimentalFeatures;
+/**
+ * Reads the value of a single experimental feature flag from the store.
+ *
+ * @example
+ * const enabled = store.use(
+ * selectorChartExperimentalFeaturesState,
+ * 'useNewDefaultTickSpacing',
+ * );
+ */
+export const selectorChartExperimentalFeaturesState = (
+ state: ChartState<[UseChartExperimentalFeaturesSignature]>,
+ featureName: K,
+): ChartExperimentalFeatures[K] | undefined => state.experimentalFeatures?.[featureName];
diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.ts
index a60a39fb73b1d..05a5fac7110cf 100644
--- a/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.ts
+++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.ts
@@ -1,5 +1,8 @@
import { type ChartPluginSignature } from '../../models';
-import type { ChartSeriesType } from '../../../../models/seriesType/config';
+import type {
+ CartesianChartSeriesType,
+ ChartSeriesType,
+} from '../../../../models/seriesType/config';
interface LineExperimentalFeatures {
/**
@@ -15,8 +18,27 @@ interface LineExperimentalFeatures {
enablePositionBasedPointerInteraction?: boolean;
}
+interface CartesianExperimentalFeatures {
+ /**
+ * Automatically reduces the number of ticks and tick labels on ordinal axes
+ * (`band` / `point` scales) based on the rendered drawing area size.
+ *
+ * Explicit `tickNumber`, `tickSpacing`, or `tickInterval` values set by the
+ * consumer are not overridden.
+ */
+ useNewDefaultTickSpacing?: boolean;
+}
+
+/* True if any cartesian series (which can have `band` / `point` scales) is in `SeriesType`. */
+type HasCartesianSeries = [
+ Extract,
+] extends [never]
+ ? false
+ : true;
+
export type ChartExperimentalFeatures =
- 'line' extends SeriesType ? LineExperimentalFeatures : {};
+ ('line' extends SeriesType ? LineExperimentalFeatures : {}) &
+ (HasCartesianSeries extends true ? CartesianExperimentalFeatures : {});
export interface UseChartExperimentalFeaturesParameters<
SeriesType extends ChartSeriesType = ChartSeriesType,
diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisValue.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisValue.ts
index a5999e6335fc4..f41f1ee1a5604 100644
--- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisValue.ts
+++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisValue.ts
@@ -81,6 +81,9 @@ export function resolveAxisSize(
const DEFAULT_CATEGORY_GAP_RATIO = 0.2;
const DEFAULT_BAR_GAP_RATIO = 0.1;
+/* Matches the 50px-per-tick heuristic used by `getDefaultTickNumber` for continuous axes. */
+const RESPONSIVE_ORDINAL_TICK_SPACING = 50;
+
export type ComputeResult = {
axis: ComputedAxisConfig;
axisIds: AxisId[];
@@ -101,6 +104,8 @@ type ComputeCommonParams =
>;
autoSizes?: Record;
axesGap?: number;
+ /* When true, ordinal axes without an explicit `tickSpacing` get a size-aware default. */
+ responsiveTickAdjustment?: boolean;
};
/**
@@ -166,6 +171,7 @@ export function computeAxisValue({
domains,
autoSizes,
axesGap = 0,
+ responsiveTickAdjustment = false,
}: ComputeCommonParams & {
axis?: DefaultedAxis[];
axisDirection: 'x' | 'y';
@@ -206,6 +212,10 @@ export function computeAxisValue({
if (isOrdinalScale(scale)) {
const scaleRange = axisDirection === 'y' ? [range[1], range[0]] : range;
+ const effectiveTickSpacing =
+ axis.tickSpacing ??
+ (responsiveTickAdjustment ? RESPONSIVE_ORDINAL_TICK_SPACING : undefined);
+
if (isBandScale(scale) && isBandScaleConfig(axis)) {
const desiredCategoryGapRatio = axis.categoryGapRatio ?? DEFAULT_CATEGORY_GAP_RATIO;
const ignoreGapRatios = shouldIgnoreGapRatios(scale, desiredCategoryGapRatio);
@@ -227,6 +237,7 @@ export function computeAxisValue({
* discrepancy will hopefully not be noticeable. */
scale: ignoreGapRatios ? scale.copy().padding(0) : scale,
tickNumber,
+ tickSpacing: effectiveTickSpacing,
colorScale:
axis.colorMap &&
(axis.colorMap.type === 'ordinal'
@@ -244,6 +255,7 @@ export function computeAxisValue({
data,
scale,
tickNumber,
+ tickSpacing: effectiveTickSpacing,
colorScale:
axis.colorMap &&
(axis.colorMap.type === 'ordinal'
diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.test.tsx b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.test.tsx
index 4c0955e5a2710..e0f6e33baabbc 100644
--- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.test.tsx
+++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.test.tsx
@@ -1,9 +1,90 @@
-import { createRenderer } from '@mui/internal-test-utils';
+import { createRenderer, screen } from '@mui/internal-test-utils';
import { BarChart } from '@mui/x-charts/BarChart';
+import { LineChart } from '@mui/x-charts/LineChart';
+import { isJSDOM } from 'test/utils/skipIf';
describe('useChartCartesianAxis', () => {
const { render } = createRenderer();
+ describe('experimentalFeatures.useNewDefaultTickSpacing', () => {
+ const manyCategories = Array.from({ length: 20 }, (_, i) => `cat-${i}`);
+ const data = manyCategories.map((_, i) => i);
+
+ // In the browser, `getVisibleLabels` measures real label widths and hides
+ // the overlapping ones, so the "all 20 labels render" assertion only holds
+ // in jsdom where measurements are bypassed.
+ it.skipIf(!isJSDOM)('should render one tick per band when the feature is disabled', () => {
+ render(
+ ,
+ );
+
+ const tickLabels = screen.getAllByTestId('ChartsXAxisTickLabel');
+ expect(tickLabels).toHaveLength(manyCategories.length);
+ });
+
+ it('should thin ticks on a band axis based on the drawing area width when the feature is enabled', () => {
+ render(
+ ,
+ );
+
+ const tickLabels = screen.getAllByTestId('ChartsXAxisTickLabel');
+ // With ~280px of drawing area and a 50px default spacing, we expect
+ // roughly width / 50 ticks instead of one per band.
+ expect(tickLabels.length).toBeLessThan(manyCategories.length);
+ expect(tickLabels.length).toBeGreaterThan(0);
+ });
+
+ it.skipIf(!isJSDOM)(
+ 'should not override an explicit tickSpacing when the feature is enabled',
+ () => {
+ render(
+ ,
+ );
+
+ // tickSpacing of 10px on a ~280px area should keep every band's tick.
+ const tickLabels = screen.getAllByTestId('ChartsXAxisTickLabel');
+ expect(tickLabels).toHaveLength(manyCategories.length);
+ },
+ );
+
+ it('should thin ticks on a non-bar cartesian chart when the feature is enabled', () => {
+ render(
+ ,
+ );
+
+ const tickLabels = screen.getAllByTestId('ChartsXAxisTickLabel');
+ expect(tickLabels.length).toBeLessThan(manyCategories.length);
+ expect(tickLabels.length).toBeGreaterThan(0);
+ });
+ });
+
it('should throw an error when axis have duplicate ids', () => {
const expectedError = [
'MUI X Charts: The following axis ids are duplicated: qwerty.',
diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisRendering.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisRendering.selectors.ts
index 281287daaebb8..1e67ff563a56a 100644
--- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisRendering.selectors.ts
+++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisRendering.selectors.ts
@@ -51,11 +51,19 @@ import {
selectorChartXAxisExtrema,
selectorChartYAxisExtrema,
} from './useChartAxisExtrema.selectors';
+import { selectorChartExperimentalFeaturesState } from '../../corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.selectors';
import { selectorChartZAxis } from '../useChartZAxis';
import getMarkerSize, {
type ScatterSizeGetter,
} from '../../../../ScatterChart/seriesConfig/getMarkerSize';
+/* `selectorChartExperimentalFeaturesState` takes a feature name as a
+ * second argument, but `createSelectorMemoized` inputs are pure state
+ * selectors. Wrap it to bind the feature name we care about. */
+const selectorResponsiveTickAdjustment = (
+ state: Parameters[0],
+) => selectorChartExperimentalFeaturesState(state, 'useNewDefaultTickSpacing') || true; // Dumb modification to see the impact on the entire docs
+
export const createZoomMap = (zoom: readonly ZoomData[]) => {
const zoomItemMap = new Map();
zoom.forEach((zoomItem) => {
@@ -437,6 +445,7 @@ export const selectorChartXAxis = createSelectorMemoized(
selectorChartXScales,
selectorChartXAxisAutoSizes,
selectorChartCartesianAxesGap,
+ selectorResponsiveTickAdjustment,
function selectorChartXAxis(
drawingArea,
@@ -447,6 +456,7 @@ export const selectorChartXAxis = createSelectorMemoized(
scales,
autoSizes,
axesGap,
+ responsiveTickAdjustment,
) {
return computeAxisValue({
scales,
@@ -459,6 +469,7 @@ export const selectorChartXAxis = createSelectorMemoized(
domains,
autoSizes,
axesGap,
+ responsiveTickAdjustment: responsiveTickAdjustment ?? false,
});
},
);
@@ -472,6 +483,7 @@ export const selectorChartYAxis = createSelectorMemoized(
selectorChartYScales,
selectorChartYAxisAutoSizes,
selectorChartCartesianAxesGap,
+ selectorResponsiveTickAdjustment,
function selectorChartYAxis(
drawingArea,
@@ -482,6 +494,7 @@ export const selectorChartYAxis = createSelectorMemoized(
scales,
autoSizes,
axesGap,
+ responsiveTickAdjustment,
) {
return computeAxisValue({
scales,
@@ -494,6 +507,7 @@ export const selectorChartYAxis = createSelectorMemoized(
domains,
autoSizes,
axesGap,
+ responsiveTickAdjustment: responsiveTickAdjustment ?? false,
});
},
);