Skip to content

Commit

Permalink
Extend clip path configs - new PR (#3602)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->
Extend clip path configs by [@tylerben](https://github.com/tylerben) in
2022

This is the same description and the same PR as
#2488 with some changes for
type safety as well as merging in the latest and adding a story.

Opening this for draft for thoughts since this work is already done.
@nikolasrieble thoughts on these changes to get this stale PR closed?

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->
#2304

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->
- test functionality in local storybook

## Screenshots (if appropriate):
Without `clipDot`

![image](https://github.com/recharts/recharts/assets/25180830/513d1ba5-442c-4dec-9b99-0bd65cc9067b)


With `clipDot`

![image](https://github.com/recharts/recharts/assets/25180830/fdeddc8a-9122-45a7-bbf4-a44f604d9e5c)


## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.

---------

Co-authored-by: Ben Tyler <tylerben14@gmail.com>
Co-authored-by: Coltin Kifer <ckifer@amazon.com>
  • Loading branch information
3 people committed May 31, 2023
1 parent 8f4b999 commit 91801b1
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 20 deletions.
4 changes: 3 additions & 1 deletion demo/component/AreaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ export default class AreaChartDemo extends React.Component<any, any> {
render() {
return (
<div className="area-charts">
<button type="button" onClick={this.handleChangeData}>change data</button>
<button type="button" onClick={this.handleChangeData}>
change data
</button>
<br />

<p>Stacked AreaChart</p>
Expand Down
11 changes: 10 additions & 1 deletion demo/component/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,14 @@ const data03 = [
{ date: 'Dec 30 2016', price: 115.82 },
];

const data04 = [
{ name: 'Page A', uv: 1000, pv: 400, amt: 2400 },
{ name: 'Page B', uv: 3000, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 2000, pv: 0, amt: 2290 },
{ name: 'Page D', uv: 2780, pv: 0, amt: 2000 },
{ name: 'Page E', uv: 1890, pv: 3000, amt: 2181 },
];

const series = [
{
name: 'Series 1',
Expand Down Expand Up @@ -343,6 +351,7 @@ const initialState = {
data,
data01,
data02,
data04,
opacity: 1,
anotherState: false,
};
Expand Down Expand Up @@ -416,7 +425,7 @@ export default class Demo extends Component<any, any> {
};

render() {
const { data, data01, data02, opacity } = this.state;
const { data, data01, data02, data04, opacity } = this.state;

return (
<div className="line-charts">
Expand Down
30 changes: 24 additions & 6 deletions src/cartesian/Area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ interface State {
totalLength?: number;
}

const isDotProps = (dot: AreaDot): dot is DotProps =>
typeof dot === 'object' && 'cx' in dot && 'cy' in dot && 'r' in dot;

export class Area extends PureComponent<Props, State> {
static displayName = 'Area';

Expand Down Expand Up @@ -309,7 +312,7 @@ export class Area extends PureComponent<Props, State> {
}
};

renderDots(needClip: boolean, clipPathId: string) {
renderDots(needClip: boolean, clipDot: boolean, clipPathId: string) {
const { isAnimationActive } = this.props;
const { isAnimationFinished } = this.state;

Expand Down Expand Up @@ -338,7 +341,7 @@ export class Area extends PureComponent<Props, State> {
return Area.renderDotItem(dot, dotProps);
});
const dotsProps = {
clipPath: needClip ? `url(#clipPath-${clipPathId})` : null,
clipPath: needClip ? `url(#clipPath-${clipDot ? '' : 'dots-'}${clipPathId})` : null,
};
return (
<Layer className="recharts-area-dots" {...dotsProps}>
Expand Down Expand Up @@ -553,20 +556,35 @@ export class Area extends PureComponent<Props, State> {
const { isAnimationFinished } = this.state;
const hasSinglePoint = points.length === 1;
const layerClass = classNames('recharts-area', className);
const needClip = (xAxis && xAxis.allowDataOverflow) || (yAxis && yAxis.allowDataOverflow);
const needClipX = xAxis && xAxis.allowDataOverflow;
const needClipY = yAxis && yAxis.allowDataOverflow;
const needClip = needClipX || needClipY;
const clipPathId = _.isNil(id) ? this.id : id;
const { r, strokeWidth } = filterProps(dot) || { r: 3, strokeWidth: 2 };
const { clipDot = true } = isDotProps(dot) ? dot : {};
const dotSize = r * 2 + strokeWidth;

return (
<Layer className={layerClass}>
{needClip ? (
{needClipX || needClipY ? (
<defs>
<clipPath id={`clipPath-${clipPathId}`}>
<rect x={left} y={top} width={width} height={Math.floor(height)} />
<rect
x={needClipX ? left : left - width / 2}
y={needClipY ? top : top - height / 2}
width={needClipX ? width : width * 2}
height={needClipY ? height : height * 2}
/>
</clipPath>
{!clipDot && (
<clipPath id={`clipPath-dots-${clipPathId}`}>
<rect x={left - dotSize / 2} y={top - dotSize / 2} width={width + dotSize} height={height + dotSize} />
</clipPath>
)}
</defs>
) : null}
{!hasSinglePoint ? this.renderArea(needClip, clipPathId) : null}
{(dot || hasSinglePoint) && this.renderDots(needClip, clipPathId)}
{(dot || hasSinglePoint) && this.renderDots(needClip, clipDot, clipPathId)}
{(!isAnimationActive || isAnimationFinished) && LabelList.renderCallByParent(this.props, points)}
</Layer>
);
Expand Down
15 changes: 12 additions & 3 deletions src/cartesian/Bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface InternalBarProps {
data?: BarRectangleItem[];
top?: number;
left?: number;
width?: number;
height?: number;
}

type RectangleShapeType =
Expand Down Expand Up @@ -467,15 +469,22 @@ export class Bar extends PureComponent<Props, State> {

const { isAnimationFinished } = this.state;
const layerClass = classNames('recharts-bar', className);
const needClip = (xAxis && xAxis.allowDataOverflow) || (yAxis && yAxis.allowDataOverflow);
const needClipX = xAxis && xAxis.allowDataOverflow;
const needClipY = yAxis && yAxis.allowDataOverflow;
const needClip = needClipX || needClipY;
const clipPathId = _.isNil(id) ? this.id : id;

return (
<Layer className={layerClass}>
{needClip ? (
{needClipX || needClipY ? (
<defs>
<clipPath id={`clipPath-${clipPathId}`}>
<rect x={left} y={top} width={width} height={height} />
<rect
x={needClipX ? left : left - width / 2}
y={needClipY ? top : top - height / 2}
width={needClipX ? width : width * 2}
height={needClipY ? height : height * 2}
/>
</clipPath>
</defs>
) : null}
Expand Down
27 changes: 21 additions & 6 deletions src/cartesian/Line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export class Line extends PureComponent<Props, State> {
return dotItem;
}

renderDots(needClip: boolean, clipPathId: string) {
renderDots(needClip: boolean, clipDot: boolean, clipPathId: string) {
const { isAnimationActive } = this.props;

if (isAnimationActive && !this.state.isAnimationFinished) {
Expand All @@ -334,7 +334,7 @@ export class Line extends PureComponent<Props, State> {
return Line.renderDotItem(dot, dotProps);
});
const dotsProps = {
clipPath: needClip ? `url(#clipPath-${clipPathId})` : null,
clipPath: needClip ? `url(#clipPath-${clipDot ? '' : 'dots-'}${clipPathId})` : null,
};

return (
Expand Down Expand Up @@ -461,21 +461,36 @@ export class Line extends PureComponent<Props, State> {
const { isAnimationFinished } = this.state;
const hasSinglePoint = points.length === 1;
const layerClass = classNames('recharts-line', className);
const needClip = (xAxis && xAxis.allowDataOverflow) || (yAxis && yAxis.allowDataOverflow);
const needClipX = xAxis && xAxis.allowDataOverflow;
const needClipY = yAxis && yAxis.allowDataOverflow;
const needClip = needClipX || needClipY;
const clipPathId = _.isNil(id) ? this.id : id;
const { r, strokeWidth } = filterProps(dot) || { r: 3, strokeWidth: 2 };
const { clipDot = true } = dot as DotProps;
const dotSize = r * 2 + strokeWidth;

return (
<Layer className={layerClass}>
{needClip ? (
{needClipX || needClipY ? (
<defs>
<clipPath id={`clipPath-${clipPathId}`}>
<rect x={left} y={top} width={width} height={height} />
<rect
x={needClipX ? left : left - width / 2}
y={needClipY ? top : top - height / 2}
width={needClipX ? width : width * 2}
height={needClipY ? height : height * 2}
/>
</clipPath>
{!clipDot && (
<clipPath id={`clipPath-dots-${clipPathId}`}>
<rect x={left - dotSize / 2} y={top - dotSize / 2} width={width + dotSize} height={height + dotSize} />
</clipPath>
)}
</defs>
) : null}
{!hasSinglePoint && this.renderCurve(needClip, clipPathId)}
{this.renderErrorBar(needClip, clipPathId)}
{(hasSinglePoint || dot) && this.renderDots(needClip, clipPathId)}
{(hasSinglePoint || dot) && this.renderDots(needClip, clipDot, clipPathId)}
{(!isAnimationActive || isAnimationFinished) && LabelList.renderCallByParent(this.props, points)}
</Layer>
);
Expand Down
15 changes: 12 additions & 3 deletions src/cartesian/Scatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface ScatterProps {

left?: number;
top?: number;
width?: number;
height?: number;

xAxis?: Omit<XAxisProps, 'scale'> & { scale: D3Scale<string | number> };
yAxis?: Omit<YAxisProps, 'scale'> & { scale: D3Scale<string | number> };
Expand Down Expand Up @@ -435,15 +437,22 @@ export class Scatter extends PureComponent<Props, State> {
}
const { isAnimationFinished } = this.state;
const layerClass = classNames('recharts-scatter', className);
const needClip = (xAxis && xAxis.allowDataOverflow) || (yAxis && yAxis.allowDataOverflow);
const needClipX = xAxis && xAxis.allowDataOverflow;
const needClipY = yAxis && yAxis.allowDataOverflow;
const needClip = needClipX || needClipY;
const clipPathId = _.isNil(id) ? this.id : id;

return (
<Layer className={layerClass} clipPath={needClip ? `url(#clipPath-${clipPathId})` : null}>
{needClip ? (
{needClipX || needClipY ? (
<defs>
<clipPath id={`clipPath-${clipPathId}`}>
<rect x={left} y={top} width={width} height={height} />
<rect
x={needClipX ? left : left - width / 2}
y={needClipY ? top : top - height / 2}
width={needClipX ? width : width * 2}
height={needClipY ? height : height * 2}
/>
</clipPath>
</defs>
) : null}
Expand Down
1 change: 1 addition & 0 deletions src/shape/Dot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface DotProps {
cx?: number;
cy?: number;
r?: number;
clipDot?: boolean;
}

export type Props = PresentationAttributesWithProps<DotProps, SVGCircleElement> & DotProps;
Expand Down
41 changes: 41 additions & 0 deletions storybook/stories/Examples/LineChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// eslint-disable-next-line max-classes-per-file
import React, { PureComponent, useState } from 'react';
import { StoryObj } from '@storybook/react';
import { Impressions, impressionsData, pageData } from '../data';
import {
Line,
Expand Down Expand Up @@ -325,6 +326,46 @@ export const WithCustomizedDot = {
},
};

export const ClipDot: StoryObj = {
render: (args: Record<string, any>) => {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart
margin={{
top: 20,
right: 20,
bottom: 20,
left: 20,
}}
data={pageData}
>
<Line
isAnimationActive={false}
dataKey="uv"
{...args}
dot={{ clipDot: args.clipDot, r: 4, strokeWidth: 2, fill: '#ffffff', fillOpacity: 1 }}
/>
<Tooltip />
<XAxis dataKey="name" allowDataOverflow />
<YAxis />
</LineChart>
</ResponsiveContainer>
);
},
args: {
clipDot: false,
},
parameters: {
controls: { include: ['clipDot'] },
docs: {
description: {
clipDot: `When \`allowDataOverflow\` is true on \`XAxis\` or \`YAxis\`, set
\`clipDot\` within the dot object to determine if recharts should clip the dots at the end of the page.`,
},
},
},
};

export const WithCustomizedLabel = {
render: () => {
class CustomizedLabel extends PureComponent {
Expand Down

0 comments on commit 91801b1

Please sign in to comment.