Skip to content

Commit

Permalink
feat(pie): add support for layers to Pie component
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Nov 4, 2020
1 parent f0cefd2 commit a8f6468
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 42 deletions.
128 changes: 89 additions & 39 deletions packages/pie/src/Pie.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React, { useMemo } from 'react'
import React, { Fragment, useMemo, createElement } from 'react'
import {
getLabelGenerator,
withContainer,
Expand All @@ -19,9 +19,9 @@ import { getInheritedColorGenerator, useInheritedColor } from '@nivo/colors'
import { PieSvgDefaultProps, PieSvgPropTypes } from './props'
import PieSlice from './PieSlice'
import PieRadialLabels from './PieRadialLabels'
import PieSlicesLabels from './PieSlicesLabels'
import PieSliceLabels from './PieSliceLabels'
import PieLegends from './PieLegends'
import { useNormalizedData, usePieFromBox } from './hooks'
import { useNormalizedData, usePieFromBox, usePieLayerContext } from './hooks'

const Pie = ({
data,
Expand All @@ -30,6 +30,8 @@ const Pie = ({
valueFormat,
sortByValue,

layers,

startAngle,
endAngle,
padAngle,
Expand Down Expand Up @@ -117,16 +119,16 @@ const Pie = ({

const boundDefs = bindDefs(defs, dataWithArc, fill)

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
defs={boundDefs}
theme={theme}
role={role}
>
<g transform={`translate(${centerX},${centerY})`}>
const layerById = {
slices: null,
radialLabels: null,
sliceLabels: null,
legends: null,
}

if (layers.includes('slices')) {
layerById.slices = (
<g key="slices" transform={`translate(${centerX},${centerY})`}>
{dataWithArc.map(arc => (
<PieSlice
key={arc.id}
Expand All @@ -143,40 +145,88 @@ const Pie = ({
onMouseLeave={onMouseLeave}
/>
))}
{enableRadialLabels && (
<PieRadialLabels
arcs={dataWithArc}
radius={radius}
label={getRadialLabel}
skipAngle={radialLabelsSkipAngle}
linkOffset={radialLabelsLinkOffset}
linkDiagonalLength={radialLabelsLinkDiagonalLength}
linkHorizontalLength={radialLabelsLinkHorizontalLength}
linkStrokeWidth={radialLabelsLinkStrokeWidth}
textXOffset={radialLabelsTextXOffset}
textColor={getInheritedColorGenerator(radialLabelsTextColor, theme)}
linkColor={getInheritedColorGenerator(radialLabelsLinkColor, theme)}
/>
)}
{enableSlicesLabels && (
<PieSlicesLabels
arcs={dataWithArc}
radius={radius}
innerRadius={innerRadius}
theme={theme}
label={getSliceLabel}
skipAngle={slicesLabelsSkipAngle}
textColor={getInheritedColorGenerator(slicesLabelsTextColor, theme)}
/>
)}
</g>
)
}

if (enableRadialLabels && layers.includes('radialLabels')) {
layerById.radialLabels = (
<g key="radialLabels" transform={`translate(${centerX},${centerY})`}>
<PieRadialLabels
arcs={dataWithArc}
radius={radius}
label={getRadialLabel}
skipAngle={radialLabelsSkipAngle}
linkOffset={radialLabelsLinkOffset}
linkDiagonalLength={radialLabelsLinkDiagonalLength}
linkHorizontalLength={radialLabelsLinkHorizontalLength}
linkStrokeWidth={radialLabelsLinkStrokeWidth}
textXOffset={radialLabelsTextXOffset}
textColor={getInheritedColorGenerator(radialLabelsTextColor, theme)}
linkColor={getInheritedColorGenerator(radialLabelsLinkColor, theme)}
/>
</g>
)
}

if (enableSlicesLabels && layers.includes('sliceLabels')) {
layerById.sliceLabels = (
<g key="sliceLabels" transform={`translate(${centerX},${centerY})`}>
<PieSliceLabels
arcs={dataWithArc}
radius={radius}
innerRadius={innerRadius}
theme={theme}
label={getSliceLabel}
skipAngle={slicesLabelsSkipAngle}
textColor={getInheritedColorGenerator(slicesLabelsTextColor, theme)}
/>
</g>
)
}

if (legends.length > 0 && layers.includes('legends')) {
layerById.legends = (
<PieLegends
key="legends"
width={innerWidth}
height={innerHeight}
arcs={dataWithArc}
legends={legends}
theme={theme}
/>
)
}

const layerContext = usePieLayerContext({
dataWithArc,
arcGenerator,
centerX,
centerY,
radius,
innerRadius,
})

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
defs={boundDefs}
theme={theme}
role={role}
>
{layers.map((layer, i) => {
if (layerById[layer] !== undefined) {
return layerById[layer]
}

if (typeof layer === 'function') {
return <Fragment key={i}>{createElement(layer, layerContext)}</Fragment>
}

return null
})}
</SvgWrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const sliceStyle = {
pointerEvents: 'none',
}

export default class PieSlicesLabels extends Component {
export default class PieSliceLabels extends Component {
static propTypes = {
arcs: PropTypes.arrayOf(arcPropType).isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
Expand Down
23 changes: 23 additions & 0 deletions packages/pie/src/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,26 @@ export const usePieFromBox = ({
...computedProps,
}
}

/**
* Memoize the context to pass to custom layers.
*/
export const usePieLayerContext = ({
dataWithArc,
arcGenerator,
centerX,
centerY,
radius,
innerRadius,
}) =>
useMemo(
() => ({
dataWithArc,
arcGenerator,
centerX,
centerY,
radius,
innerRadius,
}),
[dataWithArc, arcGenerator, centerX, centerY, radius, innerRadius]
)
9 changes: 9 additions & 0 deletions packages/pie/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ export const PiePropTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
valueFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),

layers: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.oneOf(['slices', 'radialLabels', 'sliceLabels', 'legends']),
PropTypes.func,
])
).isRequired,

// layout
startAngle: PropTypes.number.isRequired,
endAngle: PropTypes.number.isRequired,
Expand Down Expand Up @@ -105,6 +112,8 @@ export const PieDefaultProps = {
padAngle: 0,
cornerRadius: 0,

layers: ['slices', 'radialLabels', 'sliceLabels', 'legends'],

// layout
startAngle: 0,
endAngle: radiansToDegrees(Math.PI * 2),
Expand Down
48 changes: 46 additions & 2 deletions packages/pie/tests/Pie.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ describe('Pie', () => {
it('should support onMouseMove handler', () => {})

it('should support onMouseLeave handler', () => {})

it('should allow to completely disable interactivity', () => {})
})

describe('tooltip', () => {
Expand All @@ -252,8 +254,50 @@ describe('Pie', () => {
})

describe('layers', () => {
it('should support disabling a layer', () => {})
it('should support disabling a layer', () => {
let pie = TestRenderer.create(<Pie width={400} height={400} data={sampleData} />)
const pieInstance = pie.root

let slices = pieInstance.findAllByType(PieSlice)
expect(slices).toHaveLength(3)

TestRenderer.act(() => {
pie.update(
<Pie
width={400}
height={400}
data={sampleData}
layers={['radialLabels', 'sliceLabels', 'legends']}
/>
)
})

slices = pieInstance.findAllByType(PieSlice)
expect(slices).toHaveLength(0)
})

it('should support adding a custom layer', () => {
const CustomLayer = () => null

const pie = TestRenderer.create(
<Pie
width={400}
height={400}
data={sampleData}
innerRadius={0.5}
layers={['slices', 'radialLabels', 'sliceLabels', 'legends', CustomLayer]}
/>
)
const pieInstance = pie.root

it('should support adding a custom layer', () => {})
const customLayer = pieInstance.findByType(CustomLayer)

expect(customLayer.props.dataWithArc).toHaveLength(3)
expect(customLayer.props.centerX).toEqual(200)
expect(customLayer.props.centerY).toEqual(200)
expect(customLayer.props.arcGenerator).toBeDefined()
expect(customLayer.props.radius).toEqual(200)
expect(customLayer.props.innerRadius).toEqual(100)
})
})
})

0 comments on commit a8f6468

Please sign in to comment.