Skip to content

Commit 5a55c73

Browse files
committed
feat(sunburst): add innerRadius and renderRootNode props
1 parent d1115eb commit 5a55c73

File tree

11 files changed

+26278
-33957
lines changed

11 files changed

+26278
-33957
lines changed

packages/sunburst/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
"@nivo/tooltip": "workspace:*",
3737
"@types/d3-hierarchy": "^3.1.7",
3838
"d3-hierarchy": "^3.1.2",
39+
"@types/d3-scale": "^4.0.8",
40+
"d3-scale": "^4.0.2",
3941
"lodash": "^4.17.21"
4042
},
4143
"peerDependencies": {

packages/sunburst/src/Sunburst.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const InnerSunburst = <RawDatum,>({
2525
data,
2626
id = defaultProps.id,
2727
value = defaultProps.value,
28+
innerRadius = defaultProps.innerRadius,
29+
renderRootNode = defaultProps.renderRootNode,
2830
valueFormat,
2931
cornerRadius = defaultProps.cornerRadius,
3032
layers = defaultProps.layers as SunburstLayer<RawDatum>[],
@@ -74,6 +76,8 @@ const InnerSunburst = <RawDatum,>({
7476
valueFormat,
7577
radius,
7678
cornerRadius,
79+
innerRadius,
80+
renderRootNode,
7781
colors,
7882
colorBy,
7983
inheritColorFromParent,

packages/sunburst/src/hooks.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMemo } from 'react'
22
import { partition as d3Partition, hierarchy as d3Hierarchy } from 'd3-hierarchy'
3+
import { scaleRadial as d3ScaleRadial } from 'd3-scale'
34
import cloneDeep from 'lodash/cloneDeep.js'
45
import sortBy from 'lodash/sortBy.js'
56
import { usePropertyAccessor, useValueFormatter } from '@nivo/core'
@@ -22,6 +23,8 @@ export const useSunburst = <RawDatum>({
2223
valueFormat,
2324
radius,
2425
cornerRadius = defaultProps.cornerRadius,
26+
innerRadius = defaultProps.innerRadius,
27+
renderRootNode = defaultProps.renderRootNode,
2528
colors = defaultProps.colors,
2629
colorBy = defaultProps.colorBy,
2730
inheritColorFromParent = defaultProps.inheritColorFromParent,
@@ -33,6 +36,8 @@ export const useSunburst = <RawDatum>({
3336
valueFormat?: DataProps<RawDatum>['valueFormat']
3437
radius: number
3538
cornerRadius?: SunburstCommonProps<RawDatum>['cornerRadius']
39+
innerRadius?: SunburstCommonProps<RawDatum>['innerRadius']
40+
renderRootNode?: SunburstCommonProps<RawDatum>['renderRootNode']
3641
colors?: SunburstCommonProps<RawDatum>['colors']
3742
colorBy?: SunburstCommonProps<RawDatum>['colorBy']
3843
inheritColorFromParent?: SunburstCommonProps<RawDatum>['inheritColorFromParent']
@@ -58,8 +63,10 @@ export const useSunburst = <RawDatum>({
5863
const hierarchy = d3Hierarchy(clonedData).sum(getValue)
5964

6065
const partition = d3Partition<RawDatum>().size([2 * Math.PI, radius * radius])
61-
// exclude root node
62-
const descendants = partition(hierarchy).descendants().slice(1)
66+
// exclude root node if renderRootNode is false
67+
const descendants = renderRootNode
68+
? partition(hierarchy).descendants()
69+
: partition(hierarchy).descendants().slice(1)
6370

6471
const total = hierarchy.value ?? 0
6572

@@ -69,6 +76,12 @@ export const useSunburst = <RawDatum>({
6976
// are going to be computed first
7077
const sortedNodes = sortBy(descendants, 'depth')
7178

79+
const innerRadiusOffset = radius * Math.min(innerRadius, 1)
80+
81+
const maxDepth = Math.max(...sortedNodes.map(n => n.depth))
82+
83+
const radiusScale = d3ScaleRadial().domain([0, maxDepth]).range([innerRadiusOffset, radius])
84+
7285
return sortedNodes.reduce<ComputedDatum<RawDatum>[]>((acc, descendant) => {
7386
const id = getId(descendant.data)
7487
// d3 hierarchy node value is optional by default as it depends on
@@ -79,12 +92,13 @@ export const useSunburst = <RawDatum>({
7992
const value = descendant.value!
8093
const percentage = (100 * value) / total
8194
const path = descendant.ancestors().map(ancestor => getId(ancestor.data))
95+
const isRootNode = renderRootNode && descendant.depth === 0
8296

8397
const arc: Arc = {
8498
startAngle: descendant.x0,
8599
endAngle: descendant.x1,
86-
innerRadius: Math.sqrt(descendant.y0),
87-
outerRadius: Math.sqrt(descendant.y1),
100+
innerRadius: isRootNode ? 0 : radiusScale(descendant.depth - 1),
101+
outerRadius: isRootNode ? innerRadiusOffset : radiusScale(descendant.depth),
88102
}
89103

90104
let parent: ComputedDatum<RawDatum> | undefined
@@ -119,6 +133,8 @@ export const useSunburst = <RawDatum>({
119133
}, [
120134
data,
121135
radius,
136+
innerRadius,
137+
renderRootNode,
122138
getValue,
123139
getId,
124140
valueFormat,

packages/sunburst/src/props.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const defaultProps = {
77
id: 'id',
88
value: 'value',
99
cornerRadius: 0,
10+
innerRadius: 0.4,
11+
renderRootNode: false,
1012
layers: ['arcs', 'arcLabels'] as SunburstLayerId[],
1113
colors: { scheme: 'nivo' } as unknown as OrdinalColorScaleConfig,
1214
colorBy: 'id' as const,

packages/sunburst/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export type SunburstCommonProps<RawDatum> = {
5858
margin?: Box
5959
cornerRadius: number
6060
theme: PartialTheme
61+
innerRadius: number
62+
renderRootNode: boolean
6163
colors: OrdinalColorScaleConfig<Omit<ComputedDatum<RawDatum>, 'color' | 'fill'>>
6264
colorBy: 'id' | 'depth'
6365
inheritColorFromParent: boolean

packages/sunburst/tests/Sunburst.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,35 @@ describe('Sunburst', () => {
181181
expect(layer.exists()).toBeTruthy()
182182
expect(layer.prop('arcGenerator').cornerRadius()()).toEqual(3)
183183
})
184+
185+
it('should render the root node correctly when innerRadius and renderRootNode are set', () => {
186+
const width = 400
187+
const height = 400
188+
const innerRadiusProp = 0.6
189+
190+
// Assuming default margins { top: 0, right: 0, bottom: 0, left: 0 }
191+
// as per Sunburst defaultProps
192+
const chartRadius = Math.min(width, height) / 2
193+
const expectedRootOuterRadius = chartRadius * innerRadiusProp
194+
195+
const wrapper = mount(
196+
<Sunburst
197+
width={width}
198+
height={height}
199+
data={sampleData} // sampleData has a root with id: 'root'
200+
innerRadius={innerRadiusProp}
201+
renderRootNode
202+
/>
203+
)
204+
205+
const rootArcShape = wrapper.find(ArcShape).filterWhere(n => n.prop('datum').depth === 0)
206+
expect(rootArcShape.exists()).toBe(true)
207+
208+
const rootDatum = rootArcShape.prop('datum')
209+
expect(rootDatum.id).toEqual('root') // Verify we got the correct root node
210+
expect(rootDatum.arc.innerRadius).toEqual(0)
211+
expect(rootDatum.arc.outerRadius).toEqual(expectedRootOuterRadius)
212+
})
184213
})
185214

186215
describe('colors', () => {

0 commit comments

Comments
 (0)