/
hooks.ts
142 lines (126 loc) · 4.56 KB
/
hooks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { useMemo } from 'react'
import sortBy from 'lodash/sortBy'
import cloneDeep from 'lodash/cloneDeep'
import { usePropertyAccessor, useTheme, useValueFormatter } from '@nivo/core'
import { Arc, ArcGenerator, useArcGenerator } from '@nivo/arcs'
import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors'
import { partition as d3Partition, hierarchy as d3Hierarchy } from 'd3-hierarchy'
import { CommonProps, ComputedDatum, DataProps, DatumId, SunburstCustomLayerProps } from './types'
export const useSunburst = <RawDatum>({
childColor,
colors,
cornerRadius,
data,
id,
value,
valueFormat,
radius,
}: {
childColor: CommonProps<RawDatum>['childColor']
colors: CommonProps<RawDatum>['colors']
cornerRadius: CommonProps<RawDatum>['cornerRadius']
data: DataProps<RawDatum>['data']
id: NonNullable<DataProps<RawDatum>['id']>
radius: number
value: NonNullable<DataProps<RawDatum>['value']>
valueFormat: DataProps<RawDatum>['valueFormat']
}) => {
const theme = useTheme()
const getColor = useOrdinalColorScale<Omit<ComputedDatum<RawDatum>, 'color' | 'fill'>>(
colors,
'id'
)
const getChildColor = useInheritedColor<ComputedDatum<RawDatum>>(childColor, theme)
const getId = usePropertyAccessor<RawDatum, DatumId>(id)
const getValue = usePropertyAccessor<RawDatum, number>(value)
const formatValue = useValueFormatter<number>(valueFormat)
const nodes: ComputedDatum<RawDatum>[] = useMemo(() => {
// d3 mutates the data for performance reasons,
// however it does not work well with reactive programming,
// this ensures that we don't mutate the input data
const clonedData = cloneDeep(data)
const hierarchy = d3Hierarchy(clonedData).sum(getValue)
const partition = d3Partition<RawDatum>().size([2 * Math.PI, radius * radius])
const descendants = partition(hierarchy)
.descendants()
.filter(descendant => descendant.depth > 0)
const total = hierarchy.value ?? 0
// It's important to sort node by depth,
// it ensures that we assign a parent node
// which has already been computed, because parent nodes
// are going to be computed first
const sortedNodes = sortBy(descendants, 'depth')
return sortedNodes.reduce<ComputedDatum<RawDatum>[]>((acc, descendant) => {
const id = getId(descendant.data)
const value = descendant.value!
const percentage = (100 * value) / total
const path = descendant.ancestors().map(ancestor => getId(ancestor.data))
const arc: Arc = {
startAngle: descendant.x0,
endAngle: descendant.x1,
innerRadius: Math.sqrt(descendant.y0),
outerRadius: Math.sqrt(descendant.y1),
}
let parent: ComputedDatum<RawDatum> | undefined
if (descendant.parent) {
parent = acc.find(node => node.id === getId(descendant.parent!.data))
}
const normalizedNode: ComputedDatum<RawDatum> = {
id,
path,
value,
percentage,
formattedValue: valueFormat ? formatValue(value) : `${percentage.toFixed(2)}%`,
color: '',
arc,
data: descendant.data,
depth: descendant.depth,
height: descendant.height,
}
if (childColor !== 'noinherit' && parent && descendant.depth > 1) {
normalizedNode.color = getChildColor(parent)
} else {
normalizedNode.color = getColor(normalizedNode)
}
return [...acc, normalizedNode]
}, [])
}, [
data,
radius,
getValue,
getId,
valueFormat,
formatValue,
childColor,
getColor,
getChildColor,
])
const arcGenerator = useArcGenerator({ cornerRadius })
return { arcGenerator, nodes }
}
/**
* Memoize the context to pass to custom layers.
*/
export const useSunburstLayerContext = <RawDatum>({
nodes,
arcGenerator,
centerX,
centerY,
radius,
}: {
nodes: ComputedDatum<RawDatum>[]
arcGenerator: ArcGenerator
centerX: number
centerY: number
radius: number
}): SunburstCustomLayerProps<RawDatum> =>
useMemo(
() => ({
nodes,
arcGenerator,
centerX,
centerY,
radius,
}),
[nodes, arcGenerator, centerX, centerY, radius]
)