Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Explain how to clip plots with composition #12679

Merged
merged 5 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/data/charts/composition/LimitOverflow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as React from 'react';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import useId from '@mui/utils/useId';

import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer';
import { ScatterPlot } from '@mui/x-charts/ScatterChart';
import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart';
import { ChartsClipPath } from '@mui/x-charts/ChartsClipPath';
import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
import { ChartsGrid } from '@mui/x-charts/ChartsGrid';

import { Chance } from 'chance';

const chance = new Chance(42);

const data = Array.from({ length: 100 }, () => ({
x: chance.floating({ min: -25, max: 25 }),
y: chance.floating({ min: -25, max: 25 }),
})).map((d, index) => ({ ...d, id: index }));

const minDistance = 10;

export default function LimitOverflow() {
const [isLimited, setIsLimited] = React.useState(false);
const [xLimits, setXLimites] = React.useState([-20, 20]);

const id = useId();
const clipPathId = `${id}-clip-path`;

const handleChange = (event, newValue, activeThumb) => {
if (!Array.isArray(newValue)) {
return;
}

if (newValue[1] - newValue[0] < minDistance) {
if (activeThumb === 0) {
const clamped = Math.min(newValue[0], 100 - minDistance);
setXLimites([clamped, clamped + minDistance]);
} else {
const clamped = Math.max(newValue[1], minDistance);
setXLimites([clamped - minDistance, clamped]);
}
} else {
setXLimites(newValue);
}
};

return (
<Box sx={{ width: '100%', maxWidth: 500 }}>
<FormControlLabel
checked={isLimited}
control={
<Checkbox onChange={(event) => setIsLimited(event.target.checked)} />
}
label="Clip the plot"
labelPlacement="end"
/>
<ResponsiveChartContainer
xAxis={[
{
label: 'x',
min: xLimits[0],
max: xLimits[1],
data: [-30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25],
},
]}
series={[
{ type: 'scatter', data, markerSize: 8 },
{
type: 'line',
data: [10, 13, 12, 5, -6, -3, 4, 20, 18, 17, 12, 11],
showMark: true,
},
]}
height={300}
margin={{ top: 10 }}
>
<ChartsGrid vertical horizontal />
<g clipPath={`url(#${clipPathId})`}>
<ScatterPlot />
<LinePlot />
</g>
<ChartsXAxis />
<ChartsYAxis />
<MarkPlot />
{isLimited && <ChartsClipPath id={clipPathId} />}
</ResponsiveChartContainer>

<Slider
value={xLimits}
onChange={handleChange}
valueLabelDisplay="auto"
min={-40}
max={40}
sx={{ mt: 2 }}
/>
</Box>
);
}
107 changes: 107 additions & 0 deletions docs/data/charts/composition/LimitOverflow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as React from 'react';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import useId from '@mui/utils/useId';

import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer';
import { ScatterPlot } from '@mui/x-charts/ScatterChart';
import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart';
import { ChartsClipPath } from '@mui/x-charts/ChartsClipPath';
import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
import { ChartsGrid } from '@mui/x-charts/ChartsGrid';

import { Chance } from 'chance';

const chance = new Chance(42);

const data = Array.from({ length: 100 }, () => ({
x: chance.floating({ min: -25, max: 25 }),
y: chance.floating({ min: -25, max: 25 }),
})).map((d, index) => ({ ...d, id: index }));

const minDistance = 10;

export default function LimitOverflow() {
const [isLimited, setIsLimited] = React.useState(false);
const [xLimits, setXLimites] = React.useState<number[]>([-20, 20]);

const id = useId();
const clipPathId = `${id}-clip-path`;

const handleChange = (
event: Event,
newValue: number | number[],
activeThumb: number,
) => {
if (!Array.isArray(newValue)) {
return;
}

if (newValue[1] - newValue[0] < minDistance) {
if (activeThumb === 0) {
const clamped = Math.min(newValue[0], 100 - minDistance);
setXLimites([clamped, clamped + minDistance]);
} else {
const clamped = Math.max(newValue[1], minDistance);
setXLimites([clamped - minDistance, clamped]);
}
} else {
setXLimites(newValue as number[]);
}
};

return (
<Box sx={{ width: '100%', maxWidth: 500 }}>
<FormControlLabel
checked={isLimited}
control={
<Checkbox onChange={(event) => setIsLimited(event.target.checked)} />
}
label="Clip the plot"
labelPlacement="end"
/>
<ResponsiveChartContainer
xAxis={[
{
label: 'x',
min: xLimits[0],
max: xLimits[1],
data: [-30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25],
},
]}
series={[
{ type: 'scatter', data, markerSize: 8 },
{
type: 'line',
data: [10, 13, 12, 5, -6, -3, 4, 20, 18, 17, 12, 11],
showMark: true,
},
]}
height={300}
margin={{ top: 10 }}
>
<ChartsGrid vertical horizontal />
<g clipPath={`url(#${clipPathId})`}>
<ScatterPlot />
<LinePlot />
</g>
<ChartsXAxis />
<ChartsYAxis />
<MarkPlot />
{isLimited && <ChartsClipPath id={clipPathId} />}
</ResponsiveChartContainer>

<Slider
value={xLimits}
onChange={handleChange}
valueLabelDisplay="auto"
min={-40}
max={40}
sx={{ mt: 2 }}
/>
</Box>
);
}
35 changes: 35 additions & 0 deletions docs/data/charts/composition/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,41 @@ The order of elements in composition is the only way to define how they overlap.

To display data, you have components named `<XxxPlot />` such as `<LinePlot />`, `<AreaPlot />`, `<MarkPlot />`, `<BarPlot />`, etc.

### Clipping

To ensure chart elements stay confined to the designated drawing area, leverage the `ChartsClipPath` component.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that's just me, but leverage is not a super common word and we don't use it a lot.
Since we target an international audience, sticking to simple (boring 😆 ) words when they don't loose any subtlety to the sentence might be better.

I would stick to the good old "you can use" here.

alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
This component defines a rectangular clip path that acts as a boundary.

1. **Define the Clip Path**: Use `<ChartsClipPath id={clipPathId} />` to establish the clip path for the drawing area. `clipPathId` must be a unique identifier.
2. **Wrap the Chart**: Enclose the chart elements you want to clip within a `<g>` element. Set the `clipPath` attribute to `url(#${clipPathId})` to reference the previously defined clip path. Example: ``<g clipPath={`url(#${clipPathId})`}>``

```jsx
<ChartContainer>
<g clipPath={`url(#${clipPathId})`}>
// The plotting to clip in the drawing area.
<ScatterPlot />
<LinePlot />
</g>
<ChartsClipPath id={clipPathId} /> // Defines the clip path of the drawing area.
</ChartContainer>
```

The following demo allows you to toggle clipping for scatter and line plots.
Observe how line markers extend beyond the clip area, rendering on top of the axes.

{{"demo": "LimitOverflow.js" }}

:::warning
The provided demo is generating a unique ID with `useId()`.

```js
const id = useId();
const clipPathId = `${id}-clip-path`;
```

It's important to generate unique IDs for clip paths, especially when dealing with multiple charts on a page. Assigning a static ID like `"my-id"` would lead to conflicts.
:::

### Axis

To add axes, you can use `<ChartsXAxis />` and `<ChartsYAxis />` as defined in the [axis page](/x/react-charts/axis/#composition).
Expand Down