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

363 lines between labels and slices #2734

Merged
merged 16 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion demo/js/components/victory-pie-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { random, range } from "lodash";
import React from "react";
import { VictoryPie } from "victory-pie";
import { VictoryTooltip } from "victory-tooltip";
import { VictoryTheme } from "victory-core";
import { VictoryTheme, LineSegment } from "victory-core";

export default class App extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -317,6 +317,20 @@ export default class App extends React.Component {
{ x: 8, y: 1, l: 315 },
]}
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator={<LineSegment style={{opacity:"1",strokeWidth:"1px",stroke: "red"}}/>}
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator={<LineSegment style={{opacity:"1",strokeWidth:"1px",strokeDasharray: "1",stroke: "red"}}/>}
labelIndicatorInnerOffset={45}
labelIndicatorOuterOffset={15}
/>
</div>
</div>
);
Expand Down
16 changes: 15 additions & 1 deletion demo/ts/components/victory-pie-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { random, range } from "lodash";
import { VictoryPie } from "victory-pie";
import { VictoryTooltip } from "victory-tooltip";
import { VictoryTheme } from "victory-core";
import { VictoryTheme, LineSegment } from "victory-core";

interface VictoryPieDemoState {
data: {
Expand Down Expand Up @@ -306,6 +306,20 @@ export default class VictoryPieDemo extends React.Component<
animate={{ duration: 2000 }}
innerRadius={140}
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator={<LineSegment style={{opacity:"1",strokeWidth:"1px",stroke: "red"}}/>}
/>
<VictoryPie
style={{ parent: parentStyle }}
labelIndicator={<LineSegment style={{opacity:"1",strokeWidth:"1px",strokeDasharray: "1",stroke: "red"}}/>}
labelIndicatorInnerOffset={45}
labelIndicatorOuterOffset={15}
/>
</div>
</div>
);
Expand Down
55 changes: 55 additions & 0 deletions docs/src/content/docs/victory-pie.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,61 @@ See the [Data Accessors Guide][] for more detail on formatting and processing da
y={(d) => d.value + d.error}
```

## labelIndicator

`type: boolean || element`

The `labelIndicator` prop defines the label indicator line between labels and the pie chart. If this prop is used as a boolean,then the default indicator will be displayed. To customize or pass your own styling `<LineSegment/>` can be passed to labelIndicator. LabelIndicator is functional only when labelPosition = "centroid". To adjust the labelIndicator length, `labelIndicatorInnerOffset` and `labelIndicatorOuterOffset` props can be used alongside labelIndicator.

```playground
<VictoryPie
data={sampleData}
labelIndicator
style={{ labels: { fill: "white", fontSize: 20, fontWeight: "bold" } }}
/>
<VictoryPie
data={sampleData}
labelIndicator={<LineSegment style = {{stroke:"red", strokeDasharray:1,fill: "none",}}/>}
style={{ labels: { fill: "white", fontSize: 20, fontWeight: "bold" } }}
/>
<VictoryPie
data={sampleData}
labelIndicator={<LineSegment style = {{stroke:"red", strokeDasharray:1,fill: "none",}}/>}
style={{ labels: { fill: "white", fontSize: 20, fontWeight: "bold" } }}
labelIndicatorInnerOffset = {10}
labelIndicatorOuterOffset = {15}
/>
```
## labelIndicatorInnerOffset

`type: number`

The `labelIndicatorInnerOffset` prop defines the offset by which the indicator length inside pie chart is being drawn. Higher the number shorter the length.

```playground
<VictoryPie
data={sampleData}
labelIndicator
style={{ labels: { fill: "white", fontSize: 20, fontWeight: "bold" } }}
labelIndicatorInnerOffset = {10}
/>
```

## labelIndicatorOuterOffset

`type: number`

The `labelIndicatorOuterOffset` prop defines the offset by which the indicator length outside the pie chart is being drawn. Higher the number shorter the length.

```playground
<VictoryPie
data={sampleData}
labelIndicator
style={{ labels: { fill: "white", fontSize: 20, fontWeight: "bold" } }}
labelIndicatorOuterOffset = {10}
/>
```

[animations guide]: /guides/animations
[data accessors guide]: /guides/data-accessors
[custom components guide]: /guides/custom-components
Expand Down
89 changes: 85 additions & 4 deletions packages/victory-pie/src/helper-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,13 @@ const getLabelText = (props, datum, index) => {
return checkForValidText(text);
};

const getLabelArc = (radius, labelRadius, style) => {
const getLabelArc = (labelRadius) => {
return d3Shape.arc().outerRadius(labelRadius).innerRadius(labelRadius);
};

const getCalculatedLabelRadius = (radius, labelRadius, style) => {
const padding = (style && style.padding) || 0;
const arcRadius = labelRadius || radius + padding;
return d3Shape.arc().outerRadius(arcRadius).innerRadius(arcRadius);
return labelRadius || radius + padding;
};

const getLabelPosition = (arc, slice, position) => {
Expand Down Expand Up @@ -195,7 +198,12 @@ const getLabelProps = (text, dataProps, calculatedValues) => {
labelStyle,
assign({ labelRadius, text }, dataProps),
);
const labelArc = getLabelArc(defaultRadius, labelRadius, evaluatedStyle);
const calculatedLabelRadius = getCalculatedLabelRadius(
defaultRadius,
labelRadius,
evaluatedStyle,
);
const labelArc = getLabelArc(calculatedLabelRadius);
const position = getLabelPosition(labelArc, slice, labelPosition);
const baseAngle = getBaseLabelAngle(slice, labelPosition, labelStyle);
const labelAngle = getLabelAngle(baseAngle, labelPlacement);
Expand All @@ -219,6 +227,7 @@ const getLabelProps = (text, dataProps, calculatedValues) => {
textAnchor,
verticalAnchor,
angle: labelAngle,
calculatedLabelRadius,
};

if (!Helpers.isTooltip(labelComponent)) {
Expand All @@ -228,6 +237,66 @@ const getLabelProps = (text, dataProps, calculatedValues) => {
return defaults({}, labelProps, Helpers.omit(tooltipTheme, ["style"]));
};

export const radian = Math.PI / 180;
export const getXOffsetMultiplayerByAngle = (angle) =>
Math.cos(angle - 90 * radian);
export const getYOffsetMultiplayerByAngle = (angle) =>
Math.sin(angle - 90 * radian);
export const getXOffset = (offset, angle) =>
offset * getXOffsetMultiplayerByAngle(angle);
export const getYOffset = (offset, angle) =>
offset * getYOffsetMultiplayerByAngle(angle);
export const getAverage = (array) =>
array.reduce((acc, cur) => acc + cur, 0) / array.length;

export const getLabelIndicatorPropsForLineSegment = (
props,
calculatedValues,
labelProps,
) => {
const {
innerRadius,
radius,
slice: { startAngle, endAngle },
labelIndicatorInnerOffset,
labelIndicatorOuterOffset,
index,
} = props;

const { height, width } = calculatedValues;
const { calculatedLabelRadius } = labelProps;
// calculation
const middleRadius = getAverage([innerRadius, radius]);
const midAngle = getAverage([endAngle, startAngle]);
const centerX = width / 2;
const centerY = height / 2;
const innerOffset = middleRadius + labelIndicatorInnerOffset;
let outerOffset;

if (innerRadius > 0) {
outerOffset = middleRadius + labelIndicatorOuterOffset;
} else {
outerOffset =
calculatedLabelRadius - middleRadius + labelIndicatorOuterOffset;
}

const x1 = centerX + getXOffset(innerOffset, midAngle);
const y1 = centerY + getYOffset(innerOffset, midAngle);

const offSetEnd = 2 * radius - outerOffset;
const x2 = centerX + getXOffset(offSetEnd, midAngle);
const y2 = centerY + getYOffset(offSetEnd, midAngle);

const labelIndicatorProps = {
x1,
y1,
x2,
y2,
index,
};
return defaults({}, labelIndicatorProps);
};

export const getBaseProps = (initialProps, fallbackProps) => {
const props = Helpers.modifyProps(initialProps, fallbackProps, "pie");
const calculatedValues = getCalculatedValues(props);
Expand All @@ -248,6 +317,7 @@ export const getBaseProps = (initialProps, fallbackProps) => {
cornerRadius,
padAngle,
disableInlineStyles,
labelIndicator,
} = calculatedValues;
const radius = props.radius || defaultRadius;
const initialChildProps = {
Expand Down Expand Up @@ -288,6 +358,17 @@ export const getBaseProps = (initialProps, fallbackProps) => {
assign({}, props, dataProps),
calculatedValues,
);
if (labelIndicator) {
const labelProps = childProps[eventKey].labels;
if (labelProps.calculatedLabelRadius > radius) {
childProps[eventKey].labelIndicators =
getLabelIndicatorPropsForLineSegment(
assign({}, props, dataProps),
calculatedValues,
labelProps,
);
}
}
}
return childProps;
}, initialChildProps);
Expand Down
3 changes: 3 additions & 0 deletions packages/victory-pie/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export interface VictoryPieProps
>[];
eventKey?: StringOrNumberOrCallback;
innerRadius?: NumberOrCallback;
labelIndicator?: boolean | React.ReactElement;
labelIndicatorInnerOffset: number;
labelIndicatorOuterOffset: number;
labelPlacement?:
| VictorySliceLabelPlacementType
| ((props: SliceProps) => VictorySliceLabelPlacementType);
Expand Down
77 changes: 76 additions & 1 deletion packages/victory-pie/src/victory-pie.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import {
addEvents,
Helpers,
Data,
LineSegment,
PropTypes as CustomPropTypes,
VictoryContainer,
VictoryLabel,
VictoryTheme,
UserProps,
} from "victory-core";
import Slice from "./slice";
import { isNil } from "lodash";
import { getBaseProps } from "./helper-methods";

const fallbackProps = {
endAngle: 360,
height: 400,
radius: 100,
innerRadius: 0,
cornerRadius: 0,
padAngle: 0,
Expand All @@ -35,6 +38,12 @@ const fallbackProps = {
"#000000",
],
labelPosition: "centroid",
labelIndicatorInnerOffset: 25,
labelIndicatorOuterOffset: 15,
};

const datumHasXandY = (datum) => {
return !isNil(datum._x) && !isNil(datum._y);
};

class VictoryPie extends React.Component {
Expand Down Expand Up @@ -141,6 +150,10 @@ class VictoryPie extends React.Component {
PropTypes.func,
]),
labelComponent: PropTypes.element,
labelIndicator: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]),
labelIndicatorInnerOffset: PropTypes.number,
labelIndicatorMiddleOffset: PropTypes.number,
labelIndicatorOuterOffset: PropTypes.number,
labelPlacement: PropTypes.oneOfType([
PropTypes.func,
PropTypes.oneOf(["parallel", "perpendicular", "vertical"]),
Expand Down Expand Up @@ -240,13 +253,75 @@ class VictoryPie extends React.Component {
"labelComponent",
"groupComponent",
"containerComponent",
"labelIndicatorComponent",
];

// Overridden in victory-native
shouldAnimate() {
return Boolean(this.props.animate);
}

renderComponents(props, shouldRenderDatum = datumHasXandY) {
const {
dataComponent,
labelComponent,
groupComponent,
labelIndicator,
labelPosition,
} = props;
const showIndicator = labelIndicator && labelPosition === "centroid";

let labelIndicatorComponents = null;

const dataComponents = this.dataKeys.reduce(
(validDataComponents, _dataKey, index) => {
const dataProps = this.getComponentProps(dataComponent, "data", index);
if (shouldRenderDatum(dataProps.datum)) {
validDataComponents.push(
React.cloneElement(dataComponent, dataProps),
);
}
return validDataComponents;
},
[],
);

const labelComponents = this.dataKeys
.map((_dataKey, index) => {
const labelProps = this.getComponentProps(
labelComponent,
"labels",
index,
);
if (labelProps.text !== undefined && labelProps.text !== null) {
return React.cloneElement(labelComponent, labelProps);
}
return undefined;
})
.filter(Boolean);

if (showIndicator) {
let labelIndicatorComponent = <LineSegment />;
if (typeof labelIndicator === "object") {
// pass user provided react component
labelIndicatorComponent = labelIndicator;
}

labelIndicatorComponents = this.dataKeys.map((_dataKey, index) => {
const labelIndicatorProps = this.getComponentProps(
labelIndicatorComponent,
"labelIndicators",
index,
);
return React.cloneElement(labelIndicatorComponent, labelIndicatorProps);
});
}
const children = showIndicator
? [...dataComponents, ...labelComponents, ...labelIndicatorComponents]
: [...dataComponents, ...labelComponents];
return this.renderContainer(groupComponent, children);
}

render() {
const { animationWhitelist, role } = VictoryPie;
const props = Helpers.modifyProps(this.props, fallbackProps, role);
Expand All @@ -255,7 +330,7 @@ class VictoryPie extends React.Component {
return this.animateComponent(props, animationWhitelist);
}

const children = this.renderData(props);
const children = this.renderComponents(props);

const component = props.standalone
? this.renderContainer(props.containerComponent, children)
Expand Down
Loading