victory-core
Version:
176 lines (175 loc) • 5.43 kB
JavaScript
import React from "react";
import defaults from "lodash/defaults";
import uniqueId from "lodash/uniqueId";
import * as Helpers from "../victory-util/helpers";
import * as UserProps from "../victory-util/user-props";
import { ClipPath } from "../victory-primitives/clip-path";
import { Circle } from "../victory-primitives/circle";
import { Rect } from "../victory-primitives/rect";
export class VictoryClipContainer extends React.Component {
static displayName = "VictoryClipContainer";
static role = "container";
static defaultProps = {
circleComponent: /*#__PURE__*/React.createElement(Circle, null),
rectComponent: /*#__PURE__*/React.createElement(Rect, null),
clipPathComponent: /*#__PURE__*/React.createElement(ClipPath, null),
groupComponent: /*#__PURE__*/React.createElement("g", null)
};
constructor(props) {
super(props);
this.state = {
clipId: props?.clipId
};
}
// The clipId state is used to prevent hydration mismatches between the server and client (issue #2941).
// A better alternative would be to utilize React 18's useId hook instead of uniqueId, which would avoid needing state for this purpose.
// However, this component currently supports React 16 at the time of writing, so this workaround is necessary.
componentDidMount() {
if (!this.state.clipId) {
this.setState({
clipId: uniqueId("victory-clip-")
});
}
}
calculateAttributes(props) {
const {
polar,
origin,
clipWidth = 0,
clipHeight = 0,
translateX = 0,
translateY = 0
} = props;
const clipPadding = Helpers.getPadding(props.clipPadding);
const radius = props.radius || Helpers.getRadius(props);
return {
x: (polar ? origin.x : translateX) - clipPadding.left,
y: (polar ? origin.y : translateY) - clipPadding.top,
width: Math.max((polar ? radius : clipWidth) + clipPadding.left + clipPadding.right, 0),
height: Math.max((polar ? radius : clipHeight) + clipPadding.top + clipPadding.bottom, 0)
};
}
renderClippedGroup(props, clipId) {
const userProps = UserProps.getSafeUserProps(props);
const {
style,
events,
transform,
children,
className,
groupComponent,
tabIndex
} = props;
const clipComponent = this.renderClipComponent(props, clipId);
const groupProps = Object.assign({
className,
style,
transform,
key: `clipped-group-${clipId}`,
clipPath: `url(#${clipId})`
}, events);
return /*#__PURE__*/React.cloneElement(groupComponent, {
...groupProps,
tabIndex,
...userProps
}, [clipComponent, ...React.Children.toArray(children)]);
}
renderGroup(props) {
const {
style,
events,
transform,
children,
className,
groupComponent,
tabIndex
} = props;
return /*#__PURE__*/React.cloneElement(groupComponent, Object.assign({
className,
style,
transform,
"aria-label": props["aria-label"],
tabIndex
}, events), children);
}
renderClipComponent(props, clipId) {
const {
polar,
origin,
clipWidth = 0,
clipHeight = 0,
translateX = 0,
translateY = 0,
circleComponent,
rectComponent,
clipPathComponent
} = props;
const {
top,
bottom,
left,
right
} = Helpers.getPadding(props.clipPadding);
let child;
if (polar) {
const radius = props.radius || Helpers.getRadius(props);
const circleProps = {
r: Math.max(radius + left + right, radius + top + bottom, 0),
cx: origin.x - left,
cy: origin.y - top
};
child = /*#__PURE__*/React.cloneElement(circleComponent, circleProps);
} else {
const rectProps = {
x: translateX - left,
y: translateY - top,
width: Math.max(clipWidth + left + right, 0),
height: Math.max(clipHeight + top + bottom, 0)
};
child = /*#__PURE__*/React.cloneElement(rectComponent, rectProps);
}
return /*#__PURE__*/React.cloneElement(clipPathComponent, Object.assign({
key: `clip-path-${clipId}`
}, props, {
clipId
}), child);
}
getClipValue(props, axis) {
const clipValues = {
x: props.clipWidth,
y: props.clipHeight
};
if (clipValues[axis] !== undefined) {
return clipValues[axis];
}
const range = Helpers.getRange(props, axis);
return range ? Math.abs(range[0] - range[1]) || undefined : undefined;
}
getTranslateValue(props, axis) {
const translateValues = {
x: props.translateX,
y: props.translateY
};
if (translateValues[axis] !== undefined) {
return translateValues[axis];
}
const range = Helpers.getRange(props, axis);
return range ? Math.min(...range) : undefined;
}
render() {
const clipHeight = this.getClipValue(this.props, "y");
const clipWidth = this.getClipValue(this.props, "x");
if (clipWidth === undefined || clipHeight === undefined) {
return this.renderGroup(this.props);
}
const translateX = this.getTranslateValue(this.props, "x");
const translateY = this.getTranslateValue(this.props, "y");
const clipProps = defaults({}, this.props, {
clipHeight,
clipWidth,
translateX,
translateY
});
return this.renderClippedGroup(clipProps, this.state.clipId);
}
}