Skip to content

Commit

Permalink
feat(pie): add support for forwarding legend data
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Nov 20, 2023
1 parent a1a774d commit 4fa26a2
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 17 deletions.
5 changes: 4 additions & 1 deletion packages/pie/src/Pie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@nivo/core'
import { ArcLabelsLayer, ArcLinkLabelsLayer } from '@nivo/arcs'
import { InheritedColorConfig } from '@nivo/colors'
import PieLegends from './PieLegends'
import { PieLegends } from './PieLegends'
import { useNormalizedData, usePieFromBox, usePieLayerContext } from './hooks'
import { ComputedDatum, PieLayer, PieSvgProps, PieLayerId, MayHaveLabel } from './types'
import { defaultProps } from './props'
Expand Down Expand Up @@ -81,6 +81,8 @@ const InnerPie = <RawDatum extends MayHaveLabel>({
transitionMode = defaultProps.transitionMode,

legends = defaultProps.legends,
forwardLegendData,

role = defaultProps.role,
}: PieSvgProps<RawDatum>) => {
const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions(
Expand Down Expand Up @@ -123,6 +125,7 @@ const InnerPie = <RawDatum extends MayHaveLabel>({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
forwardLegendData,
})

const boundDefs = bindDefs(defs, dataWithArc, fill)
Expand Down
2 changes: 2 additions & 0 deletions packages/pie/src/PieCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const InnerPieCanvas = <RawDatum extends MayHaveLabel>({
defaultActiveId,

legends = defaultProps.legends,
forwardLegendData,
}: PieCanvasProps<RawDatum>) => {
const canvasEl = useRef<HTMLCanvasElement | null>(null)
const theme = useTheme()
Expand Down Expand Up @@ -107,6 +108,7 @@ const InnerPieCanvas = <RawDatum extends MayHaveLabel>({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
forwardLegendData,
})

const getBorderColor = useInheritedColor<ComputedDatum<RawDatum>>(borderColor, theme)
Expand Down
8 changes: 3 additions & 5 deletions packages/pie/src/PieLegends.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { BoxLegendSvg } from '@nivo/legends'
import { CompletePieSvgProps, ComputedDatum, DatumId } from './types'
import { CompletePieSvgProps, DatumId, LegendDatum } from './types'

interface PieLegendsProps<RawDatum> {
width: number
height: number
legends: CompletePieSvgProps<RawDatum>['legends']
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
data: LegendDatum<RawDatum>[]
toggleSerie: (id: DatumId) => void
}

const PieLegends = <RawDatum,>({
export const PieLegends = <RawDatum,>({
width,
height,
legends,
Expand All @@ -31,5 +31,3 @@ const PieLegends = <RawDatum,>({
</>
)
}

export default PieLegends
35 changes: 30 additions & 5 deletions packages/pie/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { pie as d3Pie } from 'd3-shape'
import { useArcGenerator, computeArcBoundingBox } from '@nivo/arcs'
import {
Expand All @@ -16,6 +16,8 @@ import {
DatumId,
PieArc,
PieCustomLayerProps,
LegendDatum,
CommonPieProps,
} from './types'

/**
Expand Down Expand Up @@ -81,6 +83,7 @@ export const usePieArcs = <RawDatum>({
activeInnerRadiusOffset,
activeOuterRadiusOffset,
hiddenIds,
forwardLegendData,
}: {
data: Omit<ComputedDatum<RawDatum>, 'arc' | 'fill'>[]
// in degrees
Expand All @@ -97,9 +100,10 @@ export const usePieArcs = <RawDatum>({
activeInnerRadiusOffset: number
activeOuterRadiusOffset: number
hiddenIds: DatumId[]
forwardLegendData?: CommonPieProps<RawDatum>['forwardLegendData']
}): {
dataWithArc: Omit<ComputedDatum<RawDatum>, 'fill'>[]
legendData: Omit<ComputedDatum<RawDatum>, 'arc' | 'fill'>[]
legendData: LegendDatum<RawDatum>[]
} => {
const pie = useMemo(() => {
const innerPie = d3Pie<Omit<ComputedDatum<RawDatum>, 'arc' | 'fill'>>()
Expand All @@ -115,7 +119,7 @@ export const usePieArcs = <RawDatum>({
return innerPie
}, [startAngle, endAngle, padAngle, sortByValue])

return useMemo(() => {
const result = useMemo(() => {
const hiddenData = data.filter(item => !hiddenIds.includes(item.id))
const dataWithArc = pie(hiddenData).map(
(
Expand Down Expand Up @@ -150,7 +154,13 @@ export const usePieArcs = <RawDatum>({
}
}
)
const legendData = data.map(item => ({ ...item, hidden: hiddenIds.includes(item.id) }))
const legendData: LegendDatum<RawDatum>[] = data.map(item => ({
id: item.id,
label: item.label,
color: item.color,
hidden: hiddenIds.includes(item.id),
data: item,
}))

return { dataWithArc, legendData }
}, [
Expand All @@ -163,6 +173,16 @@ export const usePieArcs = <RawDatum>({
outerRadius,
activeOuterRadiusOffset,
])

// Forward the legends data if `forwardLegendData` is defined.
const legendData = result.legendData
const forwardLegendDataRef = useRef(forwardLegendData)
useEffect(() => {
if (typeof forwardLegendDataRef.current !== 'function') return
forwardLegendDataRef.current(legendData)
}, [forwardLegendDataRef, legendData])

return result
}

/**
Expand Down Expand Up @@ -222,6 +242,7 @@ export const usePie = <RawDatum>({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
forwardLegendData,
}: Pick<
Partial<CompletePieSvgProps<RawDatum>>,
| 'startAngle'
Expand All @@ -234,6 +255,7 @@ export const usePie = <RawDatum>({
| 'activeId'
| 'onActiveIdChange'
| 'defaultActiveId'
| 'forwardLegendData'
> & {
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
radius: number
Expand All @@ -258,6 +280,7 @@ export const usePie = <RawDatum>({
activeInnerRadiusOffset,
activeOuterRadiusOffset,
hiddenIds,
forwardLegendData,
})

const toggleSerie = useCallback((id: DatumId) => {
Expand Down Expand Up @@ -295,6 +318,7 @@ export const usePieFromBox = <RawDatum>({
activeId: activeIdFromProps,
onActiveIdChange,
defaultActiveId,
forwardLegendData,
}: Pick<
CompletePieSvgProps<RawDatum>,
| 'width'
Expand All @@ -311,7 +335,7 @@ export const usePieFromBox = <RawDatum>({
> &
Pick<
Partial<CompletePieSvgProps<RawDatum>>,
'activeId' | 'onActiveIdChange' | 'defaultActiveId'
'activeId' | 'onActiveIdChange' | 'defaultActiveId' | 'forwardLegendData'
> & {
data: Omit<ComputedDatum<RawDatum>, 'arc'>[]
}) => {
Expand Down Expand Up @@ -382,6 +406,7 @@ export const usePieFromBox = <RawDatum>({
activeInnerRadiusOffset,
activeOuterRadiusOffset,
hiddenIds,
forwardLegendData,
})

const toggleSerie = useCallback((id: DatumId) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/pie/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export type CommonPieProps<RawDatum> = {
defaultActiveId: DatumId | null

legends: readonly LegendProps[]
forwardLegendData: (data: LegendDatum<RawDatum>[]) => void

role: string
renderWrapper: boolean
Expand All @@ -135,6 +136,14 @@ export type PieSvgCustomComponents<RawDatum> = {
arcLinkLabelComponent?: ArcLinkLabelsProps<ComputedDatum<RawDatum>>['component']
}

export interface LegendDatum<RawDatum> {
id: ComputedDatum<RawDatum>['id']
label: ComputedDatum<RawDatum>['label']
color: string
hidden: boolean
data: Omit<ComputedDatum<RawDatum>, 'fill' | 'arc'>
}

export type PieSvgProps<RawDatum> = DataProps<RawDatum> &
Dimensions &
Partial<CommonPieProps<RawDatum>> &
Expand Down
2 changes: 1 addition & 1 deletion packages/waffle/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export const useWaffle = <D extends Datum = Datum>({
id: datum.id,
label: datum.label,
color: datum.color,
// fill: datum.fill,,
// fill: datum.fill,
data: datum,
}))

Expand Down
72 changes: 70 additions & 2 deletions storybook/stories/pie/Pie.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { animated } from '@react-spring/web'
import { generateProgrammingLanguageStats } from '@nivo/generators'
import { Pie } from '@nivo/pie'
import { LegendDatum, Pie } from '@nivo/pie'
import { nivoTheme } from '../nivo-theme'

const meta: Meta<typeof Pie> = {
Expand Down Expand Up @@ -281,3 +281,71 @@ const ControlledPies = () => {
export const ControlledActiveId: Story = {
render: () => <ControlledPies />,
}

const PieWithCustomLegend = () => {
const [customLegends, setCustomLegends] = useState<LegendDatum<SampleDatum>[]>([])

const valueFormat = useCallback(
(value: number) =>
`${Number(value).toLocaleString('ru-RU', {
minimumFractionDigits: 2,
})} ₽`,
[]
)

return (
<div>
<Pie
{...commonProperties}
width={500}
margin={{
top: 100,
right: 100,
bottom: 100,
left: 100,
}}
valueFormat={valueFormat}
forwardLegendData={setCustomLegends}
/>
<div>
<table className="Table">
<thead>
<tr>
<th>Color</th>
<th>ID</th>
<th>Value</th>
<th>Formatted Value</th>
<th>Label</th>
</tr>
</thead>
<tbody>
{customLegends.map(legend => {
return (
<tr key={legend.id}>
<td>
<span
className="Chip"
style={{ backgroundColor: legend.color }}
/>
</td>
<td>
<em>{legend.id}</em>
</td>
<td>
<em>{legend.data.value}</em>
</td>
<td>{legend.data.formattedValue}</td>
<td>{legend.label}</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
)
}

export const CustomLegend: Story = {
render: () => <PieWithCustomLegend />,
}
72 changes: 70 additions & 2 deletions storybook/stories/pie/PieCanvas.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { generateProgrammingLanguageStats } from '@nivo/generators'
import { PieCanvas } from '@nivo/pie'
import { LegendDatum, PieCanvas } from '@nivo/pie'
import { nivoTheme } from '../nivo-theme'

const meta: Meta<typeof PieCanvas> = {
Expand Down Expand Up @@ -161,3 +161,71 @@ const ControlledPies = () => {
export const ControlledActiveId: Story = {
render: () => <ControlledPies />,
}

const PieWithCustomLegend = () => {
const [customLegends, setCustomLegends] = useState<LegendDatum<SampleDatum>[]>([])

const valueFormat = useCallback(
(value: number) =>
`${Number(value).toLocaleString('ru-RU', {
minimumFractionDigits: 2,
})} ₽`,
[]
)

return (
<div>
<PieCanvas
{...commonProperties}
width={500}
margin={{
top: 100,
right: 100,
bottom: 100,
left: 100,
}}
valueFormat={valueFormat}
forwardLegendData={setCustomLegends}
/>
<div>
<table className="Table">
<thead>
<tr>
<th>Color</th>
<th>ID</th>
<th>Value</th>
<th>Formatted Value</th>
<th>Label</th>
</tr>
</thead>
<tbody>
{customLegends.map(legend => {
return (
<tr key={legend.id}>
<td>
<span
className="Chip"
style={{ backgroundColor: legend.color }}
/>
</td>
<td>
<em>{legend.id}</em>
</td>
<td>
<em>{legend.data.value}</em>
</td>
<td>{legend.data.formattedValue}</td>
<td>{legend.label}</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
)
}

export const CustomLegend: Story = {
render: () => <PieWithCustomLegend />,
}
2 changes: 1 addition & 1 deletion storybook/stories/waffle/Waffle.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const CustomLegend: Story = {
render: args => {
const [legends, setLegends] = useState<LegendDatum<Datum>[]>([])

const formatValue = useCallback((value: number) => `${value} peolpe`, [])
const formatValue = useCallback((value: number) => `${value} people`, [])

return (
<div>
Expand Down

1 comment on commit 4fa26a2

@vercel
Copy link

@vercel vercel bot commented on 4fa26a2 Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nivo – ./

nivo-git-master-plouc.vercel.app
nivo.rocks
nivo-plouc.vercel.app
nivo.vercel.app
www.nivo.rocks

Please sign in to comment.