Skip to content

Commit 0fb0352

Browse files
move the initial state to be stored in react state
1 parent 6c7cb5d commit 0fb0352

File tree

1 file changed

+57
-83
lines changed

1 file changed

+57
-83
lines changed

src/ui/SunburstChart/SunburstChart.jsx

+57-83
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { select } from 'd3-selection'
44
import { arc } from 'd3-shape'
55
import { transition } from 'd3-transition' // Kinda odd d3 behavior seems to need to imported but not directly referenced.
66
import PropTypes from 'prop-types'
7-
import { useLayoutEffect, useRef } from 'react'
7+
import { useLayoutEffect, useRef, useState } from 'react'
88

99
// Modification of https://observablehq.com/@d3/zoomable-sunburst
10-
import { colorRange, formatData, partitionFn } from './utils'
10+
import { arcVisible, colorRange, formatData, partitionFn } from './utils'
11+
1112
function SunburstChart({
1213
data,
1314
onClick = () => {},
@@ -24,13 +25,41 @@ function SunburstChart({
2425
const clickHandler = useRef(onClick)
2526
const hoverHandler = useRef(onHover)
2627

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+
2751
// In this case D3 is handling rendering not React, so useLayoutEffect is used to handle rendering
2852
// and changes outside of the React lifecycle.
2953
useLayoutEffect(() => {
54+
// early return if the ref is not found
55+
if (!ref.current) return
56+
3057
// The svg render size. This is *not* equivalent to normal rendering.
3158
const width = svgRenderSize
59+
3260
// Overall graph size
3361
const radius = width / 6
62+
3463
// Creates a function for creating arcs representing files and folders.
3564
const drawArc = arc()
3665
.startAngle((d) => d.x0)
@@ -39,56 +68,17 @@ function SunburstChart({
3968
.padRadius(radius * 1.5)
4069
.innerRadius((d) => d.y0 * radius)
4170
.outerRadius((d) => Math.max(d.y0 * radius, d.y1 * radius - 1))
71+
4272
// A color function you can pass a number from 0-100 to and get a color back from the specified color range
4373
// Ex color(10.4)
4474
const color = scaleSequential()
4575
.domain([colorDomainMin, colorDomainMax])
4676
.interpolator(colorRange)
4777
.clamp(true)
78+
4879
// Tracks previous location for rendering .. in the breadcrumb.
4980
let previous
5081

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-
9282
// Bind D3 to a DOM element
9383
const svg = select(ref.current)
9484

@@ -224,47 +214,36 @@ function SunburstChart({
224214
function changeLocation(p) {
225215
// Because you can move two layers at a time previous !== parent
226216
previous = p
227-
217+
const selected = p.parent || root
228218
const t = transition(g).duration(750)
229219

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 })
241222
}
242223

243224
function handleArcsUpdate({ current, selected, transition }) {
244225
parent.datum(selected)
245226

246227
// 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+
})
268247

269248
// Transition the data on all arcs, even the ones that aren’t visible,
270249
// so that if this transition is interrupted, entering arcs will start
@@ -297,20 +276,15 @@ function SunburstChart({
297276
}
298277
}
299278

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-
305279
// On cleanup remove the root DOM generated by D3
306280
return () => g.remove()
307-
}, [colorDomainMax, colorDomainMin, data, svgFontSize, svgRenderSize])
281+
}, [colorDomainMax, colorDomainMin, data, root, svgFontSize, svgRenderSize])
308282

309283
return (
310284
<svg
285+
ref={ref}
311286
data-testid="sunburst"
312287
viewBox={[0, 0, svgRenderSize, svgRenderSize]}
313-
ref={ref}
314288
/>
315289
)
316290
}

0 commit comments

Comments
 (0)