diff --git a/docs/data/charts/scatter/ScatterZAxis.js b/docs/data/charts/scatter/ScatterZAxis.js
new file mode 100644
index 0000000000000..597569e0457dd
--- /dev/null
+++ b/docs/data/charts/scatter/ScatterZAxis.js
@@ -0,0 +1,240 @@
+import * as React from 'react';
+import { ScatterChart } from '@mui/x-charts/ScatterChart';
+import { useZAxis, useScatterSeries } from '@mui/x-charts/hooks';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import { ChartsDataProvider } from '@mui/x-charts/ChartsDataProvider';
+import { darken } from '@mui/material/styles';
+
+// The Iris flower dataset, with sepal length and width mapped to the x and y axes, respectively,
+// and species and petal length mapped to two separate z-axes for color and size encoding.
+const irisData = [
+ { id: 1, x: 5.1, y: 3.5, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 2, x: 4.9, y: 3, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 3, x: 4.7, y: 3.2, colorValue: 'Setosa', sizeValue: 1.3 },
+ { id: 4, x: 5, y: 3.6, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 5, x: 5.4, y: 3.9, colorValue: 'Setosa', sizeValue: 1.7 },
+ { id: 6, x: 4.6, y: 3.4, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 7, x: 5, y: 3.4, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 8, x: 4.4, y: 2.9, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 9, x: 5.4, y: 3.7, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 10, x: 4.8, y: 3.4, colorValue: 'Setosa', sizeValue: 1.6 },
+ { id: 11, x: 5.8, y: 4, colorValue: 'Setosa', sizeValue: 1.2 },
+ { id: 12, x: 5.7, y: 4.4, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 13, x: 5.4, y: 3.4, colorValue: 'Setosa', sizeValue: 1.7 },
+ { id: 14, x: 5.1, y: 3.7, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 15, x: 4.6, y: 3.6, colorValue: 'Setosa', sizeValue: 1 },
+ { id: 16, x: 7, y: 3.2, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 17, x: 6.4, y: 3.2, colorValue: 'Versicolor', sizeValue: 4.5 },
+ { id: 18, x: 6.9, y: 3.1, colorValue: 'Versicolor', sizeValue: 4.9 },
+ { id: 19, x: 5.5, y: 2.3, colorValue: 'Versicolor', sizeValue: 4 },
+ { id: 20, x: 6.5, y: 2.8, colorValue: 'Versicolor', sizeValue: 4.6 },
+ { id: 21, x: 5.7, y: 2.8, colorValue: 'Versicolor', sizeValue: 4.5 },
+ { id: 22, x: 6.3, y: 3.3, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 23, x: 4.9, y: 2.4, colorValue: 'Versicolor', sizeValue: 3.3 },
+ { id: 24, x: 6.6, y: 2.9, colorValue: 'Versicolor', sizeValue: 4.6 },
+ { id: 25, x: 5.2, y: 2.7, colorValue: 'Versicolor', sizeValue: 3.9 },
+ { id: 26, x: 5, y: 2, colorValue: 'Versicolor', sizeValue: 3.5 },
+ { id: 27, x: 5.9, y: 3, colorValue: 'Versicolor', sizeValue: 4.2 },
+ { id: 28, x: 6, y: 2.2, colorValue: 'Versicolor', sizeValue: 4 },
+ { id: 29, x: 6.1, y: 2.9, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 30, x: 5.6, y: 2.9, colorValue: 'Versicolor', sizeValue: 3.6 },
+ { id: 31, x: 6.3, y: 3.3, colorValue: 'Virginica', sizeValue: 6 },
+ { id: 32, x: 5.8, y: 2.7, colorValue: 'Virginica', sizeValue: 5.1 },
+ { id: 33, x: 7.1, y: 3, colorValue: 'Virginica', sizeValue: 5.9 },
+ { id: 34, x: 6.3, y: 2.9, colorValue: 'Virginica', sizeValue: 5.6 },
+ { id: 35, x: 6.5, y: 3, colorValue: 'Virginica', sizeValue: 5.8 },
+ { id: 36, x: 7.6, y: 3, colorValue: 'Virginica', sizeValue: 6.6 },
+ { id: 37, x: 4.9, y: 2.5, colorValue: 'Virginica', sizeValue: 4.5 },
+ { id: 38, x: 7.3, y: 2.9, colorValue: 'Virginica', sizeValue: 6.3 },
+ { id: 39, x: 6.7, y: 2.5, colorValue: 'Virginica', sizeValue: 5.8 },
+ { id: 40, x: 7.2, y: 3.6, colorValue: 'Virginica', sizeValue: 6.1 },
+ { id: 41, x: 6.5, y: 3.2, colorValue: 'Virginica', sizeValue: 5.1 },
+ { id: 42, x: 6.4, y: 2.7, colorValue: 'Virginica', sizeValue: 5.3 },
+ { id: 43, x: 6.8, y: 3, colorValue: 'Virginica', sizeValue: 5.5 },
+ { id: 44, x: 5.7, y: 2.5, colorValue: 'Virginica', sizeValue: 5 },
+ { id: 45, x: 5.8, y: 2.8, colorValue: 'Virginica', sizeValue: 5.1 },
+];
+
+const species = ['Setosa', 'Versicolor', 'Virginica'];
+const speciesColors = ['#2e7d32', '#ed6c02', '#9c27b0'];
+
+/**
+ * A custom marker that uses the `color` and `size` props—already
+ * mapped through the z-axis scales by `ScatterPlot`—to derive
+ * independent stroke and fill styling.
+ */
+function IrisMarker(props) {
+ const { x, y, color, size, isHighlighted, isFaded, ...other } = props;
+
+ const r = (isHighlighted ? 1.2 : 1) * size;
+ const strokeWidth = Math.max(1, size / 5);
+ const strokeColor = darken(color, 0.2);
+
+ return (
+
+ );
+}
+
+/**
+ * A custom legend showing the color encoding used for each Iris species.
+ */
+function IrisLegend() {
+ return (
+
+ {species.map((name, index) => {
+ const color = speciesColors[index];
+ return (
+
+
+ {name}
+
+ );
+ })}
+
+ );
+}
+/**
+ * An annotation component that calculates the average petal length across the dataset and uses the z-axis size scale to show how that value maps to marker radius, demonstrating how to access and use z-axis scales directly in custom components.
+ */
+function IrisAnnotation() {
+ const series = useScatterSeries('data');
+ const zAxis = useZAxis('petal');
+ const sizeScale = zAxis?.sizeScale;
+
+ if (!series?.data || !sizeScale) {
+ return null;
+ }
+
+ const avgPetalLength =
+ series.data.reduce((sum, item) => sum + Number(item.sizeValue ?? 0), 0) /
+ series.data.length;
+
+ const avgMarkerRadius = sizeScale(avgPetalLength);
+
+ return (
+
+ Bubble size is proportional to petal length (average:{' '}
+ {avgPetalLength.toFixed(1)} cm, mapped to a {avgMarkerRadius?.toFixed(1)}px
+ radius)
+
+ );
+}
+
+export default function ScatterZAxis() {
+ return (
+
+ d.colorValue),
+ colorMap: {
+ type: 'ordinal',
+ values: species,
+ colors: speciesColors,
+ },
+ },
+ {
+ id: 'petal',
+ data: irisData.map((d) => d.sizeValue),
+ sizeMap: {
+ type: 'continuous',
+ min: 1,
+ max: 7,
+ size: [4, 16],
+ },
+ },
+ ]}
+ >
+
+ v
+ ? `Sepal: ${v.x} × ${v.y} cm · Petal length: ${v.sizeValue} cm`
+ : '',
+ },
+ ]}
+ xAxis={[{ label: 'Sepal length (cm)', min: 4 }]}
+ yAxis={[{ label: 'Sepal width (cm)', min: 1.5 }]}
+ zAxis={[
+ {
+ id: 'species',
+ dataKey: 'colorValue',
+ colorMap: {
+ type: 'ordinal',
+ values: species,
+ colors: speciesColors,
+ },
+ },
+ {
+ id: 'petal',
+ dataKey: 'sizeValue',
+ sizeMap: {
+ type: 'continuous',
+ min: 1,
+ max: 7,
+ size: [4, 16],
+ },
+ },
+ ]}
+ slots={{ marker: IrisMarker }}
+ >
+
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/scatter/ScatterZAxis.tsx b/docs/data/charts/scatter/ScatterZAxis.tsx
new file mode 100644
index 0000000000000..37b9f79d3783e
--- /dev/null
+++ b/docs/data/charts/scatter/ScatterZAxis.tsx
@@ -0,0 +1,240 @@
+import * as React from 'react';
+import { ScatterChart, ScatterMarkerProps } from '@mui/x-charts/ScatterChart';
+import { useZAxis, useScatterSeries } from '@mui/x-charts/hooks';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import { ChartsDataProvider } from '@mui/x-charts/ChartsDataProvider';
+import { darken } from '@mui/material/styles';
+
+// The Iris flower dataset, with sepal length and width mapped to the x and y axes, respectively,
+// and species and petal length mapped to two separate z-axes for color and size encoding.
+const irisData = [
+ { id: 1, x: 5.1, y: 3.5, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 2, x: 4.9, y: 3, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 3, x: 4.7, y: 3.2, colorValue: 'Setosa', sizeValue: 1.3 },
+ { id: 4, x: 5, y: 3.6, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 5, x: 5.4, y: 3.9, colorValue: 'Setosa', sizeValue: 1.7 },
+ { id: 6, x: 4.6, y: 3.4, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 7, x: 5, y: 3.4, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 8, x: 4.4, y: 2.9, colorValue: 'Setosa', sizeValue: 1.4 },
+ { id: 9, x: 5.4, y: 3.7, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 10, x: 4.8, y: 3.4, colorValue: 'Setosa', sizeValue: 1.6 },
+ { id: 11, x: 5.8, y: 4, colorValue: 'Setosa', sizeValue: 1.2 },
+ { id: 12, x: 5.7, y: 4.4, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 13, x: 5.4, y: 3.4, colorValue: 'Setosa', sizeValue: 1.7 },
+ { id: 14, x: 5.1, y: 3.7, colorValue: 'Setosa', sizeValue: 1.5 },
+ { id: 15, x: 4.6, y: 3.6, colorValue: 'Setosa', sizeValue: 1 },
+ { id: 16, x: 7, y: 3.2, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 17, x: 6.4, y: 3.2, colorValue: 'Versicolor', sizeValue: 4.5 },
+ { id: 18, x: 6.9, y: 3.1, colorValue: 'Versicolor', sizeValue: 4.9 },
+ { id: 19, x: 5.5, y: 2.3, colorValue: 'Versicolor', sizeValue: 4 },
+ { id: 20, x: 6.5, y: 2.8, colorValue: 'Versicolor', sizeValue: 4.6 },
+ { id: 21, x: 5.7, y: 2.8, colorValue: 'Versicolor', sizeValue: 4.5 },
+ { id: 22, x: 6.3, y: 3.3, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 23, x: 4.9, y: 2.4, colorValue: 'Versicolor', sizeValue: 3.3 },
+ { id: 24, x: 6.6, y: 2.9, colorValue: 'Versicolor', sizeValue: 4.6 },
+ { id: 25, x: 5.2, y: 2.7, colorValue: 'Versicolor', sizeValue: 3.9 },
+ { id: 26, x: 5, y: 2, colorValue: 'Versicolor', sizeValue: 3.5 },
+ { id: 27, x: 5.9, y: 3, colorValue: 'Versicolor', sizeValue: 4.2 },
+ { id: 28, x: 6, y: 2.2, colorValue: 'Versicolor', sizeValue: 4 },
+ { id: 29, x: 6.1, y: 2.9, colorValue: 'Versicolor', sizeValue: 4.7 },
+ { id: 30, x: 5.6, y: 2.9, colorValue: 'Versicolor', sizeValue: 3.6 },
+ { id: 31, x: 6.3, y: 3.3, colorValue: 'Virginica', sizeValue: 6 },
+ { id: 32, x: 5.8, y: 2.7, colorValue: 'Virginica', sizeValue: 5.1 },
+ { id: 33, x: 7.1, y: 3, colorValue: 'Virginica', sizeValue: 5.9 },
+ { id: 34, x: 6.3, y: 2.9, colorValue: 'Virginica', sizeValue: 5.6 },
+ { id: 35, x: 6.5, y: 3, colorValue: 'Virginica', sizeValue: 5.8 },
+ { id: 36, x: 7.6, y: 3, colorValue: 'Virginica', sizeValue: 6.6 },
+ { id: 37, x: 4.9, y: 2.5, colorValue: 'Virginica', sizeValue: 4.5 },
+ { id: 38, x: 7.3, y: 2.9, colorValue: 'Virginica', sizeValue: 6.3 },
+ { id: 39, x: 6.7, y: 2.5, colorValue: 'Virginica', sizeValue: 5.8 },
+ { id: 40, x: 7.2, y: 3.6, colorValue: 'Virginica', sizeValue: 6.1 },
+ { id: 41, x: 6.5, y: 3.2, colorValue: 'Virginica', sizeValue: 5.1 },
+ { id: 42, x: 6.4, y: 2.7, colorValue: 'Virginica', sizeValue: 5.3 },
+ { id: 43, x: 6.8, y: 3, colorValue: 'Virginica', sizeValue: 5.5 },
+ { id: 44, x: 5.7, y: 2.5, colorValue: 'Virginica', sizeValue: 5 },
+ { id: 45, x: 5.8, y: 2.8, colorValue: 'Virginica', sizeValue: 5.1 },
+];
+
+const species = ['Setosa', 'Versicolor', 'Virginica'];
+const speciesColors = ['#2e7d32', '#ed6c02', '#9c27b0'];
+
+/**
+ * A custom marker that uses the `color` and `size` props—already
+ * mapped through the z-axis scales by `ScatterPlot`—to derive
+ * independent stroke and fill styling.
+ */
+function IrisMarker(props: ScatterMarkerProps) {
+ const { x, y, color, size, isHighlighted, isFaded, ...other } = props;
+
+ const r = (isHighlighted ? 1.2 : 1) * size;
+ const strokeWidth = Math.max(1, size / 5);
+ const strokeColor = darken(color, 0.2);
+
+ return (
+
+ );
+}
+
+/**
+ * A custom legend showing the color encoding used for each Iris species.
+ */
+function IrisLegend() {
+ return (
+
+ {species.map((name, index) => {
+ const color = speciesColors[index];
+ return (
+
+
+ {name}
+
+ );
+ })}
+
+ );
+}
+/**
+ * An annotation component that calculates the average petal length across the dataset and uses the z-axis size scale to show how that value maps to marker radius, demonstrating how to access and use z-axis scales directly in custom components.
+ */
+function IrisAnnotation() {
+ const series = useScatterSeries('data');
+ const zAxis = useZAxis('petal');
+ const sizeScale = zAxis?.sizeScale;
+
+ if (!series?.data || !sizeScale) {
+ return null;
+ }
+
+ const avgPetalLength =
+ series.data.reduce((sum, item) => sum + Number(item.sizeValue ?? 0), 0) /
+ series.data.length;
+
+ const avgMarkerRadius = sizeScale(avgPetalLength);
+
+ return (
+
+ Bubble size is proportional to petal length (average:{' '}
+ {avgPetalLength.toFixed(1)} cm, mapped to a {avgMarkerRadius?.toFixed(1)}px
+ radius)
+
+ );
+}
+
+export default function ScatterZAxis() {
+ return (
+
+ d.colorValue),
+ colorMap: {
+ type: 'ordinal',
+ values: species,
+ colors: speciesColors,
+ },
+ },
+ {
+ id: 'petal',
+ data: irisData.map((d) => d.sizeValue),
+ sizeMap: {
+ type: 'continuous',
+ min: 1,
+ max: 7,
+ size: [4, 16],
+ },
+ },
+ ]}
+ >
+
+ v
+ ? `Sepal: ${v.x} × ${v.y} cm · Petal length: ${v.sizeValue} cm`
+ : '',
+ },
+ ]}
+ xAxis={[{ label: 'Sepal length (cm)', min: 4 }]}
+ yAxis={[{ label: 'Sepal width (cm)', min: 1.5 }]}
+ zAxis={[
+ {
+ id: 'species',
+ dataKey: 'colorValue',
+ colorMap: {
+ type: 'ordinal',
+ values: species,
+ colors: speciesColors,
+ },
+ },
+ {
+ id: 'petal',
+ dataKey: 'sizeValue',
+ sizeMap: {
+ type: 'continuous',
+ min: 1,
+ max: 7,
+ size: [4, 16],
+ },
+ },
+ ]}
+ slots={{ marker: IrisMarker }}
+ >
+
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/scatter/scatter.md b/docs/data/charts/scatter/scatter.md
index 3218741357f88..e4b288a682c95 100644
--- a/docs/data/charts/scatter/scatter.md
+++ b/docs/data/charts/scatter/scatter.md
@@ -133,6 +133,16 @@ See [Custom components](/x/react-charts/components/) for more ways to customize
{{"demo": "CustomScatter.js"}}
+### Using zAxis in custom components
+
+The `zAxis` provides color and size scales that the default scatter plot uses internally.
+
+Custom markers receive `color` and `size` values that have already been mapped through these scales, making it easy to derive additional visual properties such as stroke color and stroke width. For more advanced use cases, custom components can access z-axis scales directly with the `useZAxis()` hook and series data with the `useScatterSeries()` hook.
+
+The example below renders the [Iris flower dataset](https://en.wikipedia.org/wiki/Iris_flower_data_set) using two z-axes: one maps species to color and another maps petal length to marker size. The custom marker derives a darker stroke color and a proportional stroke width from the mapped values, while a custom annotation demonstrates how to access the underlying size scale directly from the z-axis.
+
+{{"demo": "ScatterZAxis.js"}}
+
## Performance
### SVG batch rendering