diff --git a/docs/data/charts/styling/CustomOverlay.js b/docs/data/charts/styling/CustomOverlay.js
new file mode 100644
index 0000000000000..502ce26a93e22
--- /dev/null
+++ b/docs/data/charts/styling/CustomOverlay.js
@@ -0,0 +1,76 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Stack from '@mui/material/Stack';
+import { BarChart } from '@mui/x-charts/BarChart';
+import { useDrawingArea, useXScale, useYScale } from '@mui/x-charts/hooks';
+
+const ratios = [0.2, 0.8, 0.6, 0.5];
+
+const LoadingReact = styled('rect')({
+ opacity: 0.2,
+ fill: 'lightgray',
+});
+
+const LoadingText = styled('text')(({ theme }) => ({
+ stroke: 'none',
+ fill: theme.palette.text.primary,
+ shapeRendering: 'crispEdges',
+ textAnchor: 'middle',
+ dominantBaseline: 'middle',
+}));
+
+function LoadingOverlay() {
+ const xScale = useXScale();
+ const yScale = useYScale();
+ const { left, width, height } = useDrawingArea();
+
+ const bandWidth = xScale.bandwidth();
+
+ const [bottom, top] = yScale.range();
+
+ return (
+
+ {xScale.domain().map((item, index) => {
+ const ratio = ratios[index % ratios.length];
+ const barHeight = ratio * (bottom - top);
+
+ return (
+
+ );
+ })}
+
+ Loading data ...
+
+
+ );
+}
+
+export default function CustomOverlay() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/CustomOverlay.tsx b/docs/data/charts/styling/CustomOverlay.tsx
new file mode 100644
index 0000000000000..99af073ed8d5c
--- /dev/null
+++ b/docs/data/charts/styling/CustomOverlay.tsx
@@ -0,0 +1,76 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Stack from '@mui/material/Stack';
+import { BarChart } from '@mui/x-charts/BarChart';
+import { useDrawingArea, useXScale, useYScale } from '@mui/x-charts/hooks';
+
+const ratios = [0.2, 0.8, 0.6, 0.5];
+
+const LoadingReact = styled('rect')({
+ opacity: 0.2,
+ fill: 'lightgray',
+});
+
+const LoadingText = styled('text')(({ theme }) => ({
+ stroke: 'none',
+ fill: theme.palette.text.primary,
+ shapeRendering: 'crispEdges',
+ textAnchor: 'middle',
+ dominantBaseline: 'middle',
+}));
+
+function LoadingOverlay() {
+ const xScale = useXScale<'band'>();
+ const yScale = useYScale();
+ const { left, width, height } = useDrawingArea();
+
+ const bandWidth = xScale.bandwidth();
+
+ const [bottom, top] = yScale.range();
+
+ return (
+
+ {xScale.domain().map((item, index) => {
+ const ratio = ratios[index % ratios.length];
+ const barHeight = ratio * (bottom - top);
+
+ return (
+
+ );
+ })}
+
+ Loading data ...
+
+
+ );
+}
+
+export default function CustomOverlay() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/Overlay.js b/docs/data/charts/styling/Overlay.js
new file mode 100644
index 0000000000000..586e8f5b2ad0c
--- /dev/null
+++ b/docs/data/charts/styling/Overlay.js
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+import { LineChart } from '@mui/x-charts/LineChart';
+
+const emptySeries = {
+ series: [],
+ margin: { top: 10, right: 10, left: 25, bottom: 25 },
+ height: 150,
+};
+
+export default function Overlay() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/Overlay.tsx b/docs/data/charts/styling/Overlay.tsx
new file mode 100644
index 0000000000000..586e8f5b2ad0c
--- /dev/null
+++ b/docs/data/charts/styling/Overlay.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+import { LineChart } from '@mui/x-charts/LineChart';
+
+const emptySeries = {
+ series: [],
+ margin: { top: 10, right: 10, left: 25, bottom: 25 },
+ height: 150,
+};
+
+export default function Overlay() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/Overlay.tsx.preview b/docs/data/charts/styling/Overlay.tsx.preview
new file mode 100644
index 0000000000000..caa7653ae1e29
--- /dev/null
+++ b/docs/data/charts/styling/Overlay.tsx.preview
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/docs/data/charts/styling/OverlayWithAxis.js b/docs/data/charts/styling/OverlayWithAxis.js
new file mode 100644
index 0000000000000..3a9aaab01e604
--- /dev/null
+++ b/docs/data/charts/styling/OverlayWithAxis.js
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+import { LineChart } from '@mui/x-charts/LineChart';
+
+const emptySeries = {
+ series: [],
+ margin: { top: 10, right: 10, left: 25, bottom: 25 },
+ height: 150,
+};
+
+export default function OverlayWithAxis() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/OverlayWithAxis.tsx b/docs/data/charts/styling/OverlayWithAxis.tsx
new file mode 100644
index 0000000000000..3a9aaab01e604
--- /dev/null
+++ b/docs/data/charts/styling/OverlayWithAxis.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+import { LineChart } from '@mui/x-charts/LineChart';
+
+const emptySeries = {
+ series: [],
+ margin: { top: 10, right: 10, left: 25, bottom: 25 },
+ height: 150,
+};
+
+export default function OverlayWithAxis() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/styling/styling.md b/docs/data/charts/styling/styling.md
index 61d1a1357f6d3..3ab1003b62f95 100644
--- a/docs/data/charts/styling/styling.md
+++ b/docs/data/charts/styling/styling.md
@@ -102,6 +102,40 @@ This configuration can be used in Bar Charts to set colors according to string c
}
```
+## Overlay
+
+Charts have a _loading_ and _noData_ overlays that appears if
+
+- `loading` prop is set to `true`.
+- There is no data to display.
+
+{{"demo": "Overlay.js"}}
+
+### Axis display
+
+You can provide the axes data in order to display them while loading the data.
+
+{{"demo": "OverlayWithAxis.js"}}
+
+### Custom overlay
+
+To modify the overly message, you can use the `message` props as follow.
+
+```jsx
+
+```
+
+For more advanced customization, use the `loadingOverlay` and `noDataOverlay` slots link in the following demo.
+
+{{"demo": "CustomOverlay.js"}}
+
## Styling
### Size
diff --git a/docs/pages/x/api/charts/bar-chart.json b/docs/pages/x/api/charts/bar-chart.json
index 6b8bb27f372b3..5e16516b000a7 100644
--- a/docs/pages/x/api/charts/bar-chart.json
+++ b/docs/pages/x/api/charts/bar-chart.json
@@ -36,6 +36,7 @@
"type": { "name": "union", "description": "object
| string" },
"default": "yAxisIds[0] The id of the first provided axis"
},
+ "loading": { "type": { "name": "bool" } },
"margin": {
"type": {
"name": "shape",
@@ -152,6 +153,18 @@
"description": "Custom component for displaying tooltip content when triggered by item event.",
"default": "DefaultChartsItemTooltipContent",
"class": null
+ },
+ {
+ "name": "loadingOverlay",
+ "description": "Overlay component rendered when the chart is in a loading state.",
+ "default": "ChartsLoadingOverlay",
+ "class": null
+ },
+ {
+ "name": "noDataOverlay",
+ "description": "Overlay component rendered when the chart has no data to display.",
+ "default": "ChartsNoDataOverlay",
+ "class": null
}
],
"classes": [],
diff --git a/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json b/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json
index 951dac84a962a..0e922896786aa 100644
--- a/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json
+++ b/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json
@@ -8,7 +8,6 @@
},
"required": true
},
- "axisValue": { "type": { "name": "any" }, "required": true },
"classes": {
"type": { "name": "object" },
"required": true,
@@ -18,6 +17,9 @@
"type": { "name": "arrayOf", "description": "Array<object>" },
"required": true
},
+ "axisValue": {
+ "type": { "name": "union", "description": "Date
| number
| string" }
+ },
"dataIndex": { "type": { "name": "number" } }
},
"name": "DefaultChartsAxisTooltipContent",
diff --git a/docs/pages/x/api/charts/line-chart.json b/docs/pages/x/api/charts/line-chart.json
index b1d80bcaf2f82..0da1697a0aa15 100644
--- a/docs/pages/x/api/charts/line-chart.json
+++ b/docs/pages/x/api/charts/line-chart.json
@@ -34,6 +34,7 @@
"type": { "name": "union", "description": "object
| string" },
"default": "yAxisIds[0] The id of the first provided axis"
},
+ "loading": { "type": { "name": "bool" } },
"margin": {
"type": {
"name": "shape",
@@ -155,6 +156,18 @@
"description": "Custom component for displaying tooltip content when triggered by item event.",
"default": "DefaultChartsItemTooltipContent",
"class": null
+ },
+ {
+ "name": "loadingOverlay",
+ "description": "Overlay component rendered when the chart is in a loading state.",
+ "default": "ChartsLoadingOverlay",
+ "class": null
+ },
+ {
+ "name": "noDataOverlay",
+ "description": "Overlay component rendered when the chart has no data to display.",
+ "default": "ChartsNoDataOverlay",
+ "class": null
}
],
"classes": [],
diff --git a/docs/pages/x/api/charts/pie-chart.json b/docs/pages/x/api/charts/pie-chart.json
index 69293d2a8da24..8583de3dbaa33 100644
--- a/docs/pages/x/api/charts/pie-chart.json
+++ b/docs/pages/x/api/charts/pie-chart.json
@@ -39,6 +39,7 @@
"deprecated": true,
"deprecationInfo": "Consider using slotProps.legend
instead."
},
+ "loading": { "type": { "name": "bool" } },
"margin": {
"type": {
"name": "shape",
@@ -139,6 +140,18 @@
"description": "Custom component for displaying tooltip content when triggered by item event.",
"default": "DefaultChartsItemTooltipContent",
"class": null
+ },
+ {
+ "name": "loadingOverlay",
+ "description": "Overlay component rendered when the chart is in a loading state.",
+ "default": "ChartsLoadingOverlay",
+ "class": null
+ },
+ {
+ "name": "noDataOverlay",
+ "description": "Overlay component rendered when the chart has no data to display.",
+ "default": "ChartsNoDataOverlay",
+ "class": null
}
],
"classes": [],
diff --git a/docs/pages/x/api/charts/scatter-chart.json b/docs/pages/x/api/charts/scatter-chart.json
index d5ef167205545..ff1370a2a4d36 100644
--- a/docs/pages/x/api/charts/scatter-chart.json
+++ b/docs/pages/x/api/charts/scatter-chart.json
@@ -34,6 +34,7 @@
"type": { "name": "union", "description": "object
| string" },
"default": "yAxisIds[0] The id of the first provided axis"
},
+ "loading": { "type": { "name": "bool" } },
"margin": {
"type": {
"name": "shape",
@@ -145,6 +146,18 @@
"description": "Custom component for displaying tooltip content when triggered by item event.",
"default": "DefaultChartsItemTooltipContent",
"class": null
+ },
+ {
+ "name": "loadingOverlay",
+ "description": "Overlay component rendered when the chart is in a loading state.",
+ "default": "ChartsLoadingOverlay",
+ "class": null
+ },
+ {
+ "name": "noDataOverlay",
+ "description": "Overlay component rendered when the chart has no data to display.",
+ "default": "ChartsNoDataOverlay",
+ "class": null
}
],
"classes": [],
diff --git a/docs/translations/api-docs/charts/bar-chart/bar-chart.json b/docs/translations/api-docs/charts/bar-chart/bar-chart.json
index 176f08e5a0a96..ee7dd914cfdbd 100644
--- a/docs/translations/api-docs/charts/bar-chart/bar-chart.json
+++ b/docs/translations/api-docs/charts/bar-chart/bar-chart.json
@@ -23,6 +23,7 @@
"leftAxis": {
"description": "Indicate which axis to display the left of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"margin": {
"description": "The margin between the SVG and the drawing area. It's used for leaving some space for extra information such as the x- and y-axis or legend. Accepts an object with the optional properties: top
, bottom
, left
, and right
."
},
@@ -76,6 +77,8 @@
"bar": "The component that renders the bar.",
"itemContent": "Custom component for displaying tooltip content when triggered by item event.",
"legend": "Custom rendering of the legend.",
+ "loadingOverlay": "Overlay component rendered when the chart is in a loading state.",
+ "noDataOverlay": "Overlay component rendered when the chart has no data to display.",
"popper": "Custom component for the tooltip popper."
}
}
diff --git a/docs/translations/api-docs/charts/line-chart/line-chart.json b/docs/translations/api-docs/charts/line-chart/line-chart.json
index eed651782034e..2df6f3875d557 100644
--- a/docs/translations/api-docs/charts/line-chart/line-chart.json
+++ b/docs/translations/api-docs/charts/line-chart/line-chart.json
@@ -25,6 +25,7 @@
"leftAxis": {
"description": "Indicate which axis to display the left of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"margin": {
"description": "The margin between the SVG and the drawing area. It's used for leaving some space for extra information such as the x- and y-axis or legend. Accepts an object with the optional properties: top
, bottom
, left
, and right
."
},
@@ -76,7 +77,9 @@
"legend": "Custom rendering of the legend.",
"line": "The component that renders the line.",
"lineHighlight": "",
+ "loadingOverlay": "Overlay component rendered when the chart is in a loading state.",
"mark": "",
+ "noDataOverlay": "Overlay component rendered when the chart has no data to display.",
"popper": "Custom component for the tooltip popper."
}
}
diff --git a/docs/translations/api-docs/charts/pie-chart/pie-chart.json b/docs/translations/api-docs/charts/pie-chart/pie-chart.json
index fdbc83823da9d..d5a4ea4df15e9 100644
--- a/docs/translations/api-docs/charts/pie-chart/pie-chart.json
+++ b/docs/translations/api-docs/charts/pie-chart/pie-chart.json
@@ -22,6 +22,7 @@
"description": "Indicate which axis to display the left of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
"legend": { "description": "The props of the legend." },
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"margin": {
"description": "The margin between the SVG and the drawing area. It's used for leaving some space for extra information such as the x- and y-axis or legend. Accepts an object with the optional properties: top
, bottom
, left
, and right
."
},
@@ -61,6 +62,8 @@
"axisTickLabel": "Custom component for tick label.",
"itemContent": "Custom component for displaying tooltip content when triggered by item event.",
"legend": "Custom rendering of the legend.",
+ "loadingOverlay": "Overlay component rendered when the chart is in a loading state.",
+ "noDataOverlay": "Overlay component rendered when the chart has no data to display.",
"pieArc": "",
"pieArcLabel": "",
"popper": "Custom component for the tooltip popper."
diff --git a/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json b/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json
index e21764d22d4c1..7c64bdaf77575 100644
--- a/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json
+++ b/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json
@@ -25,6 +25,7 @@
"leftAxis": {
"description": "Indicate which axis to display the left of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"margin": {
"description": "The margin between the SVG and the drawing area. It's used for leaving some space for extra information such as the x- and y-axis or legend. Accepts an object with the optional properties: top
, bottom
, left
, and right
."
},
@@ -73,6 +74,8 @@
"axisTickLabel": "Custom component for tick label.",
"itemContent": "Custom component for displaying tooltip content when triggered by item event.",
"legend": "Custom rendering of the legend.",
+ "loadingOverlay": "Overlay component rendered when the chart is in a loading state.",
+ "noDataOverlay": "Overlay component rendered when the chart has no data to display.",
"popper": "Custom component for the tooltip popper.",
"scatter": ""
}
diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json
index dc1028649c3e2..891f2d299dc2e 100644
--- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json
+++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json
@@ -232,7 +232,7 @@
"keepNonExistentRowsSelected": {
"description": "If true
, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
- "loading": { "description": "If true
, a loading overlay is displayed." },
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository."
},
diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json
index 3bc27f5060e0e..be79188d0bbc9 100644
--- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json
+++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json
@@ -213,7 +213,7 @@
"keepNonExistentRowsSelected": {
"description": "If true
, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
- "loading": { "description": "If true
, a loading overlay is displayed." },
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository."
},
diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json
index abe89e1a60738..7ea702d9b2107 100644
--- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json
+++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json
@@ -156,7 +156,7 @@
"keepNonExistentRowsSelected": {
"description": "If true
, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
- "loading": { "description": "If true
, a loading overlay is displayed." },
+ "loading": { "description": "If true
, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository."
},
diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx
index 6d812f1bdc27f..e2514278171b4 100644
--- a/packages/x-charts/src/BarChart/BarChart.tsx
+++ b/packages/x-charts/src/BarChart/BarChart.tsx
@@ -30,22 +30,31 @@ import {
ChartsOnAxisClickHandler,
ChartsOnAxisClickHandlerProps,
} from '../ChartsOnAxisClickHandler';
+import {
+ ChartsOverlay,
+ ChartsOverlayProps,
+ ChartsOverlaySlotProps,
+ ChartsOverlaySlots,
+} from '../ChartsOverlay/ChartsOverlay';
export interface BarChartSlots
extends ChartsAxisSlots,
BarPlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots {}
+ ChartsTooltipSlots,
+ ChartsOverlaySlots {}
export interface BarChartSlotProps
extends ChartsAxisSlotProps,
BarPlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps {}
+ ChartsTooltipSlotProps,
+ ChartsOverlaySlotProps {}
export interface BarChartProps
extends Omit,
Omit,
Omit,
+ Omit,
ChartsOnAxisClickHandlerProps {
/**
* The series to display in the bar chart.
@@ -128,6 +137,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
children,
slots,
slotProps,
+ loading,
} = props;
const id = useId();
@@ -187,6 +197,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
skipAnimation={skipAnimation}
onItemClick={onItemClick}
/>
+
-
+ {!loading && }
{children}
@@ -280,6 +291,10 @@ BarChart.propTypes = {
slotProps: PropTypes.object,
slots: PropTypes.object,
}),
+ /**
+ * If `true`, a loading overlay is displayed.
+ */
+ loading: PropTypes.bool,
/**
* The margin between the SVG and the drawing area.
* It's used for leaving some space for extra information such as the x- and y-axis or legend.
diff --git a/packages/x-charts/src/ChartsOverlay/ChartsLoadingOverlay.tsx b/packages/x-charts/src/ChartsOverlay/ChartsLoadingOverlay.tsx
new file mode 100644
index 0000000000000..aff9dc1678b70
--- /dev/null
+++ b/packages/x-charts/src/ChartsOverlay/ChartsLoadingOverlay.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import { useDrawingArea } from '../hooks/useDrawingArea';
+import type { CommonOverlayProps } from './ChartsOverlay';
+
+const StyledText = styled('text')(({ theme }) => ({
+ stroke: 'none',
+ fill: theme.palette.text.primary,
+ shapeRendering: 'crispEdges',
+ textAnchor: 'middle',
+ dominantBaseline: 'middle',
+}));
+
+export function ChartsLoadingOverlay(props: CommonOverlayProps) {
+ const { message, ...other } = props;
+ const { top, left, height, width } = useDrawingArea();
+
+ return (
+
+ {message ?? 'Loading data ...'}
+
+ );
+}
diff --git a/packages/x-charts/src/ChartsOverlay/ChartsNoDataOverlay.tsx b/packages/x-charts/src/ChartsOverlay/ChartsNoDataOverlay.tsx
new file mode 100644
index 0000000000000..3567e83b4b1e1
--- /dev/null
+++ b/packages/x-charts/src/ChartsOverlay/ChartsNoDataOverlay.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import { useDrawingArea } from '../hooks/useDrawingArea';
+import type { CommonOverlayProps } from './ChartsOverlay';
+
+const StyledText = styled('text')(({ theme }) => ({
+ stroke: 'none',
+ fill: theme.palette.text.primary,
+ shapeRendering: 'crispEdges',
+ textAnchor: 'middle',
+ dominantBaseline: 'middle',
+}));
+
+export function ChartsNoDataOverlay(props: CommonOverlayProps) {
+ const { message, ...other } = props;
+ const { top, left, height, width } = useDrawingArea();
+
+ return (
+
+ {message ?? 'No data to display'}
+
+ );
+}
diff --git a/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx b/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx
new file mode 100644
index 0000000000000..ef1e7371336f5
--- /dev/null
+++ b/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx
@@ -0,0 +1,68 @@
+import * as React from 'react';
+import { SxProps, Theme } from '@mui/material/styles';
+import { ChartsLoadingOverlay } from './ChartsLoadingOverlay';
+import { useSeries } from '../hooks/useSeries';
+import { SeriesId } from '../models/seriesType/common';
+import { ChartsNoDataOverlay } from './ChartsNoDataOverlay';
+
+export function useNoData() {
+ const seriesPerType = useSeries();
+
+ return Object.values(seriesPerType).every((seriesOfGivenType) => {
+ if (!seriesOfGivenType) {
+ return true;
+ }
+ const { series, seriesOrder } = seriesOfGivenType;
+
+ return seriesOrder.every((seriesId: SeriesId) => series[seriesId].data.length === 0);
+ });
+}
+
+export type CommonOverlayProps = React.SVGAttributes & {
+ /**
+ * The message displayed by the overlay.
+ */
+ message?: string;
+ sx?: SxProps;
+};
+
+export interface ChartsOverlaySlots {
+ /**
+ * Overlay component rendered when the chart is in a loading state.
+ * @default ChartsLoadingOverlay
+ */
+ loadingOverlay?: React.ElementType;
+ /**
+ * Overlay component rendered when the chart has no data to display.
+ * @default ChartsNoDataOverlay
+ */
+ noDataOverlay?: React.ElementType;
+}
+export interface ChartsOverlaySlotProps {
+ loadingOverlay?: Partial;
+ noDataOverlay?: Partial;
+}
+
+export interface ChartsOverlayProps {
+ /**
+ * If `true`, a loading overlay is displayed.
+ */
+ loading?: boolean;
+
+ slots?: ChartsOverlaySlots;
+ slotProps?: ChartsOverlaySlotProps;
+}
+
+export function ChartsOverlay(props: ChartsOverlayProps) {
+ const noData = useNoData();
+
+ if (props.loading) {
+ const LoadingOverlay = props.slots?.loadingOverlay ?? ChartsLoadingOverlay;
+ return ;
+ }
+ if (noData) {
+ const NoDataOverlay = props.slots?.noDataOverlay ?? ChartsNoDataOverlay;
+ return ;
+ }
+ return null;
+}
diff --git a/packages/x-charts/src/ChartsOverlay/index.ts b/packages/x-charts/src/ChartsOverlay/index.ts
new file mode 100644
index 0000000000000..200c86e4c6073
--- /dev/null
+++ b/packages/x-charts/src/ChartsOverlay/index.ts
@@ -0,0 +1,3 @@
+export { ChartsOverlay } from './ChartsOverlay';
+export { ChartsLoadingOverlay } from './ChartsLoadingOverlay';
+export { ChartsNoDataOverlay } from './ChartsNoDataOverlay';
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
index 8eaadafc69c06..29c4d26e88358 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
@@ -37,7 +37,7 @@ export type ChartsAxisContentProps = {
/**
* The value associated to the current mouse position.
*/
- axisValue: any;
+ axisValue: string | number | Date | null;
/**
* Override or extend the styles applied to the component.
*/
@@ -157,7 +157,11 @@ ChartsAxisTooltipContent.propTypes = {
.isRequired,
}),
}),
- axisValue: PropTypes.any,
+ axisValue: PropTypes.oneOfType([
+ PropTypes.instanceOf(Date),
+ PropTypes.number,
+ PropTypes.string,
+ ]),
classes: PropTypes.object,
dataIndex: PropTypes.number,
series: PropTypes.arrayOf(PropTypes.object),
diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
index 1b224e78fb2ea..8254e1c9ed64b 100644
--- a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
@@ -96,7 +96,7 @@ DefaultChartsAxisTooltipContent.propTypes = {
/**
* The value associated to the current mouse position.
*/
- axisValue: PropTypes.any.isRequired,
+ axisValue: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]),
/**
* Override or extend the styles applied to the component.
*/
diff --git a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
index 1bd054d7e6e3a..35917b1845093 100644
--- a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
+++ b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
@@ -192,6 +192,13 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
ownerState: {},
});
+ const domain = xScale.domain();
+ if (domain.length === 0 || domain[0] === domain[1]) {
+ // Skip axis rendering if
+ // - the data is empty (for band and point axis)
+ // - No data is associated to the axis (other scale types)
+ return null;
+ }
return (
,
Omit,
+ Omit,
ChartsOnAxisClickHandlerProps {
/**
* The series to display in the line chart.
@@ -155,6 +164,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref
slots,
slotProps,
skipAnimation,
+ loading,
} = props;
const id = useId();
@@ -209,6 +219,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref
onItemClick={onLineClick}
skipAnimation={skipAnimation}
/>
+
-
+ {!loading && }
{children}
@@ -307,6 +318,10 @@ LineChart.propTypes = {
slotProps: PropTypes.object,
slots: PropTypes.object,
}),
+ /**
+ * If `true`, a loading overlay is displayed.
+ */
+ loading: PropTypes.bool,
/**
* The margin between the SVG and the drawing area.
* It's used for leaving some space for extra information such as the x- and y-axis or legend.
diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx
index b358e0e005760..380252524043b 100644
--- a/packages/x-charts/src/PieChart/PieChart.tsx
+++ b/packages/x-charts/src/PieChart/PieChart.tsx
@@ -30,22 +30,31 @@ import {
ChartsYAxisProps,
} from '../models/axis';
import { useIsRTL } from '../internals/useIsRTL';
+import {
+ ChartsOverlay,
+ ChartsOverlayProps,
+ ChartsOverlaySlotProps,
+ ChartsOverlaySlots,
+} from '../ChartsOverlay/ChartsOverlay';
export interface PieChartSlots
extends ChartsAxisSlots,
PiePlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots {}
+ ChartsTooltipSlots,
+ ChartsOverlaySlots {}
export interface PieChartSlotProps
extends ChartsAxisSlotProps,
PiePlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps {}
+ ChartsTooltipSlotProps,
+ ChartsOverlaySlotProps {}
export interface PieChartProps
extends Omit,
Omit,
+ Omit,
Pick {
/**
* Indicate which axis to display the bottom of the charts.
@@ -133,6 +142,7 @@ function PieChart(props: PieChartProps) {
slots,
slotProps,
onItemClick,
+ loading,
} = props;
const isRTL = useIsRTL();
@@ -181,9 +191,10 @@ function PieChart(props: PieChartProps) {
onItemClick={onItemClick}
skipAnimation={skipAnimation}
/>
+
-
+ {!loading && }
{children}
);
@@ -253,6 +264,10 @@ PieChart.propTypes = {
slotProps: PropTypes.object,
slots: PropTypes.object,
}),
+ /**
+ * If `true`, a loading overlay is displayed.
+ */
+ loading: PropTypes.bool,
/**
* The margin between the SVG and the drawing area.
* It's used for leaving some space for extra information such as the x- and y-axis or legend.
diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
index ba6afc0469af9..eb8666d4ec51b 100644
--- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx
+++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
@@ -25,6 +25,12 @@ import {
ChartsLegendSlotProps,
ChartsLegendSlots,
} from '../ChartsLegend';
+import {
+ ChartsOverlay,
+ ChartsOverlayProps,
+ ChartsOverlaySlotProps,
+ ChartsOverlaySlots,
+} from '../ChartsOverlay/ChartsOverlay';
import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight';
import { ChartsAxisSlots, ChartsAxisSlotProps } from '../models/axis';
import {
@@ -38,17 +44,20 @@ export interface ScatterChartSlots
extends ChartsAxisSlots,
ScatterPlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots {}
+ ChartsTooltipSlots,
+ ChartsOverlaySlots {}
export interface ScatterChartSlotProps
extends ChartsAxisSlotProps,
ScatterPlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps {}
+ ChartsTooltipSlotProps,
+ ChartsOverlaySlotProps {}
export interface ScatterChartProps
extends Omit,
Omit,
Omit,
+ Omit,
Omit {
/**
* The series to display in the scatter chart.
@@ -133,6 +142,7 @@ const ScatterChart = React.forwardRef(function ScatterChart(props: ScatterChartP
children,
slots,
slotProps,
+ loading,
} = props;
return (
+
-
+ {!loading && }
{children}
@@ -253,6 +264,10 @@ ScatterChart.propTypes = {
slotProps: PropTypes.object,
slots: PropTypes.object,
}),
+ /**
+ * If `true`, a loading overlay is displayed.
+ */
+ loading: PropTypes.bool,
/**
* The margin between the SVG and the drawing area.
* It's used for leaving some space for extra information such as the x- and y-axis or legend.
diff --git a/packages/x-charts/src/hooks/useScale.ts b/packages/x-charts/src/hooks/useScale.ts
index c891a82d0af19..736435eeab43a 100644
--- a/packages/x-charts/src/hooks/useScale.ts
+++ b/packages/x-charts/src/hooks/useScale.ts
@@ -1,7 +1,7 @@
import * as React from 'react';
import { CartesianContext } from '../context/CartesianContextProvider';
import { isBandScale } from '../internals/isBandScale';
-import { D3Scale } from '../models/axis';
+import { AxisScaleConfig, D3Scale, ScaleName } from '../models/axis';
/**
* For a given scale return a function that map value to their position.
@@ -16,7 +16,9 @@ export function getValueToPositionMapper(scale: D3Scale) {
return (value: any) => scale(value) as number;
}
-export function useXScale(identifier?: number | string) {
+export function useXScale(
+ identifier?: number | string,
+): AxisScaleConfig[S]['scale'] {
const { xAxis, xAxisIds } = React.useContext(CartesianContext);
const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0];
@@ -24,7 +26,9 @@ export function useXScale(identifier?: number | string) {
return xAxis[id].scale;
}
-export function useYScale(identifier?: number | string) {
+export function useYScale(
+ identifier?: number | string,
+): AxisScaleConfig[S]['scale'] {
const { yAxis, yAxisIds } = React.useContext(CartesianContext);
const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0];
diff --git a/packages/x-charts/src/hooks/useTicks.ts b/packages/x-charts/src/hooks/useTicks.ts
index fe8f84f3f39e1..2e861a33c88e2 100644
--- a/packages/x-charts/src/hooks/useTicks.ts
+++ b/packages/x-charts/src/hooks/useTicks.ts
@@ -139,6 +139,11 @@ export function useTicks(
}));
}
+ if (scale.domain().length === 0 || scale.domain()[0] === scale.domain()[1]) {
+ // The axis should not be visible, so ticks should also be hidden.
+ return [];
+ }
+
const ticks = typeof tickInterval === 'object' ? tickInterval : scale.ticks(tickNumber);
return ticks.map((value: any) => ({
value,
diff --git a/packages/x-charts/src/models/axis.ts b/packages/x-charts/src/models/axis.ts
index fe9bba7b51091..cb6b11300956b 100644
--- a/packages/x-charts/src/models/axis.ts
+++ b/packages/x-charts/src/models/axis.ts
@@ -159,7 +159,7 @@ export interface ChartsXAxisProps extends ChartsAxisProps {
export type ScaleName = 'linear' | 'band' | 'point' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc';
export type ContinuousScaleName = 'linear' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc';
-interface AxisScaleConfig {
+export interface AxisScaleConfig {
band: {
scaleType: 'band';
scale: ScaleBand;
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
index 9e3f04214546f..2528ab754e58b 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
@@ -515,7 +515,7 @@ DataGridPremiumRaw.propTypes = {
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
- * If `true`, a loading overlay is displayed.
+ * If `true`, a loading overlay is displayed.
*/
loading: PropTypes.bool,
/**
diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
index cd6c469747b2f..00861a129946d 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
+++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
@@ -456,7 +456,7 @@ DataGridProRaw.propTypes = {
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
- * If `true`, a loading overlay is displayed.
+ * If `true`, a loading overlay is displayed.
*/
loading: PropTypes.bool,
/**
diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx
index 56a9868fa1826..5be3925e2bfd8 100644
--- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx
+++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx
@@ -367,7 +367,7 @@ DataGridRaw.propTypes = {
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
- * If `true`, a loading overlay is displayed.
+ * If `true`, a loading overlay is displayed.
*/
loading: PropTypes.bool,
/**
diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts
index 3d80dcdb101c4..dab6bbdef927b 100644
--- a/packages/x-data-grid/src/models/props/DataGridProps.ts
+++ b/packages/x-data-grid/src/models/props/DataGridProps.ts
@@ -735,7 +735,7 @@ export interface DataGridPropsWithoutDefaultValue;
/**
- * If `true`, a loading overlay is displayed.
+ * If `true`, a loading overlay is displayed.
*/
loading?: boolean;
/**
diff --git a/scripts/buildApiDocs/chartsSettings/index.ts b/scripts/buildApiDocs/chartsSettings/index.ts
index bf83efd12a609..1beaabaa78d6c 100644
--- a/scripts/buildApiDocs/chartsSettings/index.ts
+++ b/scripts/buildApiDocs/chartsSettings/index.ts
@@ -65,6 +65,9 @@ export default apiPages;
'x-charts/src/Gauge/GaugeReferenceArc.tsx',
'x-charts/src/Gauge/GaugeValueArc.tsx',
'x-charts/src/Gauge/GaugeValueText.tsx',
+ 'x-charts/src/ChartsOverlay/ChartsOverlay.tsx',
+ 'x-charts/src/ChartsOverlay/ChartsNoDataOverlay.tsx',
+ 'x-charts/src/ChartsOverlay/ChartsLoadingOverlay.tsx',
].some((invalidPath) => filename.endsWith(invalidPath));
},
skipAnnotatingComponentDefinition: true,