@@ -4,10 +4,11 @@ import { select } from 'd3-selection'
4
4
import { arc } from 'd3-shape'
5
5
import { transition } from 'd3-transition' // Kinda odd d3 behavior seems to need to imported but not directly referenced.
6
6
import PropTypes from 'prop-types'
7
- import { useLayoutEffect , useRef } from 'react'
7
+ import { useLayoutEffect , useRef , useState } from 'react'
8
8
9
9
// Modification of https://observablehq.com/@d 3/zoomable-sunburst
10
- import { colorRange , formatData , partitionFn } from './utils'
10
+ import { arcVisible , colorRange , formatData , partitionFn } from './utils'
11
+
11
12
function SunburstChart ( {
12
13
data,
13
14
onClick = ( ) => { } ,
@@ -24,13 +25,41 @@ function SunburstChart({
24
25
const clickHandler = useRef ( onClick )
25
26
const hoverHandler = useRef ( onHover )
26
27
28
+ const [ root ] = useState ( ( ) => {
29
+ const stack = [ data ]
30
+ const result = { ...data , value : selectorHandler . current ( data ) }
31
+ const nodeMap = new Map ( )
32
+ nodeMap . set ( data , result )
33
+
34
+ while ( stack . length > 0 ) {
35
+ const node = stack . pop ( )
36
+ const currentNode = nodeMap . get ( node )
37
+
38
+ if ( Array . isArray ( node . children ) ) {
39
+ currentNode . children = node . children . map ( ( child ) => {
40
+ const newChild = { ...child , value : selectorHandler . current ( child ) }
41
+ nodeMap . set ( child , newChild )
42
+ stack . push ( child )
43
+ return newChild
44
+ } )
45
+ }
46
+ }
47
+
48
+ return partitionFn ( result ) . each ( ( d ) => ( d . current = d ) )
49
+ } )
50
+
27
51
// In this case D3 is handling rendering not React, so useLayoutEffect is used to handle rendering
28
52
// and changes outside of the React lifecycle.
29
53
useLayoutEffect ( ( ) => {
54
+ // early return if the ref is not found
55
+ if ( ! ref . current ) return
56
+
30
57
// The svg render size. This is *not* equivalent to normal rendering.
31
58
const width = svgRenderSize
59
+
32
60
// Overall graph size
33
61
const radius = width / 6
62
+
34
63
// Creates a function for creating arcs representing files and folders.
35
64
const drawArc = arc ( )
36
65
. startAngle ( ( d ) => d . x0 )
@@ -39,56 +68,17 @@ function SunburstChart({
39
68
. padRadius ( radius * 1.5 )
40
69
. innerRadius ( ( d ) => d . y0 * radius )
41
70
. outerRadius ( ( d ) => Math . max ( d . y0 * radius , d . y1 * radius - 1 ) )
71
+
42
72
// A color function you can pass a number from 0-100 to and get a color back from the specified color range
43
73
// Ex color(10.4)
44
74
const color = scaleSequential ( )
45
75
. domain ( [ colorDomainMin , colorDomainMax ] )
46
76
. interpolator ( colorRange )
47
77
. clamp ( true )
78
+
48
79
// Tracks previous location for rendering .. in the breadcrumb.
49
80
let previous
50
81
51
- // const selectorMutate = (node) => {
52
- // if (Array.isArray(node.children)) {
53
- // return {
54
- // ...node,
55
- // value: selectorHandler.current(node),
56
- // children: node.children.map((child) => selectorMutate(child)),
57
- // }
58
- // }
59
-
60
- // return { ...node, value: selectorHandler.current(node) }
61
- // }
62
-
63
- const selectorMutate = ( rootNode ) => {
64
- const stack = [ rootNode ]
65
- const result = { ...rootNode , value : selectorHandler . current ( rootNode ) }
66
- const nodeMap = new Map ( )
67
- nodeMap . set ( rootNode , result )
68
-
69
- while ( stack . length > 0 ) {
70
- const node = stack . pop ( )
71
- const currentNode = nodeMap . get ( node )
72
-
73
- if ( Array . isArray ( node . children ) ) {
74
- currentNode . children = node . children . map ( ( child ) => {
75
- const newChild = { ...child , value : selectorHandler . current ( child ) }
76
- nodeMap . set ( child , newChild )
77
- stack . push ( child )
78
- return newChild
79
- } )
80
- }
81
- }
82
-
83
- return result
84
- }
85
-
86
- // Process data for use in D3
87
- const formatted = selectorMutate ( data )
88
- const root = partitionFn ( formatted )
89
-
90
- root . each ( ( d ) => ( d . current = d ) )
91
-
92
82
// Bind D3 to a DOM element
93
83
const svg = select ( ref . current )
94
84
@@ -224,47 +214,36 @@ function SunburstChart({
224
214
function changeLocation ( p ) {
225
215
// Because you can move two layers at a time previous !== parent
226
216
previous = p
227
-
217
+ const selected = p . parent || root
228
218
const t = transition ( g ) . duration ( 750 )
229
219
230
- handleArcsUpdate ( {
231
- current : p ,
232
- selected : p . parent || root ,
233
- transition : t ,
234
- } )
235
-
236
- handleTextUpdate ( {
237
- current : p ,
238
- selected : p . parent || root ,
239
- transition : t ,
240
- } )
220
+ handleArcsUpdate ( { current : p , selected, transition : t } )
221
+ handleTextUpdate ( { current : p , selected, transition : t } )
241
222
}
242
223
243
224
function handleArcsUpdate ( { current, selected, transition } ) {
244
225
parent . datum ( selected )
245
226
246
227
// Handle animating in/out of a folder
247
- root . each (
248
- ( d ) =>
249
- ( d . target = {
250
- x0 :
251
- Math . max (
252
- 0 ,
253
- Math . min ( 1 , ( d . x0 - current . x0 ) / ( current . x1 - current . x0 ) )
254
- ) *
255
- 2 *
256
- Math . PI ,
257
- x1 :
258
- Math . max (
259
- 0 ,
260
- Math . min ( 1 , ( d . x1 - current . x0 ) / ( current . x1 - current . x0 ) )
261
- ) *
262
- 2 *
263
- Math . PI ,
264
- y0 : Math . max ( 0 , d . y0 - current . depth ) ,
265
- y1 : Math . max ( 0 , d . y1 - current . depth ) ,
266
- } )
267
- )
228
+ root . each ( ( d ) => {
229
+ // determine x0 and y0
230
+ const x0Min = Math . min (
231
+ 1 ,
232
+ ( d . x0 - current . x0 ) / ( current . x1 - current . x0 )
233
+ )
234
+ const x0 = Math . max ( 0 , x0Min ) * 2 * Math . PI
235
+ const y0 = Math . max ( 0 , d . y0 - current . depth )
236
+
237
+ // determine x1 and y1
238
+ const x1Min = Math . min (
239
+ 1 ,
240
+ ( d . x1 - current . x0 ) / ( current . x1 - current . x0 )
241
+ )
242
+ const x1 = Math . max ( 0 , x1Min ) * 2 * Math . PI
243
+ const y1 = Math . max ( 0 , d . y1 - current . depth )
244
+
245
+ d . target = { x0, y0, x1, y1 }
246
+ } )
268
247
269
248
// Transition the data on all arcs, even the ones that aren’t visible,
270
249
// so that if this transition is interrupted, entering arcs will start
@@ -297,20 +276,15 @@ function SunburstChart({
297
276
}
298
277
}
299
278
300
- // Calculate if a arc is visible
301
- function arcVisible ( d ) {
302
- return d . y1 <= 3 && d . y0 >= 1 && d . x1 > d . x0
303
- }
304
-
305
279
// On cleanup remove the root DOM generated by D3
306
280
return ( ) => g . remove ( )
307
- } , [ colorDomainMax , colorDomainMin , data , svgFontSize , svgRenderSize ] )
281
+ } , [ colorDomainMax , colorDomainMin , data , root , svgFontSize , svgRenderSize ] )
308
282
309
283
return (
310
284
< svg
285
+ ref = { ref }
311
286
data-testid = "sunburst"
312
287
viewBox = { [ 0 , 0 , svgRenderSize , svgRenderSize ] }
313
- ref = { ref }
314
288
/>
315
289
)
316
290
}
0 commit comments