Skip to content

Commit

Permalink
Add support for pie chart slice labels (#144)
Browse files Browse the repository at this point in the history
* Add support for pie chart slice labels

* Fix documentation typo

* Use getValue accessor to either call factory props or use them as values
  • Loading branch information
joeporpeglia authored and krissalvador27 committed Feb 7, 2019
1 parent 2400ea3 commit b899b1d
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 5 deletions.
11 changes: 11 additions & 0 deletions docs/src/docs/PieChart/examples/PieChart.js.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ class PieChartExample extends React.Component {
holeRadius={50}
centerLabel={(this.state.sinVal * 50).toFixed(0)}
/>
<PieChart
data={[45, 35, 20]}
getPieSliceLabel={val => `${val}%`}
pieSliceLabelDistance={20}
holeRadius={75}
radius={100}
marginTop={50}
marginBottom={50}
marginLeft={50}
marginRight={50}
/>
</div>
}
}
Expand Down
76 changes: 71 additions & 5 deletions src/PieChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ class PieChart extends React.Component {
* Inline style object to be applied to center label.
*/
centerLabelStyle: PropTypes.object,
/**
* Accessor for getting labels that are rendered outside each slice of the pie chart.
* If not provided no labels will be rendered.
*/
getPieSliceLabel: PropTypes.func,
/**
* Inline style object applied to each slice label.
* When a function is provided it will receive the value for the slice and should return the
* style object for that slice's label.
* Used along with `getPieSliceLabel`.
*/
pieSliceLabelStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
/**
* Distance to render the label from the outer edge of the pie chart. Positive numbers will
* move away from the center and negative numbers will move toward the center.
* When a function is provided it will receive the value for the slice and should return the
* distance for that slice's label.
* Used along with `getPieSliceLabel`.
*/
pieSliceLabelDistance: PropTypes.oneOfType([
PropTypes.number,
PropTypes.func
]),
/**
* Class attribute to be applied to each pie slice,
* or accessor function which returns a class.
Expand Down Expand Up @@ -196,6 +219,17 @@ class PieChart extends React.Component {
: null;

let startPercent = 0;
const slices = this.props.data.map((d, i) => {
const slicePercent = valueAccessor(d) / total;
const slice = {
start: startPercent,
end: startPercent + slicePercent
};
startPercent += slicePercent;

return slice;
});

return (
<svg className="rct-pie-chart" {...{ width, height }}>
{this.props.data.map((d, i) => {
Expand All @@ -214,16 +248,14 @@ class PieChart extends React.Component {
d,
i
) || ""}`;
const slicePercent = valueAccessor(d) / total;
const endPercent = startPercent + slicePercent;
const slice = slices[i];
const pathStr = pieSlicePath(
startPercent,
endPercent,
slice.start,
slice.end,
center,
radius,
holeRadius
);
startPercent += slicePercent;
const key = `pie-slice-${i}`;

return (
Expand Down Expand Up @@ -262,6 +294,11 @@ class PieChart extends React.Component {
: null}

{this.props.centerLabel ? this.renderCenterLabel(center) : null}
{this.props.getPieSliceLabel
? this.props.data.map((d, i) =>
this.renderSliceLabel(d, slices[i], center, radius, i)
)
: null}
</svg>
);
}
Expand Down Expand Up @@ -292,6 +329,35 @@ class PieChart extends React.Component {
);
}

renderSliceLabel(value, slice, center, radius, index) {
const {
getPieSliceLabel,
pieSliceLabelStyle,
pieSliceLabelDistance
} = this.props;
const labelPercent = (slice.end - slice.start) / 2 + slice.start;
const style = {
textAnchor: "middle",
dominantBaseline: "central"
};

if (pieSliceLabelStyle) {
Object.assign(style, getValue(pieSliceLabelStyle, value));
}

const r = pieSliceLabelDistance
? radius + getValue(pieSliceLabelDistance, value)
: radius;
const x = center.x + Math.sin((2 * Math.PI) / (1 / labelPercent)) * r;
const y = center.y - Math.cos((2 * Math.PI) / (1 / labelPercent)) * r;

return (
<text key={index} x={x} y={y} style={style}>
{getPieSliceLabel(value)}
</text>
);
}

renderCenterLabel(center) {
const { centerLabelStyle, centerLabelClassName, centerLabel } = this.props;
const { x, y } = center;
Expand Down
35 changes: 35 additions & 0 deletions tests/jsdom/spec/PieChart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,39 @@ describe("PieChart", () => {
markerLine.simulate("mouseleave");
expect(markerLineProps.onMouseLeaveLine).to.have.been.called;
});

it("renders slices labels with custom styles and distances", () => {
const sliceStyle = { color: "red" };
const chart = mount(
<PieChart
{...props}
getPieSliceLabel={value => `${value}%`}
pieSliceLabelDistance={20}
pieSliceLabelStyle={sliceStyle}
/>
);

props.data.forEach(value => {
const textNode = chart.find(`text[children="${value}%"]`);
expect(textNode.exists()).to.be.true;
expect(textNode.prop("style")).to.include(sliceStyle);
});
});

it("calls factory props to compute slice label distances and styles", () => {
const chart = mount(
<PieChart
{...props}
getPieSliceLabel={value => `${value}%`}
pieSliceLabelDistance={value => value}
pieSliceLabelStyle={value => ({ fontSize: value })}
/>
);

props.data.forEach(value => {
const textNode = chart.find(`text[children="${value}%"]`);
expect(textNode.exists()).to.be.true;
expect(textNode.prop("style")).to.include({ fontSize: value });
});
});
});

0 comments on commit b899b1d

Please sign in to comment.