diff --git a/docs/data/charts-component-api-pages.ts b/docs/data/charts-component-api-pages.ts
index 5d3e702baff5a..b2a117fef59bc 100644
--- a/docs/data/charts-component-api-pages.ts
+++ b/docs/data/charts-component-api-pages.ts
@@ -1,6 +1,14 @@
import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage';
const apiPages: MuiPage[] = [
+ {
+ pathname: '/x/api/charts/animated-area',
+ title: 'AnimatedArea',
+ },
+ {
+ pathname: '/x/api/charts/animated-line',
+ title: 'AnimatedLine',
+ },
{
pathname: '/x/api/charts/area-element',
title: 'AreaElement',
diff --git a/docs/data/charts/lines/ConnectNulls.js b/docs/data/charts/lines/ConnectNulls.js
index 1a543aeecda35..0032c2383eab1 100644
--- a/docs/data/charts/lines/ConnectNulls.js
+++ b/docs/data/charts/lines/ConnectNulls.js
@@ -31,6 +31,7 @@ export default function ConnectNulls() {
]}
height={200}
margin={{ top: 10, bottom: 20 }}
+ skipAnimation
/>
);
diff --git a/docs/data/charts/lines/ConnectNulls.tsx b/docs/data/charts/lines/ConnectNulls.tsx
index 1a543aeecda35..0032c2383eab1 100644
--- a/docs/data/charts/lines/ConnectNulls.tsx
+++ b/docs/data/charts/lines/ConnectNulls.tsx
@@ -31,6 +31,7 @@ export default function ConnectNulls() {
]}
height={200}
margin={{ top: 10, bottom: 20 }}
+ skipAnimation
/>
);
diff --git a/docs/data/charts/lines/InterpolationDemoNoSnap.js b/docs/data/charts/lines/InterpolationDemoNoSnap.js
index 9ee64cc05e75c..7d96ed6587b13 100644
--- a/docs/data/charts/lines/InterpolationDemoNoSnap.js
+++ b/docs/data/charts/lines/InterpolationDemoNoSnap.js
@@ -52,6 +52,7 @@ export default function InterpolationDemoNoSnap() {
]}
height={300}
margin={{ top: 10, bottom: 30 }}
+ skipAnimation
/>
diff --git a/docs/data/charts/lines/LineAnimation.js b/docs/data/charts/lines/LineAnimation.js
new file mode 100644
index 0000000000000..7bb2d20fda951
--- /dev/null
+++ b/docs/data/charts/lines/LineAnimation.js
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Stack from '@mui/material/Stack';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+import { LineChart } from '@mui/x-charts/LineChart';
+import { mangoFusionPalette } from '@mui/x-charts/colorPalettes';
+
+const defaultSeries = [
+ { id: '1', data: [4, 5, 1, 2, 3, 3, 2], area: true, stack: '1' },
+ { id: '2', data: [7, 4, 6, 7, 2, 3, 5], area: true, stack: '1' },
+ { id: '3', data: [6, 4, 1, 2, 6, 3, 3], area: true, stack: '1' },
+ { id: '4', data: [4, 7, 6, 1, 2, 7, 7], area: true, stack: '1' },
+ { id: '5', data: [2, 2, 1, 7, 1, 5, 3], area: true, stack: '1' },
+ { id: '6', data: [6, 6, 1, 6, 7, 1, 1], area: true, stack: '1' },
+ { id: '7', data: [7, 6, 1, 6, 4, 4, 6], area: true, stack: '1' },
+ { id: '8', data: [4, 3, 1, 6, 6, 3, 5], area: true, stack: '1' },
+ { id: '9', data: [7, 6, 2, 7, 4, 2, 7], area: true, stack: '1' },
+].map((item, index) => ({
+ ...item,
+ color: mangoFusionPalette('light')[index],
+}));
+
+export default function LineAnimation() {
+ const [series, setSeries] = React.useState(defaultSeries);
+ const [nbSeries, setNbSeries] = React.useState(3);
+ const [skipAnimation, setSkipAnimation] = React.useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+ setSkipAnimation(event.target.checked)} />
+ }
+ label="skipAnimation"
+ labelPlacement="end"
+ />
+
+
+ );
+}
diff --git a/docs/data/charts/lines/LineAnimation.tsx b/docs/data/charts/lines/LineAnimation.tsx
new file mode 100644
index 0000000000000..7bb2d20fda951
--- /dev/null
+++ b/docs/data/charts/lines/LineAnimation.tsx
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Stack from '@mui/material/Stack';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+import { LineChart } from '@mui/x-charts/LineChart';
+import { mangoFusionPalette } from '@mui/x-charts/colorPalettes';
+
+const defaultSeries = [
+ { id: '1', data: [4, 5, 1, 2, 3, 3, 2], area: true, stack: '1' },
+ { id: '2', data: [7, 4, 6, 7, 2, 3, 5], area: true, stack: '1' },
+ { id: '3', data: [6, 4, 1, 2, 6, 3, 3], area: true, stack: '1' },
+ { id: '4', data: [4, 7, 6, 1, 2, 7, 7], area: true, stack: '1' },
+ { id: '5', data: [2, 2, 1, 7, 1, 5, 3], area: true, stack: '1' },
+ { id: '6', data: [6, 6, 1, 6, 7, 1, 1], area: true, stack: '1' },
+ { id: '7', data: [7, 6, 1, 6, 4, 4, 6], area: true, stack: '1' },
+ { id: '8', data: [4, 3, 1, 6, 6, 3, 5], area: true, stack: '1' },
+ { id: '9', data: [7, 6, 2, 7, 4, 2, 7], area: true, stack: '1' },
+].map((item, index) => ({
+ ...item,
+ color: mangoFusionPalette('light')[index],
+}));
+
+export default function LineAnimation() {
+ const [series, setSeries] = React.useState(defaultSeries);
+ const [nbSeries, setNbSeries] = React.useState(3);
+ const [skipAnimation, setSkipAnimation] = React.useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+ setSkipAnimation(event.target.checked)} />
+ }
+ label="skipAnimation"
+ labelPlacement="end"
+ />
+
+
+ );
+}
diff --git a/docs/data/charts/lines/lines.md b/docs/data/charts/lines/lines.md
index 8bc1fd8eb89bb..37a1250e87f8a 100644
--- a/docs/data/charts/lines/lines.md
+++ b/docs/data/charts/lines/lines.md
@@ -1,7 +1,7 @@
---
title: React Line chart
productId: x-charts
-components: LineChart, LineElement, LineHighlightElement, LineHighlightPlot, LinePlot, MarkElement, MarkPlot, AreaElement, AreaPlot
+components: LineChart, LineElement, LineHighlightElement, LineHighlightPlot, LinePlot, MarkElement, MarkPlot, AreaElement, AreaPlot, AnimatedLine, AnimatedArea
---
# Charts - Lines
@@ -143,3 +143,30 @@ sx={{
```
{{"demo": "CSSCustomization.js"}}
+
+## Animation
+
+To skip animation at the creation and update of your chart, you can use the `skipAnimation` prop.
+When set to `true` it skips animation powered by `@react-spring/web`.
+
+Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important).
+
+:::warning
+If you support interactive ways to add or remove series from your chart, you have to provide the series' id.
+
+Otherwise the chart will have no way to know if you are modifying, removing, or adding some series.
+This will lead to strange behaviors.
+:::
+
+```jsx
+// For a single component chart
+
+
+// For a composed chart
+
+
+
+
+```
+
+{{"demo": "LineAnimation.js"}}
diff --git a/docs/pages/x/api/charts/animated-area.js b/docs/pages/x/api/charts/animated-area.js
new file mode 100644
index 0000000000000..f03605dd0bcfd
--- /dev/null
+++ b/docs/pages/x/api/charts/animated-area.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './animated-area.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docsx/translations/api-docs/charts/animated-area',
+ false,
+ /\.\/animated-area.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/x/api/charts/animated-area.json b/docs/pages/x/api/charts/animated-area.json
new file mode 100644
index 0000000000000..709e7b00d3ff9
--- /dev/null
+++ b/docs/pages/x/api/charts/animated-area.json
@@ -0,0 +1,14 @@
+{
+ "props": { "skipAnimation": { "type": { "name": "bool" }, "default": "false" } },
+ "name": "AnimatedArea",
+ "imports": [
+ "import { AnimatedArea } from '@mui/x-charts/LineChart';",
+ "import { AnimatedArea } from '@mui/x-charts';"
+ ],
+ "classes": [],
+ "muiName": "MuiAnimatedArea",
+ "filename": "/packages/x-charts/src/LineChart/AnimatedArea.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/x/api/charts/animated-line.js b/docs/pages/x/api/charts/animated-line.js
new file mode 100644
index 0000000000000..ac113d11aa47e
--- /dev/null
+++ b/docs/pages/x/api/charts/animated-line.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './animated-line.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docsx/translations/api-docs/charts/animated-line',
+ false,
+ /\.\/animated-line.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/x/api/charts/animated-line.json b/docs/pages/x/api/charts/animated-line.json
new file mode 100644
index 0000000000000..b107967c75f46
--- /dev/null
+++ b/docs/pages/x/api/charts/animated-line.json
@@ -0,0 +1,14 @@
+{
+ "props": { "skipAnimation": { "type": { "name": "bool" }, "default": "false" } },
+ "name": "AnimatedLine",
+ "imports": [
+ "import { AnimatedLine } from '@mui/x-charts/LineChart';",
+ "import { AnimatedLine } from '@mui/x-charts';"
+ ],
+ "classes": [],
+ "muiName": "MuiAnimatedLine",
+ "filename": "/packages/x-charts/src/LineChart/AnimatedLine.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/x/api/charts/area-element.json b/docs/pages/x/api/charts/area-element.json
index 7ca180d2214d3..b5dfedae13bd5 100644
--- a/docs/pages/x/api/charts/area-element.json
+++ b/docs/pages/x/api/charts/area-element.json
@@ -1,5 +1,6 @@
{
"props": {
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
@@ -12,6 +13,14 @@
"import { AreaElement } from '@mui/x-charts/LineChart';",
"import { AreaElement } from '@mui/x-charts';"
],
+ "slots": [
+ {
+ "name": "area",
+ "description": "The component that renders the area.",
+ "default": "AnimatedArea",
+ "class": null
+ }
+ ],
"classes": [
{
"key": "faded",
diff --git a/docs/pages/x/api/charts/area-plot.json b/docs/pages/x/api/charts/area-plot.json
index c0a421fbd92da..30c6fa3f2ce3c 100644
--- a/docs/pages/x/api/charts/area-plot.json
+++ b/docs/pages/x/api/charts/area-plot.json
@@ -1,5 +1,6 @@
{
"props": {
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
@@ -12,7 +13,14 @@
"import { AreaPlot } from '@mui/x-charts/LineChart';",
"import { AreaPlot } from '@mui/x-charts';"
],
- "slots": [{ "name": "area", "description": "", "class": null }],
+ "slots": [
+ {
+ "name": "area",
+ "description": "The component that renders the area.",
+ "default": "AnimatedArea",
+ "class": null
+ }
+ ],
"classes": [],
"muiName": "MuiAreaPlot",
"filename": "/packages/x-charts/src/LineChart/AreaPlot.tsx",
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 fbb3dfe92b823..951dac84a962a 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
@@ -4,7 +4,7 @@
"axisData": {
"type": {
"name": "shape",
- "description": "{ x?: { index?: number, value: Date
| number }, y?: { index?: number, value: Date
| number } }"
+ "description": "{ x?: { index?: number, value: Date
| number
| string }, y?: { index?: number, value: Date
| number
| string } }"
},
"required": true
},
diff --git a/docs/pages/x/api/charts/line-chart.json b/docs/pages/x/api/charts/line-chart.json
index 94b00d38bddfa..1ae374f7c5e72 100644
--- a/docs/pages/x/api/charts/line-chart.json
+++ b/docs/pages/x/api/charts/line-chart.json
@@ -43,6 +43,7 @@
},
"default": "null"
},
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
@@ -80,8 +81,18 @@
{ "name": "axisTick", "description": "", "class": null },
{ "name": "axisTickLabel", "description": "", "class": null },
{ "name": "axisLabel", "description": "", "class": null },
- { "name": "area", "description": "", "class": null },
- { "name": "line", "description": "", "class": null },
+ {
+ "name": "area",
+ "description": "The component that renders the area.",
+ "default": "AnimatedArea",
+ "class": null
+ },
+ {
+ "name": "line",
+ "description": "The component that renders the line.",
+ "default": "LineElementPath",
+ "class": null
+ },
{ "name": "mark", "description": "", "class": null },
{ "name": "lineHighlight", "description": "", "class": null },
{ "name": "legend", "description": "", "class": null },
diff --git a/docs/pages/x/api/charts/line-element.json b/docs/pages/x/api/charts/line-element.json
index 92a5ebdbd4d62..3d971c68082e7 100644
--- a/docs/pages/x/api/charts/line-element.json
+++ b/docs/pages/x/api/charts/line-element.json
@@ -1,5 +1,6 @@
{
"props": {
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
@@ -12,6 +13,14 @@
"import { LineElement } from '@mui/x-charts/LineChart';",
"import { LineElement } from '@mui/x-charts';"
],
+ "slots": [
+ {
+ "name": "line",
+ "description": "The component that renders the line.",
+ "default": "LineElementPath",
+ "class": null
+ }
+ ],
"classes": [
{
"key": "faded",
diff --git a/docs/pages/x/api/charts/line-plot.json b/docs/pages/x/api/charts/line-plot.json
index db76736025062..695302fba7fa5 100644
--- a/docs/pages/x/api/charts/line-plot.json
+++ b/docs/pages/x/api/charts/line-plot.json
@@ -1,5 +1,6 @@
{
"props": {
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
@@ -12,7 +13,14 @@
"import { LinePlot } from '@mui/x-charts/LineChart';",
"import { LinePlot } from '@mui/x-charts';"
],
- "slots": [{ "name": "line", "description": "", "class": null }],
+ "slots": [
+ {
+ "name": "line",
+ "description": "The component that renders the line.",
+ "default": "LineElementPath",
+ "class": null
+ }
+ ],
"classes": [],
"muiName": "MuiLinePlot",
"filename": "/packages/x-charts/src/LineChart/LinePlot.tsx",
diff --git a/docs/pages/x/api/charts/mark-element.json b/docs/pages/x/api/charts/mark-element.json
index 7440419178cfc..492c60b29943e 100644
--- a/docs/pages/x/api/charts/mark-element.json
+++ b/docs/pages/x/api/charts/mark-element.json
@@ -7,7 +7,8 @@
"description": "'circle'
| 'cross'
| 'diamond'
| 'square'
| 'star'
| 'triangle'
| 'wye'"
},
"required": true
- }
+ },
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" }
},
"name": "MarkElement",
"imports": [
diff --git a/docs/pages/x/api/charts/mark-plot.json b/docs/pages/x/api/charts/mark-plot.json
index 9cd5db89c4ff5..e334cc78e61d5 100644
--- a/docs/pages/x/api/charts/mark-plot.json
+++ b/docs/pages/x/api/charts/mark-plot.json
@@ -1,5 +1,6 @@
{
"props": {
+ "skipAnimation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": { "type": { "name": "object" }, "default": "{}" },
"slots": {
"type": { "name": "object" },
diff --git a/docs/pages/x/api/charts/spark-line-chart.json b/docs/pages/x/api/charts/spark-line-chart.json
index 3b17773325964..6ac9a094ae81f 100644
--- a/docs/pages/x/api/charts/spark-line-chart.json
+++ b/docs/pages/x/api/charts/spark-line-chart.json
@@ -54,8 +54,18 @@
"import { SparkLineChart } from '@mui/x-charts';"
],
"slots": [
- { "name": "area", "description": "", "class": null },
- { "name": "line", "description": "", "class": null },
+ {
+ "name": "area",
+ "description": "The component that renders the area.",
+ "default": "AnimatedArea",
+ "class": null
+ },
+ {
+ "name": "line",
+ "description": "The component that renders the line.",
+ "default": "LineElementPath",
+ "class": null
+ },
{ "name": "mark", "description": "", "class": null },
{ "name": "lineHighlight", "description": "", "class": null },
{ "name": "bar", "description": "", "class": null },
diff --git a/docs/translations/api-docs/charts/animated-area/animated-area.json b/docs/translations/api-docs/charts/animated-area/animated-area.json
new file mode 100644
index 0000000000000..5300fa0d8559a
--- /dev/null
+++ b/docs/translations/api-docs/charts/animated-area/animated-area.json
@@ -0,0 +1,7 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/charts/animated-line/animated-line.json b/docs/translations/api-docs/charts/animated-line/animated-line.json
new file mode 100644
index 0000000000000..5300fa0d8559a
--- /dev/null
+++ b/docs/translations/api-docs/charts/animated-line/animated-line.json
@@ -0,0 +1,7 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/charts/area-element/area-element.json b/docs/translations/api-docs/charts/area-element/area-element.json
index fca3901855f08..35ae130885231 100644
--- a/docs/translations/api-docs/charts/area-element/area-element.json
+++ b/docs/translations/api-docs/charts/area-element/area-element.json
@@ -1,6 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
@@ -16,5 +17,6 @@
"conditions": "higlighted"
},
"root": { "description": "Styles applied to the root element." }
- }
+ },
+ "slotDescriptions": { "area": "The component that renders the area." }
}
diff --git a/docs/translations/api-docs/charts/area-plot/area-plot.json b/docs/translations/api-docs/charts/area-plot/area-plot.json
index dd9fd23a9934c..44b71c6c124ce 100644
--- a/docs/translations/api-docs/charts/area-plot/area-plot.json
+++ b/docs/translations/api-docs/charts/area-plot/area-plot.json
@@ -1,9 +1,10 @@
{
"componentDescription": "",
"propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
"classDescriptions": {},
- "slotDescriptions": { "area": "" }
+ "slotDescriptions": { "area": "The component that renders the area." }
}
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 e3ecc71677a8c..52c7ccee6d9e5 100644
--- a/docs/translations/api-docs/charts/bar-chart/bar-chart.json
+++ b/docs/translations/api-docs/charts/bar-chart/bar-chart.json
@@ -26,7 +26,7 @@
"rightAxis": {
"description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." },
"topAxis": {
diff --git a/docs/translations/api-docs/charts/bar-plot/bar-plot.json b/docs/translations/api-docs/charts/bar-plot/bar-plot.json
index d9ee286961782..b84aa3efd86ae 100644
--- a/docs/translations/api-docs/charts/bar-plot/bar-plot.json
+++ b/docs/translations/api-docs/charts/bar-plot/bar-plot.json
@@ -1,7 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
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 f279ed2629d81..a6d9fe50d903f 100644
--- a/docs/translations/api-docs/charts/line-chart/line-chart.json
+++ b/docs/translations/api-docs/charts/line-chart/line-chart.json
@@ -29,6 +29,7 @@
"rightAxis": {
"description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." },
"topAxis": {
@@ -46,7 +47,7 @@
},
"classDescriptions": {},
"slotDescriptions": {
- "area": "",
+ "area": "The component that renders the area.",
"axisContent": "",
"axisLabel": "",
"axisLine": "",
@@ -54,7 +55,7 @@
"axisTickLabel": "",
"itemContent": "",
"legend": "",
- "line": "",
+ "line": "The component that renders the line.",
"lineHighlight": "",
"mark": "",
"popper": ""
diff --git a/docs/translations/api-docs/charts/line-element/line-element.json b/docs/translations/api-docs/charts/line-element/line-element.json
index fca3901855f08..c638934e73ea0 100644
--- a/docs/translations/api-docs/charts/line-element/line-element.json
+++ b/docs/translations/api-docs/charts/line-element/line-element.json
@@ -1,6 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
@@ -16,5 +17,6 @@
"conditions": "higlighted"
},
"root": { "description": "Styles applied to the root element." }
- }
+ },
+ "slotDescriptions": { "line": "The component that renders the line." }
}
diff --git a/docs/translations/api-docs/charts/line-plot/line-plot.json b/docs/translations/api-docs/charts/line-plot/line-plot.json
index d1f64423b9414..32105cc21e585 100644
--- a/docs/translations/api-docs/charts/line-plot/line-plot.json
+++ b/docs/translations/api-docs/charts/line-plot/line-plot.json
@@ -1,9 +1,10 @@
{
"componentDescription": "",
"propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
"classDescriptions": {},
- "slotDescriptions": { "line": "" }
+ "slotDescriptions": { "line": "The component that renders the line." }
}
diff --git a/docs/translations/api-docs/charts/mark-element/mark-element.json b/docs/translations/api-docs/charts/mark-element/mark-element.json
index 81441020bb99f..522d722c7a871 100644
--- a/docs/translations/api-docs/charts/mark-element/mark-element.json
+++ b/docs/translations/api-docs/charts/mark-element/mark-element.json
@@ -2,7 +2,8 @@
"componentDescription": "",
"propDescriptions": {
"dataIndex": { "description": "The index to the element in the series' data array." },
- "shape": { "description": "The shape of the marker." }
+ "shape": { "description": "The shape of the marker." },
+ "skipAnimation": { "description": "If true
, animations are skipped." }
},
"classDescriptions": {
"faded": {
diff --git a/docs/translations/api-docs/charts/mark-plot/mark-plot.json b/docs/translations/api-docs/charts/mark-plot/mark-plot.json
index aace660db0caa..c338ac1652e53 100644
--- a/docs/translations/api-docs/charts/mark-plot/mark-plot.json
+++ b/docs/translations/api-docs/charts/mark-plot/mark-plot.json
@@ -1,6 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
diff --git a/docs/translations/api-docs/charts/pie-arc-label-plot/pie-arc-label-plot.json b/docs/translations/api-docs/charts/pie-arc-label-plot/pie-arc-label-plot.json
index 599a5d9f91662..c4bf2a3494302 100644
--- a/docs/translations/api-docs/charts/pie-arc-label-plot/pie-arc-label-plot.json
+++ b/docs/translations/api-docs/charts/pie-arc-label-plot/pie-arc-label-plot.json
@@ -16,7 +16,7 @@
},
"outerRadius": { "description": "The radius between circle center and the end of the arc." },
"paddingAngle": { "description": "The padding angle (deg) between two arcs." },
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
diff --git a/docs/translations/api-docs/charts/pie-arc-plot/pie-arc-plot.json b/docs/translations/api-docs/charts/pie-arc-plot/pie-arc-plot.json
index 8c4cdaea4809b..58bfec3d2112b 100644
--- a/docs/translations/api-docs/charts/pie-arc-plot/pie-arc-plot.json
+++ b/docs/translations/api-docs/charts/pie-arc-plot/pie-arc-plot.json
@@ -22,7 +22,7 @@
},
"outerRadius": { "description": "The radius between circle center and the end of the arc." },
"paddingAngle": { "description": "The padding angle (deg) between two arcs." },
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
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 8960e6b3a746f..c9c6863b0053a 100644
--- a/docs/translations/api-docs/charts/pie-chart/pie-chart.json
+++ b/docs/translations/api-docs/charts/pie-chart/pie-chart.json
@@ -23,7 +23,7 @@
"rightAxis": {
"description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"topAxis": {
"description": "Indicate which axis to display the top of the charts. Can be a string (the id of the axis) or an object ChartsXAxisProps
."
diff --git a/docs/translations/api-docs/charts/pie-plot/pie-plot.json b/docs/translations/api-docs/charts/pie-plot/pie-plot.json
index d156b39b03064..bf05960bf8799 100644
--- a/docs/translations/api-docs/charts/pie-plot/pie-plot.json
+++ b/docs/translations/api-docs/charts/pie-plot/pie-plot.json
@@ -9,7 +9,7 @@
"item": "The pie item."
}
},
- "skipAnimation": { "description": "If true
, animations are skiped." },
+ "skipAnimation": { "description": "If true
, animations are skipped." },
"slotProps": { "description": "The props used for each component slot." },
"slots": { "description": "Overridable component slots." }
},
diff --git a/docs/translations/api-docs/charts/spark-line-chart/spark-line-chart.json b/docs/translations/api-docs/charts/spark-line-chart/spark-line-chart.json
index 9ab42a24b8b30..6e43932009df6 100644
--- a/docs/translations/api-docs/charts/spark-line-chart/spark-line-chart.json
+++ b/docs/translations/api-docs/charts/spark-line-chart/spark-line-chart.json
@@ -40,11 +40,11 @@
},
"classDescriptions": {},
"slotDescriptions": {
- "area": "",
+ "area": "The component that renders the area.",
"axisContent": "",
"bar": "",
"itemContent": "",
- "line": "",
+ "line": "The component that renders the line.",
"lineHighlight": "",
"mark": "",
"popper": ""
diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json
index 4b37ba5922ae2..2a3a4c90fc047 100644
--- a/packages/x-charts/package.json
+++ b/packages/x-charts/package.json
@@ -50,6 +50,7 @@
"d3-delaunay": "^6.0.4",
"d3-scale": "^4.0.2",
"d3-shape": "^3.2.0",
+ "d3-interpolate": "^3.0.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
@@ -71,6 +72,7 @@
"@types/d3-color": "^3.1.3",
"@types/d3-delaunay": "^6.0.4",
"@types/d3-scale": "^4.0.8",
+ "@types/d3-interpolate": "^3.0.4",
"@types/d3-shape": "^3.1.6"
},
"exports": {
diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx
index f3215547a8974..b83f033fdcdd9 100644
--- a/packages/x-charts/src/BarChart/BarChart.tsx
+++ b/packages/x-charts/src/BarChart/BarChart.tsx
@@ -339,7 +339,7 @@ BarChart.propTypes = {
]),
series: PropTypes.arrayOf(PropTypes.object).isRequired,
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/BarChart/BarElement.tsx b/packages/x-charts/src/BarChart/BarElement.tsx
index 0bcca243e516f..df1a0dfe483ea 100644
--- a/packages/x-charts/src/BarChart/BarElement.tsx
+++ b/packages/x-charts/src/BarChart/BarElement.tsx
@@ -78,7 +78,7 @@ export type BarElementProps = Omit {
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation?: boolean;
@@ -74,7 +74,7 @@ interface CompletedBarData {
highlightScope?: Partial;
}
-const useCompletedData = (): CompletedBarData[] => {
+const useAggregatedData = (): CompletedBarData[] => {
const seriesData =
React.useContext(SeriesContext).bar ??
({ series: {}, stackingGroups: [], seriesOrder: [] } as FormatterResult<'bar'>);
@@ -215,7 +215,7 @@ const getInStyle = ({ x, width, y, height }: CompletedBarData) => ({
* - [BarPlot API](https://mui.com/x/api/charts/bar-plot/)
*/
function BarPlot(props: BarPlotProps) {
- const completedData = useCompletedData();
+ const completedData = useAggregatedData();
const { skipAnimation, ...other } = props;
const transition = useTransition(completedData, {
@@ -248,7 +248,7 @@ BarPlot.propTypes = {
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/ChartsReferenceLine/ChartsXReferenceLine.tsx b/packages/x-charts/src/ChartsReferenceLine/ChartsXReferenceLine.tsx
index 765d7d43f1b0e..6e668f814b13e 100644
--- a/packages/x-charts/src/ChartsReferenceLine/ChartsXReferenceLine.tsx
+++ b/packages/x-charts/src/ChartsReferenceLine/ChartsXReferenceLine.tsx
@@ -96,7 +96,7 @@ function ChartsXReferenceLine(props: ChartsXReferenceLineProps) {
if (!warnedOnce) {
warnedOnce = true;
console.error(
- `MUI X: the value ${x} does not exist in the data of x axis with id ${axisId}.`,
+ `MUI X Charts: the value ${x} does not exist in the data of x axis with id ${axisId}.`,
);
}
}
diff --git a/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx b/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx
index e66c086020c6c..376f1c3bfd996 100644
--- a/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx
+++ b/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx
@@ -96,7 +96,7 @@ function ChartsYReferenceLine(props: ChartsYReferenceLineProps) {
if (!warnedOnce) {
warnedOnce = true;
console.error(
- `MUI X: the value ${y} does not exist in the data of y axis with id ${axisId}.`,
+ `MUI X Charts: the value ${y} does not exist in the data of y axis with id ${axisId}.`,
);
}
}
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
index 9b0109cfefb14..102bb42989ea0 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
@@ -109,11 +109,13 @@ ChartsAxisTooltipContent.propTypes = {
axisData: PropTypes.shape({
x: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
y: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
}).isRequired,
classes: PropTypes.object.isRequired,
@@ -123,11 +125,13 @@ ChartsAxisTooltipContent.propTypes = {
axisData: PropTypes.shape({
x: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
y: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
}),
axisValue: PropTypes.any,
diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
index 27ca0f3158cc7..9a708be0ee29f 100644
--- a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx
@@ -79,11 +79,13 @@ DefaultChartsAxisTooltipContent.propTypes = {
axisData: PropTypes.shape({
x: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
y: PropTypes.shape({
index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
+ .isRequired,
}),
}).isRequired,
/**
diff --git a/packages/x-charts/src/LineChart/AnimatedArea.tsx b/packages/x-charts/src/LineChart/AnimatedArea.tsx
new file mode 100644
index 0000000000000..12d27c96c17cd
--- /dev/null
+++ b/packages/x-charts/src/LineChart/AnimatedArea.tsx
@@ -0,0 +1,90 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { styled } from '@mui/material/styles';
+import { color as d3Color } from 'd3-color';
+import { animated, useSpring } from '@react-spring/web';
+import { useAnimatedPath } from '../internals/useAnimatedPath';
+import { DrawingContext } from '../context/DrawingProvider';
+import { cleanId } from '../internals/utils';
+import type { AreaElementOwnerState } from './AreaElement';
+
+export const AreaElementPath = styled(animated.path, {
+ name: 'MuiAreaElement',
+ slot: 'Root',
+ overridesResolver: (_, styles) => styles.root,
+})<{ ownerState: AreaElementOwnerState }>(({ ownerState }) => ({
+ stroke: 'none',
+ fill: ownerState.isHighlighted
+ ? d3Color(ownerState.color)!.brighter(1).formatHex()
+ : d3Color(ownerState.color)!.brighter(0.5).formatHex(),
+ transition: 'opacity 0.2s ease-in, fill 0.2s ease-in',
+ opacity: ownerState.isFaded ? 0.3 : 1,
+}));
+
+export interface AnimatedAreaProps extends React.ComponentPropsWithoutRef<'path'> {
+ ownerState: AreaElementOwnerState;
+ d: string;
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation?: boolean;
+}
+
+/**
+ * Demos:
+ *
+ * - [Lines](https://mui.com/x/react-charts/lines/)
+ * - [Areas demonstration](https://mui.com/x/react-charts/areas-demo/)
+ *
+ * API:
+ *
+ * - [AreaElement API](https://mui.com/x/api/charts/animated-area/)
+ */
+function AnimatedArea(props: AnimatedAreaProps) {
+ const { d, skipAnimation, ownerState, ...other } = props;
+ const { left, top, right, bottom, width, height, chartId } = React.useContext(DrawingContext);
+
+ const path = useAnimatedPath(d!, skipAnimation);
+
+ const { animatedWidth } = useSpring({
+ from: { animatedWidth: left },
+ to: { animatedWidth: width + left + right },
+ reset: false,
+ immediate: skipAnimation,
+ });
+
+ const clipId = cleanId(`${chartId}-${ownerState.id}-area-clip`);
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+AnimatedArea.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ d: PropTypes.string.isRequired,
+ ownerState: PropTypes.shape({
+ classes: PropTypes.object,
+ color: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
+ isFaded: PropTypes.bool.isRequired,
+ isHighlighted: PropTypes.bool.isRequired,
+ }).isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
+} as any;
+
+export { AnimatedArea };
diff --git a/packages/x-charts/src/LineChart/AnimatedLine.tsx b/packages/x-charts/src/LineChart/AnimatedLine.tsx
new file mode 100644
index 0000000000000..593ff6a9d2fe8
--- /dev/null
+++ b/packages/x-charts/src/LineChart/AnimatedLine.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { animated, useSpring } from '@react-spring/web';
+import { color as d3Color } from 'd3-color';
+import { styled } from '@mui/material/styles';
+import { useAnimatedPath } from '../internals/useAnimatedPath';
+import { DrawingContext } from '../context/DrawingProvider';
+import { cleanId } from '../internals/utils';
+import type { LineElementOwnerState } from './LineElement';
+
+export const LineElementPath = styled(animated.path, {
+ name: 'MuiLineElement',
+ slot: 'Root',
+ overridesResolver: (_, styles) => styles.root,
+})<{ ownerState: LineElementOwnerState }>(({ ownerState }) => ({
+ strokeWidth: 2,
+ strokeLinejoin: 'round',
+ fill: 'none',
+ stroke: ownerState.isHighlighted
+ ? d3Color(ownerState.color)!.brighter(0.5).formatHex()
+ : ownerState.color,
+ transition: 'opacity 0.2s ease-in, stroke 0.2s ease-in',
+ opacity: ownerState.isFaded ? 0.3 : 1,
+}));
+
+export interface AnimatedLineProps extends React.ComponentPropsWithoutRef<'path'> {
+ ownerState: LineElementOwnerState;
+ d: string;
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation?: boolean;
+}
+
+/**
+ * Demos:
+ *
+ * - [Lines](https://mui.com/x/react-charts/lines/)
+ * - [Line demonstration](https://mui.com/x/react-charts/line-demo/)
+ *
+ * API:
+ *
+ * - [AnimatedLine API](https://mui.com/x/api/charts/animated-line/)
+ */
+function AnimatedLine(props: AnimatedLineProps) {
+ const { d, skipAnimation, ownerState, ...other } = props;
+ const { left, top, bottom, width, height, right, chartId } = React.useContext(DrawingContext);
+
+ const path = useAnimatedPath(d, skipAnimation);
+
+ const { animatedWidth } = useSpring({
+ from: { animatedWidth: left },
+ to: { animatedWidth: width + left + right },
+ reset: false,
+ immediate: skipAnimation,
+ });
+
+ const clipId = cleanId(`${chartId}-${ownerState.id}-line-clip`);
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+AnimatedLine.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ d: PropTypes.string.isRequired,
+ ownerState: PropTypes.shape({
+ classes: PropTypes.object,
+ color: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
+ isFaded: PropTypes.bool.isRequired,
+ isHighlighted: PropTypes.bool.isRequired,
+ }).isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
+} as any;
+
+export { AnimatedLine };
diff --git a/packages/x-charts/src/LineChart/AreaElement.tsx b/packages/x-charts/src/LineChart/AreaElement.tsx
index d9610d9ec7848..d8819b94cefe4 100644
--- a/packages/x-charts/src/LineChart/AreaElement.tsx
+++ b/packages/x-charts/src/LineChart/AreaElement.tsx
@@ -1,11 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import composeClasses from '@mui/utils/composeClasses';
-import { useSlotProps, SlotComponentProps } from '@mui/base/utils';
+import { useSlotProps } from '@mui/base/utils';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
-import { styled } from '@mui/material/styles';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
-import { color as d3Color } from 'd3-color';
import {
getIsFaded,
getIsHighlighted,
@@ -13,6 +11,7 @@ import {
} from '../hooks/useInteractionItemProps';
import { InteractionContext } from '../context/InteractionProvider';
import { HighlightScope } from '../context/HighlightProvider';
+import { AnimatedArea, AnimatedAreaProps } from './AnimatedArea';
export interface AreaElementClasses {
/** Styles applied to the root element. */
@@ -25,7 +24,7 @@ export interface AreaElementClasses {
export type AreaElementClassKey = keyof AreaElementClasses;
-interface AreaElementOwnerState {
+export interface AreaElementOwnerState {
id: string;
color: string;
isFaded: boolean;
@@ -52,61 +51,34 @@ const useUtilityClasses = (ownerState: AreaElementOwnerState) => {
return composeClasses(slots, getAreaElementUtilityClass, classes);
};
-export const AreaElementPath = styled('path', {
- name: 'MuiAreaElement',
- slot: 'Root',
- overridesResolver: (_, styles) => styles.root,
-})<{ ownerState: AreaElementOwnerState }>(({ ownerState }) => ({
- stroke: 'none',
- fill: ownerState.isHighlighted
- ? d3Color(ownerState.color)!.brighter(1).formatHex()
- : d3Color(ownerState.color)!.brighter(0.5).formatHex(),
- transition: 'opacity 0.2s ease-in, fill 0.2s ease-in',
- opacity: ownerState.isFaded ? 0.3 : 1,
-}));
-
-AreaElementPath.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the TypeScript types and run "yarn proptypes" |
- // ----------------------------------------------------------------------
- as: PropTypes.elementType,
- ownerState: PropTypes.shape({
- classes: PropTypes.object,
- color: PropTypes.string.isRequired,
- id: PropTypes.string.isRequired,
- isFaded: PropTypes.bool.isRequired,
- isHighlighted: PropTypes.bool.isRequired,
- }).isRequired,
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
-} as any;
+export interface AreaElementSlots {
+ /**
+ * The component that renders the area.
+ * @default AnimatedArea
+ */
+ area?: React.JSXElementConstructor;
+}
-export type AreaElementProps = Omit &
- React.ComponentPropsWithoutRef<'path'> & {
- highlightScope?: Partial;
- /**
- * The props used for each component slot.
- * @default {}
- */
- slotProps?: {
- area?: SlotComponentProps<'path', {}, AreaElementOwnerState>;
- };
- /**
- * Overridable component slots.
- * @default {}
- */
- slots?: {
- /**
- * The component that renders the root.
- * @default AreaElementPath
- */
- area?: React.ElementType;
- };
- };
+export interface AreaElementSlotProps {
+ area?: AnimatedAreaProps;
+}
+
+export interface AreaElementProps
+ extends Omit,
+ Pick {
+ d: string;
+ highlightScope?: Partial;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ slotProps?: AreaElementSlotProps;
+ /**
+ * Overridable component slots.
+ * @default {}
+ */
+ slots?: AreaElementSlots;
+}
/**
* Demos:
@@ -120,10 +92,10 @@ export type AreaElementProps = Omit;
}
@@ -157,10 +130,18 @@ AreaElement.propTypes = {
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
classes: PropTypes.object,
+ color: PropTypes.string.isRequired,
+ d: PropTypes.string.isRequired,
highlightScope: PropTypes.shape({
faded: PropTypes.oneOf(['global', 'none', 'series']),
highlighted: PropTypes.oneOf(['item', 'none', 'series']),
}),
+ id: PropTypes.string.isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx
index 786b7a441e5c6..8d4446ba7ff7a 100644
--- a/packages/x-charts/src/LineChart/AreaPlot.tsx
+++ b/packages/x-charts/src/LineChart/AreaPlot.tsx
@@ -3,22 +3,90 @@ import PropTypes from 'prop-types';
import { area as d3Area } from 'd3-shape';
import { SeriesContext } from '../context/SeriesContextProvider';
import { CartesianContext } from '../context/CartesianContextProvider';
-import { AreaElement, AreaElementProps } from './AreaElement';
+import {
+ AreaElement,
+ AreaElementProps,
+ AreaElementSlotProps,
+ AreaElementSlots,
+} from './AreaElement';
import { getValueToPositionMapper } from '../hooks/useScale';
import getCurveFactory from '../internals/getCurve';
import { DEFAULT_X_AXIS_KEY } from '../constants';
-export interface AreaPlotSlots {
- area?: React.JSXElementConstructor;
-}
+export interface AreaPlotSlots extends AreaElementSlots {}
-export interface AreaPlotSlotProps {
- area?: Partial;
-}
+export interface AreaPlotSlotProps extends AreaElementSlotProps {}
export interface AreaPlotProps
extends React.SVGAttributes,
- Pick {}
+ Pick {}
+
+const useAggregatedData = () => {
+ const seriesData = React.useContext(SeriesContext).line;
+ const axisData = React.useContext(CartesianContext);
+
+ if (seriesData === undefined) {
+ return [];
+ }
+
+ const { series, stackingGroups } = seriesData;
+ const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData;
+ const defaultXAxisId = xAxisIds[0];
+ const defaultYAxisId = yAxisIds[0];
+
+ return stackingGroups.flatMap(({ ids: groupIds }) => {
+ return groupIds.flatMap((seriesId) => {
+ const {
+ xAxisKey = defaultXAxisId,
+ yAxisKey = defaultYAxisId,
+ stackedData,
+ data,
+ connectNulls,
+ } = series[seriesId];
+
+ const xScale = getValueToPositionMapper(xAxis[xAxisKey].scale);
+ const yScale = yAxis[yAxisKey].scale;
+ const xData = xAxis[xAxisKey].data;
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (xData === undefined) {
+ throw new Error(
+ `MUI X Charts: ${
+ xAxisKey === DEFAULT_X_AXIS_KEY
+ ? 'The first `xAxis`'
+ : `The x-axis with id "${xAxisKey}"`
+ } should have data property to be able to display a line plot.`,
+ );
+ }
+ if (xData.length < stackedData.length) {
+ throw new Error(
+ `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`,
+ );
+ }
+ }
+
+ const areaPath = d3Area<{
+ x: any;
+ y: [number, number];
+ }>()
+ .x((d) => xScale(d.x))
+ .defined((_, i) => connectNulls || data[i] != null)
+ .y0((d) => d.y && yScale(d.y[0])!)
+ .y1((d) => d.y && yScale(d.y[1])!);
+
+ const curve = getCurveFactory(series[seriesId].curve);
+ const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? [];
+ const d3Data = connectNulls ? formattedData.filter((_, i) => data[i] != null) : formattedData;
+
+ const d = areaPath.curve(curve)(d3Data) || '';
+ return {
+ ...series[seriesId],
+ d,
+ seriesId,
+ };
+ });
+ });
+};
/**
* Demos:
@@ -32,82 +100,29 @@ export interface AreaPlotProps
* - [AreaPlot API](https://mui.com/x/api/charts/area-plot/)
*/
function AreaPlot(props: AreaPlotProps) {
- const { slots, slotProps, ...other } = props;
+ const { slots, slotProps, skipAnimation, ...other } = props;
- const seriesData = React.useContext(SeriesContext).line;
- const axisData = React.useContext(CartesianContext);
-
- if (seriesData === undefined) {
- return null;
- }
- const { series, stackingGroups } = seriesData;
- const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData;
- const defaultXAxisId = xAxisIds[0];
- const defaultYAxisId = yAxisIds[0];
+ const completedData = useAggregatedData();
return (
- {stackingGroups.flatMap(({ ids: groupIds }) => {
- return groupIds.flatMap((seriesId) => {
- const {
- xAxisKey = defaultXAxisId,
- yAxisKey = defaultYAxisId,
- stackedData,
- data,
- connectNulls,
- } = series[seriesId];
-
- const xScale = getValueToPositionMapper(xAxis[xAxisKey].scale);
- const yScale = yAxis[yAxisKey].scale;
- const xData = xAxis[xAxisKey].data;
-
- if (process.env.NODE_ENV !== 'production') {
- if (xData === undefined) {
- throw new Error(
- `MUI X Charts: ${
- xAxisKey === DEFAULT_X_AXIS_KEY
- ? 'The first `xAxis`'
- : `The x-axis with id "${xAxisKey}"`
- } should have data property to be able to display a line plot.`,
- );
- }
- if (xData.length < stackedData.length) {
- throw new Error(
- `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`,
- );
- }
- }
-
- const areaPath = d3Area<{
- x: any;
- y: [number, number];
- }>()
- .x((d) => xScale(d.x))
- .defined((_, i) => connectNulls || data[i] != null)
- .y0((d) => d.y && yScale(d.y[0])!)
- .y1((d) => d.y && yScale(d.y[1])!);
-
- const curve = getCurveFactory(series[seriesId].curve);
- const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? [];
- const d3Data = connectNulls
- ? formattedData.filter((_, i) => data[i] != null)
- : formattedData;
-
- return (
- !!series[seriesId].area && (
+ {completedData
+ .reverse()
+ .map(
+ ({ d, seriesId, color, highlightScope, area }) =>
+ !!area && (
- )
- );
- });
- })}
+ ),
+ )}
);
}
@@ -117,6 +132,11 @@ AreaPlot.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx
index 8fcc82e7cf92e..2e0d71190d8ee 100644
--- a/packages/x-charts/src/LineChart/LineChart.tsx
+++ b/packages/x-charts/src/LineChart/LineChart.tsx
@@ -82,6 +82,11 @@ export interface LineChartProps
* @default {}
*/
slotProps?: LineChartSlotProps;
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation?: boolean;
}
/**
@@ -116,6 +121,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref
children,
slots,
slotProps,
+ skipAnimation,
} = props;
const id = useId();
@@ -153,8 +159,8 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref
}
>
-
-
+
+
-
+
@@ -348,6 +354,11 @@ LineChart.propTypes = {
PropTypes.string,
]),
series: PropTypes.arrayOf(PropTypes.object).isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/LineElement.tsx b/packages/x-charts/src/LineChart/LineElement.tsx
index 14d13e28598fb..a03e2486c81c6 100644
--- a/packages/x-charts/src/LineChart/LineElement.tsx
+++ b/packages/x-charts/src/LineChart/LineElement.tsx
@@ -1,10 +1,8 @@
import * as React from 'react';
import PropTypes from 'prop-types';
-import { color as d3Color } from 'd3-color';
import composeClasses from '@mui/utils/composeClasses';
-import { useSlotProps, SlotComponentProps } from '@mui/base/utils';
+import { useSlotProps } from '@mui/base/utils';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
-import { styled } from '@mui/material/styles';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import { InteractionContext } from '../context/InteractionProvider';
import {
@@ -13,6 +11,7 @@ import {
useInteractionItemProps,
} from '../hooks/useInteractionItemProps';
import { HighlightScope } from '../context/HighlightProvider';
+import { AnimatedLine, AnimatedLineProps } from './AnimatedLine';
export interface LineElementClasses {
/** Styles applied to the root element. */
@@ -25,7 +24,7 @@ export interface LineElementClasses {
export type LineElementClassKey = keyof LineElementClasses;
-interface LineElementOwnerState {
+export interface LineElementOwnerState {
id: string;
color: string;
isFaded: boolean;
@@ -52,63 +51,34 @@ const useUtilityClasses = (ownerState: LineElementOwnerState) => {
return composeClasses(slots, getLineElementUtilityClass, classes);
};
-export const LineElementPath = styled('path', {
- name: 'MuiLineElement',
- slot: 'Root',
- overridesResolver: (_, styles) => styles.root,
-})<{ ownerState: LineElementOwnerState }>(({ ownerState }) => ({
- strokeWidth: 2,
- strokeLinejoin: 'round',
- fill: 'none',
- stroke: ownerState.isHighlighted
- ? d3Color(ownerState.color)!.brighter(0.5).formatHex()
- : ownerState.color,
- transition: 'opacity 0.2s ease-in, stroke 0.2s ease-in',
- opacity: ownerState.isFaded ? 0.3 : 1,
-}));
-
-LineElementPath.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the TypeScript types and run "yarn proptypes" |
- // ----------------------------------------------------------------------
- as: PropTypes.elementType,
- ownerState: PropTypes.shape({
- classes: PropTypes.object,
- color: PropTypes.string.isRequired,
- id: PropTypes.string.isRequired,
- isFaded: PropTypes.bool.isRequired,
- isHighlighted: PropTypes.bool.isRequired,
- }).isRequired,
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
-} as any;
+export interface LineElementSlots {
+ /**
+ * The component that renders the line.
+ * @default LineElementPath
+ */
+ line?: React.JSXElementConstructor;
+}
-export type LineElementProps = Omit &
- React.ComponentPropsWithoutRef<'path'> & {
- highlightScope?: Partial;
- /**
- * The props used for each component slot.
- * @default {}
- */
- slotProps?: {
- line?: SlotComponentProps<'path', {}, LineElementOwnerState>;
- };
- /**
- * Overridable component slots.
- * @default {}
- */
- slots?: {
- /**
- * The component that renders the root.
- * @default LineElementPath
- */
- line?: React.ElementType;
- };
- };
+export interface LineElementSlotProps {
+ line?: AnimatedLineProps;
+}
+
+export interface LineElementProps
+ extends Omit,
+ Pick {
+ d: string;
+ highlightScope?: Partial;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ slotProps?: LineElementSlotProps;
+ /**
+ * Overridable component slots.
+ * @default {}
+ */
+ slots?: LineElementSlots;
+}
/**
* Demos:
@@ -122,7 +92,6 @@ export type LineElementProps = Omit;
}
@@ -160,10 +130,18 @@ LineElement.propTypes = {
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
classes: PropTypes.object,
+ color: PropTypes.string.isRequired,
+ d: PropTypes.string.isRequired,
highlightScope: PropTypes.shape({
faded: PropTypes.oneOf(['global', 'none', 'series']),
highlighted: PropTypes.oneOf(['item', 'none', 'series']),
}),
+ id: PropTypes.string.isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx
index e97b47097c6c0..1d180d423a323 100644
--- a/packages/x-charts/src/LineChart/LinePlot.tsx
+++ b/packages/x-charts/src/LineChart/LinePlot.tsx
@@ -3,22 +3,88 @@ import PropTypes from 'prop-types';
import { line as d3Line } from 'd3-shape';
import { SeriesContext } from '../context/SeriesContextProvider';
import { CartesianContext } from '../context/CartesianContextProvider';
-import { LineElement, LineElementProps } from './LineElement';
+import {
+ LineElement,
+ LineElementProps,
+ LineElementSlotProps,
+ LineElementSlots,
+} from './LineElement';
import { getValueToPositionMapper } from '../hooks/useScale';
import getCurveFactory from '../internals/getCurve';
import { DEFAULT_X_AXIS_KEY } from '../constants';
-export interface LinePlotSlots {
- line?: React.JSXElementConstructor;
-}
+export interface LinePlotSlots extends LineElementSlots {}
-export interface LinePlotSlotProps {
- line?: Partial;
-}
+export interface LinePlotSlotProps extends LineElementSlotProps {}
export interface LinePlotProps
extends React.SVGAttributes,
- Pick {}
+ Pick {}
+
+const useAggregatedData = () => {
+ const seriesData = React.useContext(SeriesContext).line;
+ const axisData = React.useContext(CartesianContext);
+
+ if (seriesData === undefined) {
+ return [];
+ }
+
+ const { series, stackingGroups } = seriesData;
+ const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData;
+ const defaultXAxisId = xAxisIds[0];
+ const defaultYAxisId = yAxisIds[0];
+
+ return stackingGroups.flatMap(({ ids: groupIds }) => {
+ return groupIds.flatMap((seriesId) => {
+ const {
+ xAxisKey = defaultXAxisId,
+ yAxisKey = defaultYAxisId,
+ stackedData,
+ data,
+ connectNulls,
+ } = series[seriesId];
+
+ const xScale = getValueToPositionMapper(xAxis[xAxisKey].scale);
+ const yScale = yAxis[yAxisKey].scale;
+ const xData = xAxis[xAxisKey].data;
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (xData === undefined) {
+ throw new Error(
+ `MUI X Charts: ${
+ xAxisKey === DEFAULT_X_AXIS_KEY
+ ? 'The first `xAxis`'
+ : `The x-axis with id "${xAxisKey}"`
+ } should have data property to be able to display a line plot.`,
+ );
+ }
+ if (xData.length < stackedData.length) {
+ throw new Error(
+ `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`,
+ );
+ }
+ }
+
+ const linePath = d3Line<{
+ x: any;
+ y: [number, number];
+ }>()
+ .x((d) => xScale(d.x))
+ .defined((_, i) => connectNulls || data[i] != null)
+ .y((d) => yScale(d.y[1])!);
+
+ const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? [];
+ const d3Data = connectNulls ? formattedData.filter((_, i) => data[i] != null) : formattedData;
+
+ const d = linePath.curve(getCurveFactory(series[seriesId].curve))(d3Data) || '';
+ return {
+ ...series[seriesId],
+ d,
+ seriesId,
+ };
+ });
+ });
+};
/**
* Demos:
@@ -31,77 +97,25 @@ export interface LinePlotProps
* - [LinePlot API](https://mui.com/x/api/charts/line-plot/)
*/
function LinePlot(props: LinePlotProps) {
- const { slots, slotProps, ...other } = props;
- const seriesData = React.useContext(SeriesContext).line;
- const axisData = React.useContext(CartesianContext);
+ const { slots, slotProps, skipAnimation, ...other } = props;
- if (seriesData === undefined) {
- return null;
- }
- const { series, stackingGroups } = seriesData;
- const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData;
- const defaultXAxisId = xAxisIds[0];
- const defaultYAxisId = yAxisIds[0];
+ const completedData = useAggregatedData();
return (
- {stackingGroups.flatMap(({ ids: groupIds }) => {
- return groupIds.flatMap((seriesId) => {
- const {
- xAxisKey = defaultXAxisId,
- yAxisKey = defaultYAxisId,
- stackedData,
- data,
- connectNulls,
- } = series[seriesId];
-
- const xScale = getValueToPositionMapper(xAxis[xAxisKey].scale);
- const yScale = yAxis[yAxisKey].scale;
- const xData = xAxis[xAxisKey].data;
-
- if (process.env.NODE_ENV !== 'production') {
- if (xData === undefined) {
- throw new Error(
- `MUI X Charts: ${
- xAxisKey === DEFAULT_X_AXIS_KEY
- ? 'The first `xAxis`'
- : `The x-axis with id "${xAxisKey}"`
- } should have data property to be able to display a line plot.`,
- );
- }
- if (xData.length < stackedData.length) {
- throw new Error(
- `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`,
- );
- }
- }
-
- const linePath = d3Line<{
- x: any;
- y: [number, number];
- }>()
- .x((d) => xScale(d.x))
- .defined((_, i) => connectNulls || data[i] != null)
- .y((d) => yScale(d.y[1])!);
-
- const curve = getCurveFactory(series[seriesId].curve);
- const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? [];
- const d3Data = connectNulls
- ? formattedData.filter((_, i) => data[i] != null)
- : formattedData;
-
- return (
-
- );
- });
+ {completedData.map(({ d, seriesId, color, highlightScope }) => {
+ return (
+
+ );
})}
);
@@ -112,6 +126,11 @@ LinePlot.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/MarkElement.tsx b/packages/x-charts/src/LineChart/MarkElement.tsx
index 83af7d050517b..de78d5b1e8d8e 100644
--- a/packages/x-charts/src/LineChart/MarkElement.tsx
+++ b/packages/x-charts/src/LineChart/MarkElement.tsx
@@ -5,6 +5,7 @@ import generateUtilityClass from '@mui/utils/generateUtilityClass';
import { styled } from '@mui/material/styles';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import { symbol as d3Symbol, symbolsFill as d3SymbolsFill } from 'd3-shape';
+import { animated, to, useSpring } from '@react-spring/web';
import { getSymbol } from '../internals/utils';
import { InteractionContext } from '../context/InteractionProvider';
import { HighlightScope } from '../context/HighlightProvider';
@@ -30,8 +31,6 @@ interface MarkElementOwnerState {
color: string;
isFaded: boolean;
isHighlighted: boolean;
- x: number;
- y: number;
classes?: Partial;
}
@@ -54,13 +53,11 @@ const useUtilityClasses = (ownerState: MarkElementOwnerState) => {
return composeClasses(slots, getMarkElementUtilityClass, classes);
};
-const MarkElementPath = styled('path', {
+const MarkElementPath = styled(animated.path, {
name: 'MuiMarkElement',
slot: 'Root',
overridesResolver: (_, styles) => styles.root,
})<{ ownerState: MarkElementOwnerState }>(({ ownerState, theme }) => ({
- transform: `translate(${ownerState.x}px, ${ownerState.y}px)`,
- transformOrigin: `${ownerState.x}px ${ownerState.y}px`,
fill: (theme.vars || theme).palette.background.paper,
stroke: ownerState.color,
strokeWidth: 2,
@@ -78,8 +75,6 @@ MarkElementPath.propTypes = {
id: PropTypes.string.isRequired,
isFaded: PropTypes.bool.isRequired,
isHighlighted: PropTypes.bool.isRequired,
- x: PropTypes.number.isRequired,
- y: PropTypes.number.isRequired,
}).isRequired,
sx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
@@ -90,6 +85,11 @@ MarkElementPath.propTypes = {
export type MarkElementProps = Omit &
React.ComponentPropsWithoutRef<'path'> & {
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation?: boolean;
/**
* The shape of the marker.
*/
@@ -121,6 +121,7 @@ function MarkElement(props: MarkElementProps) {
shape,
dataIndex,
highlightScope,
+ skipAnimation,
...other
} = props;
@@ -134,20 +135,23 @@ function MarkElement(props: MarkElementProps) {
const isFaded =
!isHighlighted && getIsFaded(item, { type: 'line', seriesId: id }, highlightScope);
+ const position = useSpring({ x, y, immediate: skipAnimation });
const ownerState = {
id,
classes: innerClasses,
isHighlighted,
isFaded,
color,
- x,
- y,
};
const classes = useUtilityClasses(ownerState);
return (
`translate(${pX}px, ${pY}px)`),
+ transformOrigin: to([position.x, position.y], (pX, pY) => `${pX}px ${pY}px`),
+ }}
ownerState={ownerState}
className={classes.root}
d={d3Symbol(d3SymbolsFill[getSymbol(shape)])()!}
@@ -175,6 +179,11 @@ MarkElement.propTypes = {
*/
shape: PropTypes.oneOf(['circle', 'cross', 'diamond', 'square', 'star', 'triangle', 'wye'])
.isRequired,
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
} as any;
export { MarkElement };
diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx
index c0916c449fd8e..69baf3061cbdf 100644
--- a/packages/x-charts/src/LineChart/MarkPlot.tsx
+++ b/packages/x-charts/src/LineChart/MarkPlot.tsx
@@ -5,6 +5,8 @@ import { CartesianContext } from '../context/CartesianContextProvider';
import { MarkElement, MarkElementProps } from './MarkElement';
import { getValueToPositionMapper } from '../hooks/useScale';
import { DEFAULT_X_AXIS_KEY } from '../constants';
+import { DrawingContext } from '../context/DrawingProvider';
+import { cleanId } from '../internals/utils';
export interface MarkPlotSlots {
mark?: React.JSXElementConstructor;
@@ -14,7 +16,9 @@ export interface MarkPlotSlotProps {
mark?: Partial;
}
-export interface MarkPlotProps extends React.SVGAttributes {
+export interface MarkPlotProps
+ extends React.SVGAttributes,
+ Pick {
/**
* Overridable component slots.
* @default {}
@@ -38,10 +42,11 @@ export interface MarkPlotProps extends React.SVGAttributes {
* - [MarkPlot API](https://mui.com/x/api/charts/mark-plot/)
*/
function MarkPlot(props: MarkPlotProps) {
- const { slots, slotProps, ...other } = props;
+ const { slots, slotProps, skipAnimation, ...other } = props;
const seriesData = React.useContext(SeriesContext).line;
const axisData = React.useContext(CartesianContext);
+ const { chartId } = React.useContext(DrawingContext);
const Mark = slots?.mark ?? MarkElement;
@@ -56,7 +61,7 @@ function MarkPlot(props: MarkPlotProps) {
return (
{stackingGroups.flatMap(({ ids: groupIds }) => {
- return groupIds.flatMap((seriesId) => {
+ return groupIds.map((seriesId) => {
const {
xAxisKey = defaultXAxisId,
yAxisKey = defaultYAxisId,
@@ -96,52 +101,59 @@ function MarkPlot(props: MarkPlotProps) {
);
}
- return xData
- ?.map((x, index) => {
- const value = data[index] == null ? null : stackedData[index][1];
- return {
- x: xScale(x),
- y: value === null ? null : yScale(value)!,
- position: x,
- value,
- index,
- };
- })
- .filter(({ x, y, index, position, value }) => {
- if (value === null || y === null) {
- // Remove missing data point
- return false;
- }
- if (!isInRange({ x, y })) {
- // Remove out of range
- return false;
- }
- if (showMark === true) {
- return true;
- }
- return showMark({
- x,
- y,
- index,
- position,
- value,
- });
- })
- .map(({ x, y, index }) => {
- return (
-
- );
- });
+ const clipId = cleanId(`${chartId}-${seriesId}-line-clip`); // We assume that if displaying line mark, the line will also be rendered
+
+ return (
+
+ {xData
+ ?.map((x, index) => {
+ const value = data[index] == null ? null : stackedData[index][1];
+ return {
+ x: xScale(x),
+ y: value === null ? null : yScale(value)!,
+ position: x,
+ value,
+ index,
+ };
+ })
+ .filter(({ x, y, index, position, value }) => {
+ if (value === null || y === null) {
+ // Remove missing data point
+ return false;
+ }
+ if (!isInRange({ x, y })) {
+ // Remove out of range
+ return false;
+ }
+ if (showMark === true) {
+ return true;
+ }
+ return showMark({
+ x,
+ y,
+ index,
+ position,
+ value,
+ });
+ })
+ .map(({ x, y, index }) => {
+ return (
+
+ );
+ })}
+
+ );
});
})}
@@ -153,6 +165,11 @@ MarkPlot.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
+ /**
+ * If `true`, animations are skipped.
+ * @default false
+ */
+ skipAnimation: PropTypes.bool,
/**
* The props used for each component slot.
* @default {}
diff --git a/packages/x-charts/src/LineChart/index.tsx b/packages/x-charts/src/LineChart/index.tsx
index 742fba2f4d38f..6ac8144590359 100644
--- a/packages/x-charts/src/LineChart/index.tsx
+++ b/packages/x-charts/src/LineChart/index.tsx
@@ -6,6 +6,8 @@ export * from './MarkPlot';
export * from './LineHighlightPlot';
export * from './AreaElement';
+export * from './AnimatedArea';
export * from './LineElement';
+export * from './AnimatedLine';
export * from './MarkElement';
export * from './LineHighlightElement';
diff --git a/packages/x-charts/src/PieChart/PieArcLabelPlot.tsx b/packages/x-charts/src/PieChart/PieArcLabelPlot.tsx
index 6ce3782a109f1..831369e981057 100644
--- a/packages/x-charts/src/PieChart/PieArcLabelPlot.tsx
+++ b/packages/x-charts/src/PieChart/PieArcLabelPlot.tsx
@@ -75,7 +75,7 @@ export interface PieArcLabelPlotProps
*/
slotProps?: PieArcLabelPlotSlotProps;
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation?: boolean;
@@ -249,7 +249,7 @@ PieArcLabelPlot.propTypes = {
*/
paddingAngle: PropTypes.number,
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/PieChart/PieArcPlot.tsx b/packages/x-charts/src/PieChart/PieArcPlot.tsx
index ae3d9f200b036..1db0508cc4175 100644
--- a/packages/x-charts/src/PieChart/PieArcPlot.tsx
+++ b/packages/x-charts/src/PieChart/PieArcPlot.tsx
@@ -56,7 +56,7 @@ export interface PieArcPlotProps
item: DefaultizedPieValueType,
) => void;
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation?: boolean;
@@ -230,7 +230,7 @@ PieArcPlot.propTypes = {
*/
paddingAngle: PropTypes.number,
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx
index 95a7d6bd04a3b..cb0cd54077d67 100644
--- a/packages/x-charts/src/PieChart/PieChart.tsx
+++ b/packages/x-charts/src/PieChart/PieChart.tsx
@@ -321,7 +321,7 @@ PieChart.propTypes = {
]),
series: PropTypes.arrayOf(PropTypes.object).isRequired,
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/PieChart/PiePlot.tsx b/packages/x-charts/src/PieChart/PiePlot.tsx
index 35733978c672c..198ef8accc157 100644
--- a/packages/x-charts/src/PieChart/PiePlot.tsx
+++ b/packages/x-charts/src/PieChart/PiePlot.tsx
@@ -156,7 +156,7 @@ PiePlot.propTypes = {
*/
onClick: PropTypes.func,
/**
- * If `true`, animations are skiped.
+ * If `true`, animations are skipped.
* @default false
*/
skipAnimation: PropTypes.bool,
diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
index 1ecb4634511a6..8214bec2e2fe0 100644
--- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
+++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
@@ -197,8 +197,8 @@ const SparkLineChart = React.forwardRef(function SparkLineChart(props: SparkLine
{plotType === 'line' && (
-
-
+
+
)}
diff --git a/packages/x-charts/src/context/DrawingProvider.tsx b/packages/x-charts/src/context/DrawingProvider.tsx
index 9311c045e2e2f..4429fabffba66 100644
--- a/packages/x-charts/src/context/DrawingProvider.tsx
+++ b/packages/x-charts/src/context/DrawingProvider.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import PropTypes from 'prop-types';
+import useId from '@mui/utils/useId';
import useChartDimensions from '../hooks/useChartDimensions';
import { LayoutConfig } from '../models/layout';
@@ -38,13 +39,21 @@ export type DrawingArea = {
height: number;
};
-export const DrawingContext = React.createContext({
+export const DrawingContext = React.createContext<
+ DrawingArea & {
+ /**
+ * A random id used to distinguish each chart on the same page.
+ */
+ chartId: string;
+ }
+>({
top: 0,
left: 0,
bottom: 0,
right: 0,
height: 300,
width: 400,
+ chartId: '',
});
export const SVGContext = React.createContext>({ current: null });
@@ -56,10 +65,16 @@ export const SVGContext = React.createContext>({
function DrawingProvider(props: DrawingProviderProps) {
const { width, height, margin, svgRef, children } = props;
const drawingArea = useChartDimensions(width, height, margin);
+ const chartId = useId();
+
+ const value = React.useMemo(
+ () => ({ chartId: chartId ?? '', ...drawingArea }),
+ [chartId, drawingArea],
+ );
return (
- {children}
+ {children}
);
}
diff --git a/packages/x-charts/src/context/InteractionProvider.tsx b/packages/x-charts/src/context/InteractionProvider.tsx
index bf82c6323ca5b..a8c3f75d37fe9 100644
--- a/packages/x-charts/src/context/InteractionProvider.tsx
+++ b/packages/x-charts/src/context/InteractionProvider.tsx
@@ -9,11 +9,11 @@ export type ItemInteractionData = ChartItemIdentifier
export type AxisInteractionData = {
x: null | {
- value: number | Date;
+ value: number | Date | string;
index?: number;
};
y: null | {
- value: number | Date;
+ value: number | Date | string;
index?: number;
};
};
diff --git a/packages/x-charts/src/internals/geometry.ts b/packages/x-charts/src/internals/geometry.ts
index 43fd024cacc39..593ba18e20a0b 100644
--- a/packages/x-charts/src/internals/geometry.ts
+++ b/packages/x-charts/src/internals/geometry.ts
@@ -16,7 +16,7 @@ export function getMinXTranslation(width: number, height: number, angle: number
warnedOnce = true;
console.warn(
[
- `MUI X: It seems you applied an angle larger than 90° or smaller than -90° to an axis text.`,
+ `MUI X Charts: It seems you applied an angle larger than 90° or smaller than -90° to an axis text.`,
`This could cause some text overlapping.`,
`If you encounter a use case where it's needed, please open an issue.`,
].join('\n'),
diff --git a/packages/x-charts/src/internals/useAnimatedPath.ts b/packages/x-charts/src/internals/useAnimatedPath.ts
new file mode 100644
index 0000000000000..034302c08f7bf
--- /dev/null
+++ b/packages/x-charts/src/internals/useAnimatedPath.ts
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import { interpolateString } from 'd3-interpolate';
+import { useSpring, to } from '@react-spring/web';
+
+function usePrevious(value: T) {
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ ref.current = value;
+ }, [value]);
+ return ref.current;
+}
+
+// Taken from Nivo
+export const useAnimatedPath = (path: string, skipAnimation?: boolean) => {
+ const previousPath = usePrevious(path);
+ const interpolator = React.useMemo(
+ () => (previousPath ? interpolateString(previousPath, path) : () => path),
+ [previousPath, path],
+ );
+
+ const { value } = useSpring({
+ from: { value: 0 },
+ to: { value: 1 },
+ reset: true,
+ immediate: skipAnimation,
+ });
+
+ return to([value], interpolator);
+};
diff --git a/packages/x-charts/src/internals/utils.ts b/packages/x-charts/src/internals/utils.ts
index 81a79b389ab4f..8dfdd4ea9d826 100644
--- a/packages/x-charts/src/internals/utils.ts
+++ b/packages/x-charts/src/internals/utils.ts
@@ -51,3 +51,10 @@ export function getPercentageValue(value: number | string, refValue: number) {
`MUI-Charts: Received an unknown value "${value}". It should be a number, or a string with a percentage value.`,
);
}
+
+/**
+ * Remove spaces to have viable ids
+ */
+export function cleanId(id: string) {
+ return id.replace(' ', '_');
+}
diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json
index 46daf646da99a..49a150ddaa4b9 100644
--- a/scripts/x-charts.exports.json
+++ b/scripts/x-charts.exports.json
@@ -3,12 +3,19 @@
{ "name": "AnchorPosition", "kind": "TypeAlias" },
{ "name": "AnchorX", "kind": "TypeAlias" },
{ "name": "AnchorY", "kind": "TypeAlias" },
+ { "name": "AnimatedArea", "kind": "Function" },
+ { "name": "AnimatedAreaProps", "kind": "Interface" },
+ { "name": "AnimatedLine", "kind": "Function" },
+ { "name": "AnimatedLineProps", "kind": "Interface" },
{ "name": "AreaElement", "kind": "Function" },
{ "name": "areaElementClasses", "kind": "Variable" },
{ "name": "AreaElementClasses", "kind": "Interface" },
{ "name": "AreaElementClassKey", "kind": "TypeAlias" },
+ { "name": "AreaElementOwnerState", "kind": "Interface" },
{ "name": "AreaElementPath", "kind": "Variable" },
- { "name": "AreaElementProps", "kind": "TypeAlias" },
+ { "name": "AreaElementProps", "kind": "Interface" },
+ { "name": "AreaElementSlotProps", "kind": "Interface" },
+ { "name": "AreaElementSlots", "kind": "Interface" },
{ "name": "AreaPlot", "kind": "Function" },
{ "name": "AreaPlotProps", "kind": "Interface" },
{ "name": "AreaPlotSlotProps", "kind": "Interface" },
@@ -137,8 +144,11 @@
{ "name": "lineElementClasses", "kind": "Variable" },
{ "name": "LineElementClasses", "kind": "Interface" },
{ "name": "LineElementClassKey", "kind": "TypeAlias" },
+ { "name": "LineElementOwnerState", "kind": "Interface" },
{ "name": "LineElementPath", "kind": "Variable" },
- { "name": "LineElementProps", "kind": "TypeAlias" },
+ { "name": "LineElementProps", "kind": "Interface" },
+ { "name": "LineElementSlotProps", "kind": "Interface" },
+ { "name": "LineElementSlots", "kind": "Interface" },
{ "name": "LineHighlightElement", "kind": "Function" },
{ "name": "lineHighlightElementClasses", "kind": "Variable" },
{ "name": "LineHighlightElementClasses", "kind": "Interface" },
diff --git a/yarn.lock b/yarn.lock
index c27e8bdaea853..961dbf4ef5199 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2962,7 +2962,7 @@
dependencies:
"@types/node" "*"
-"@types/d3-color@^3.1.3":
+"@types/d3-color@*", "@types/d3-color@^3.1.3":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
@@ -2972,6 +2972,13 @@
resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1"
integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==
+"@types/d3-interpolate@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
+ integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
+ dependencies:
+ "@types/d3-color" "*"
+
"@types/d3-path@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.2.tgz#4327f4a05d475cf9be46a93fc2e0f8d23380805a"
@@ -5763,7 +5770,7 @@ d3-delaunay@^6.0.4:
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
-"d3-interpolate@1.2.0 - 3":
+"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==