Skip to content

Commit

Permalink
feat(pie): add the ability to programmatically control the activeId
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Nov 19, 2023
1 parent cba9500 commit c87658e
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/pie/src/Pie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const InnerPie = <RawDatum extends MayHaveLabel>({
onMouseMove,
onMouseLeave,
tooltip = defaultProps.tooltip,
activeId: activeIdFromProps,
onActiveIdChange,

transitionMode = defaultProps.transitionMode,

Expand Down Expand Up @@ -117,6 +119,8 @@ const InnerPie = <RawDatum extends MayHaveLabel>({
cornerRadius,
activeInnerRadiusOffset,
activeOuterRadiusOffset,
activeId: activeIdFromProps,
onActiveIdChange,
})

const boundDefs = bindDefs(defs, dataWithArc, fill)
Expand Down
73 changes: 68 additions & 5 deletions packages/pie/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,45 @@ export const usePieArcs = <RawDatum>({
])
}

/**
* Encapsulate the logic for defining/reading the active arc ID,
* which can be either controlled (handled externally), or uncontrolled
* (handled internally), we can optionally define a default value when
* it's uncontrolled.
*/
const useActiveId = ({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId = null,
}: {
activeId?: DatumId | null
onActiveIdChange?: (id: DatumId | null) => void
defaultActiveId?: DatumId | null
}) => {
const isControlled = typeof activeIdFromProps != 'undefined'

const [internalActiveId, setInternalActiveId] = useState<DatumId | null>(
!isControlled ? defaultActiveId : null
)

const activeId = isControlled ? activeIdFromProps : internalActiveId

const setActiveId = useCallback(
(id: DatumId | null) => {
if (onActiveIdChange) {
onActiveIdChange(id)
}

if (!isControlled) {
setInternalActiveId(id)
}
},
[isControlled, onActiveIdChange, setInternalActiveId]
)

return { activeId, setActiveId }
}

/**
* Compute pie layout using explicit radius/innerRadius,
* expressed in pixels.
Expand All @@ -180,6 +219,9 @@ export const usePie = <RawDatum>({
cornerRadius = defaultProps.cornerRadius,
activeInnerRadiusOffset = defaultProps.activeInnerRadiusOffset,
activeOuterRadiusOffset = defaultProps.activeOuterRadiusOffset,
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
}: Pick<
Partial<CompletePieSvgProps<RawDatum>>,
| 'startAngle'
Expand All @@ -189,12 +231,20 @@ export const usePie = <RawDatum>({
| 'cornerRadius'
| 'activeInnerRadiusOffset'
| 'activeOuterRadiusOffset'
| 'activeId'
| 'onActiveIdChange'
| 'defaultActiveId'
> & {
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
radius: number
innerRadius: number
}) => {
const [activeId, setActiveId] = useState<DatumId | null>(null)
const { activeId, setActiveId } = useActiveId({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
})

const [hiddenIds, setHiddenIds] = useState<DatumId[]>([])
const pieArcs = usePieArcs({
data,
Expand Down Expand Up @@ -242,6 +292,9 @@ export const usePieFromBox = <RawDatum>({
fit = defaultProps.fit,
activeInnerRadiusOffset = defaultProps.activeInnerRadiusOffset,
activeOuterRadiusOffset = defaultProps.activeOuterRadiusOffset,
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
}: Pick<
CompletePieSvgProps<RawDatum>,
| 'width'
Expand All @@ -255,10 +308,19 @@ export const usePieFromBox = <RawDatum>({
| 'fit'
| 'activeInnerRadiusOffset'
| 'activeOuterRadiusOffset'
> & {
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
}) => {
const [activeId, setActiveId] = useState<string | number | null>(null)
> &
Pick<
Partial<CompletePieSvgProps<RawDatum>>,
'activeId' | 'onActiveIdChange' | 'defaultActiveId'
> & {
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
}) => {
const { activeId, setActiveId } = useActiveId({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
})

const [hiddenIds, setHiddenIds] = useState<DatumId[]>([])
const computedProps = useMemo(() => {
let radius = Math.min(width, height) / 2
Expand Down Expand Up @@ -335,6 +397,7 @@ export const usePieFromBox = <RawDatum>({

return {
arcGenerator,
activeId,
setActiveId,
toggleSerie,
...pieArcs,
Expand Down
3 changes: 3 additions & 0 deletions packages/pie/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export type CommonPieProps<RawDatum> = {
// interactivity
isInteractive: boolean
tooltip: React.FC<PieTooltipProps<RawDatum>>
activeId: DatumId | null
onActiveIdChange: (id: DatumId | null) => void
defaultActiveId: DatumId | null

legends: readonly LegendProps[]

Expand Down
36 changes: 36 additions & 0 deletions storybook/stories/pie/Pie.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { animated } from '@react-spring/web'
import { generateProgrammingLanguageStats } from '@nivo/generators'
Expand Down Expand Up @@ -245,3 +246,38 @@ export const CustomArcLabelComponent: Story = {
/>
),
}

const controlledPieProps = {
...commonProperties,
width: 400,
height: 400,
margin: { top: 60, right: 80, bottom: 60, left: 80 },
innerRadius: 0.4,
padAngle: 0.3,
cornerRadius: 3,
activeOuterRadiusOffset: 12,
activeInnerRadiusOffset: 12,
arcLinkLabelsDiagonalLength: 10,
arcLinkLabelsStraightLength: 10,
}

const ControlledPies = () => {
const [activeId, setActiveId] = useState<string>(commonProperties.data[1].id)

return (
<div
style={{
width: '800px',
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
}}
>
<Pie {...controlledPieProps} activeId={activeId} onActiveIdChange={setActiveId} />
<Pie {...controlledPieProps} activeId={activeId} onActiveIdChange={setActiveId} />
</div>
)
}

export const ControlledActiveId: Story = {
render: () => <ControlledPies />,
}

0 comments on commit c87658e

Please sign in to comment.