Skip to content

Commit a0b9798

Browse files
committed
re-instument optimized Sunburst with Sentry
1 parent fb36c5d commit a0b9798

File tree

1 file changed

+200
-171
lines changed

1 file changed

+200
-171
lines changed

src/ui/SunburstChart/SunburstChart.jsx

Lines changed: 200 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,26 @@ function SunburstChart({
6767
const radius = width / 6
6868

6969
// Creates a function for creating arcs representing files and folders.
70-
const drawArc = Sentry.startSpan({ name: 'SunburstChart.drawArc' }, () => {
71-
return arc()
72-
.startAngle((d) => d.x0)
73-
.endAngle((d) => d.x1)
74-
.padAngle((d) => Math.min((d.x1 - d.x0) / 2, 0.005))
75-
.padRadius(radius * 1.5)
76-
.innerRadius((d) => d.y0 * radius)
77-
.outerRadius((d) => Math.max(d.y0 * radius, d.y1 * radius - 1))
78-
})
70+
const createDrawArcFunction = (parentSpan) =>
71+
Sentry.startSpan({ name: 'SunburstChart.drawArc', parentSpan }, () => {
72+
return arc()
73+
.startAngle((d) => d.x0)
74+
.endAngle((d) => d.x1)
75+
.padAngle((d) => Math.min((d.x1 - d.x0) / 2, 0.005))
76+
.padRadius(radius * 1.5)
77+
.innerRadius((d) => d.y0 * radius)
78+
.outerRadius((d) => Math.max(d.y0 * radius, d.y1 * radius - 1))
79+
})
7980

8081
// A color function you can pass a number from 0-100 to and get a color back from the specified color range
8182
// Ex color(10.4)
82-
const color = Sentry.startSpan({ name: 'SunburstChart.color' }, () => {
83-
return scaleSequential()
84-
.domain([colorDomainMin, colorDomainMax])
85-
.interpolator(colorRange)
86-
.clamp(true)
87-
})
83+
const createColorFunction = (parentSpan) =>
84+
Sentry.startSpan({ name: 'SunburstChart.color', parentSpan }, () => {
85+
return scaleSequential()
86+
.domain([colorDomainMin, colorDomainMax])
87+
.interpolator(colorRange)
88+
.clamp(true)
89+
})
8890

8991
// Tracks previous location for rendering .. in the breadcrumb.
9092
let previous
@@ -97,167 +99,194 @@ function SunburstChart({
9799
.append('g')
98100
.attr('transform', `translate(${width / 2},${width / 2})`)
99101

100-
function renderArcs() {
101-
const nodesToRender = selectedNode
102-
.descendants()
103-
.slice(1)
104-
.filter((d) => d.depth <= selectedNode.depth + 2)
105-
106-
// Renders an arc per data point in the correct location. (Pieces of the circle that add up to a circular graph)
107-
const path = g
108-
.append('g')
109-
.selectAll('path')
110-
.data(nodesToRender)
111-
.join('path')
112-
.attr('fill', (d) => color(d?.data?.value || 0))
113-
// If data point is a file fade the background color a bit.
114-
.attr('fill-opacity', (d) => (d.children ? 1 : 0.6))
115-
.attr('pointer-events', () => 'auto')
116-
.attr('d', (d) => drawArc(d.current))
117-
118-
// Events for folders
119-
path
120-
.filter((d) => d.children)
121-
.style('cursor', 'pointer')
122-
.on('click', clickedFolder)
123-
.on('mouseover', function (_event, p) {
124-
select(this).attr('fill-opacity', 0.6)
125-
reactHoverCallback({ target: p, type: 'folder' })
126-
})
127-
.on('mouseout', function (_event, _node) {
128-
select(this).attr('fill-opacity', 1)
129-
})
130-
131-
// Events for file
132-
path
133-
.filter((d) => !d.children)
134-
.style('cursor', 'pointer')
135-
.on('click', function (_event, node) {
136-
reactClickCallback({ target: node, type: 'file' })
137-
})
138-
.on('mouseover', function (_event, node) {
139-
select(this).attr('fill-opacity', 0.6)
140-
reactHoverCallback({ target: node, type: 'file' })
141-
})
142-
143-
// Create a11y label / mouse hover tooltip
144-
const formatTitle = (d) => {
145-
const coverage = formatData(d.data.value)
146-
const filePath = d
147-
.ancestors()
148-
.map((d) => d.data.name)
149-
.reverse()
150-
.join('/')
151-
152-
return `${filePath}\n${coverage}% coverage`
153-
}
154-
155-
path.append('title').text((d) => formatTitle(d))
156-
157-
// White circle in the middle. Act's as a "back"
158-
g.append('circle')
159-
.datum(selectedNode.parent)
160-
.attr('r', radius)
161-
.attr('class', 'fill-none')
162-
.attr('fill', 'none')
163-
.attr('pointer-events', 'all')
164-
.attr('cursor', (d) => (d ? 'pointer' : 'default'))
165-
.on('click', clickedFolder)
166-
.on('mouseover', hoveredRoot)
167-
168-
g.append('text')
169-
.datum(selectedNode.parent)
170-
.text('..')
171-
// if the parent exists (i.e. not root), show the text
172-
.attr('fill-opacity', (d) => (d ? 1 : 0))
173-
.attr('text-anchor', 'middle')
174-
.attr('class', 'text-7xl fill-ds-gray-quinary select-none')
175-
.attr('cursor', 'pointer')
176-
.on('click', clickedFolder)
177-
.on('mouseover', hoveredRoot)
178-
179-
function clickedFolder(_event, node) {
180-
reactClickCallback({ target: node, type: 'folder' })
181-
changeLocation(node)
182-
}
183-
184-
function hoveredRoot(_event, node) {
185-
if (previous) {
186-
reactHoverCallback({ target: previous, type: 'folder' })
187-
return
188-
}
189-
reactHoverCallback({ target: node, type: 'folder' })
190-
}
191-
192-
function reactClickCallback({ target, type }) {
193-
if (target?.ancestors) {
194-
// Create a string from the root data down to the current item
195-
const filePath = target
196-
.ancestors()
197-
.map((d) => d.data.name)
198-
.slice(0, -1)
199-
.reverse()
200-
.join('/')
201-
202-
// callback to parent component with a path, the data node, and raw d3 data
203-
// (just in case we need it for the second iteration to listen to location changes and direct to the correct folder.)
204-
clickHandler.current({
205-
path: filePath,
206-
data: target.data,
207-
target,
208-
type,
209-
})
210-
}
211-
}
102+
const renderSunburst = () =>
103+
Sentry.startSpan(
104+
{ name: 'SunburstChart.renderSunburst' },
105+
(renderSunburstSpan) => {
106+
const nodesToRender = selectedNode
107+
.descendants()
108+
.slice(1)
109+
.filter((d) => d.depth <= selectedNode.depth + 2)
110+
111+
const drawArc = createDrawArcFunction(renderSunburstSpan)
112+
const color = createColorFunction(renderSunburstSpan)
113+
114+
// Renders an arc per data point in the correct location. (Pieces of the circle that add up to a circular graph)
115+
const path = Sentry.startSpan(
116+
{
117+
name: 'SunburstChart.renderArcs',
118+
parentSpan: renderSunburstSpan,
119+
},
120+
() =>
121+
g
122+
.append('g')
123+
.selectAll('path')
124+
.data(nodesToRender)
125+
.join('path')
126+
.attr('fill', (d) => color(d?.data?.value || 0))
127+
// If data point is a file fade the background color a bit.
128+
.attr('fill-opacity', (d) => (d.children ? 1 : 0.6))
129+
.attr('pointer-events', () => 'auto')
130+
.attr('d', (d) => drawArc(d.current))
131+
)
212132

213-
function reactHoverCallback({ target, type }) {
214-
if (target?.ancestors) {
215-
// Create a string from the root data down to the current item
216-
const filePath = target
217-
.ancestors()
218-
.map((d) => d.data.name)
219-
.slice(0, -1)
220-
.reverse()
221-
.join('/')
222-
223-
// callback to parent component with a path, the data node, and raw d3 data
224-
// (just in case we need it for the second iteration to listen to location changes and direct to the correct folder.)
225-
hoverHandler.current({
226-
path: filePath,
227-
data: target.data,
228-
target,
229-
type,
230-
})
231-
}
232-
}
133+
// Events for folders
134+
path
135+
.filter((d) => d.children)
136+
.style('cursor', 'pointer')
137+
.on('click', clickedFolder)
138+
.on('mouseover', function (_event, p) {
139+
select(this).attr('fill-opacity', 0.6)
140+
reactHoverCallback({ target: p, type: 'folder' })
141+
})
142+
.on('mouseout', function (_event, _node) {
143+
select(this).attr('fill-opacity', 1)
144+
})
233145

234-
function changeLocation(node) {
235-
// Because you can move two layers at a time previous !== parent
236-
previous = node
237-
238-
if (node) {
239-
// Update the selected node
240-
setSelectedNode(
241-
node.each((d) => {
242-
// determine x0 and y0
243-
const x0Min = Math.min(1, (d.x0 - node.x0) / (node.x1 - node.x0))
244-
const x0 = Math.max(0, x0Min) * 2 * Math.PI
245-
const y0 = Math.max(0, d.y0 - node.depth)
246-
247-
// determine x1 and y1
248-
const x1Min = Math.min(1, (d.x1 - node.x0) / (node.x1 - node.x0))
249-
const x1 = Math.max(0, x1Min) * 2 * Math.PI
250-
const y1 = Math.max(0, d.y1 - node.depth)
251-
252-
// update the cords for the node
253-
d.current = { x0, y0, x1, y1 }
146+
// Events for file
147+
path
148+
.filter((d) => !d.children)
149+
.style('cursor', 'pointer')
150+
.on('click', function (_event, node) {
151+
reactClickCallback({ target: node, type: 'file' })
254152
})
255-
)
153+
.on('mouseover', function (_event, node) {
154+
select(this).attr('fill-opacity', 0.6)
155+
reactHoverCallback({ target: node, type: 'file' })
156+
})
157+
158+
// Create a11y label / mouse hover tooltip
159+
const formatTitle = (d) => {
160+
const coverage = formatData(d.data.value)
161+
const filePath = d
162+
.ancestors()
163+
.map((d) => d.data.name)
164+
.reverse()
165+
.join('/')
166+
167+
return `${filePath}\n${coverage}% coverage`
168+
}
169+
170+
path.append('title').text((d) => formatTitle(d))
171+
172+
// White circle in the middle. Act's as a "back"
173+
g.append('circle')
174+
.datum(selectedNode.parent)
175+
.attr('r', radius)
176+
.attr('class', 'fill-none')
177+
.attr('fill', 'none')
178+
.attr('pointer-events', 'all')
179+
.attr('cursor', (d) => (d ? 'pointer' : 'default'))
180+
.on('click', clickedFolder)
181+
.on('mouseover', hoveredRoot)
182+
183+
g.append('text')
184+
.datum(selectedNode.parent)
185+
.text('..')
186+
// if the parent exists (i.e. not root), show the text
187+
.attr('fill-opacity', (d) => (d ? 1 : 0))
188+
.attr('text-anchor', 'middle')
189+
.attr('class', 'text-7xl fill-ds-gray-quinary select-none')
190+
.attr('cursor', 'pointer')
191+
.on('click', clickedFolder)
192+
.on('mouseover', hoveredRoot)
193+
194+
function clickedFolder(_event, node) {
195+
reactClickCallback({ target: node, type: 'folder' })
196+
changeLocation(node)
197+
}
198+
199+
function hoveredRoot(_event, node) {
200+
if (previous) {
201+
reactHoverCallback({ target: previous, type: 'folder' })
202+
return
203+
}
204+
reactHoverCallback({ target: node, type: 'folder' })
205+
}
206+
207+
function reactClickCallback({ target, type }) {
208+
if (target?.ancestors) {
209+
// Create a string from the root data down to the current item
210+
const filePath = target
211+
.ancestors()
212+
.map((d) => d.data.name)
213+
.slice(0, -1)
214+
.reverse()
215+
.join('/')
216+
217+
// callback to parent component with a path, the data node, and raw d3 data
218+
// (just in case we need it for the second iteration to listen to location changes and direct to the correct folder.)
219+
clickHandler.current({
220+
path: filePath,
221+
data: target.data,
222+
target,
223+
type,
224+
})
225+
}
226+
}
227+
228+
function reactHoverCallback({ target, type }) {
229+
if (target?.ancestors) {
230+
// Create a string from the root data down to the current item
231+
const filePath = target
232+
.ancestors()
233+
.map((d) => d.data.name)
234+
.slice(0, -1)
235+
.reverse()
236+
.join('/')
237+
238+
// callback to parent component with a path, the data node, and raw d3 data
239+
// (just in case we need it for the second iteration to listen to location changes and direct to the correct folder.)
240+
hoverHandler.current({
241+
path: filePath,
242+
data: target.data,
243+
target,
244+
type,
245+
})
246+
}
247+
}
248+
249+
const changeLocation = (node) =>
250+
Sentry.startSpan(
251+
{
252+
name: 'SunburstChart.changeLocation',
253+
parentSpan: renderSunburstSpan,
254+
},
255+
() => {
256+
// Because you can move two layers at a time previous !== parent
257+
previous = node
258+
259+
if (node) {
260+
// Update the selected node
261+
setSelectedNode(
262+
node.each((d) => {
263+
// determine x0 and y0
264+
const x0Min = Math.min(
265+
1,
266+
(d.x0 - node.x0) / (node.x1 - node.x0)
267+
)
268+
const x0 = Math.max(0, x0Min) * 2 * Math.PI
269+
const y0 = Math.max(0, d.y0 - node.depth)
270+
271+
// determine x1 and y1
272+
const x1Min = Math.min(
273+
1,
274+
(d.x1 - node.x0) / (node.x1 - node.x0)
275+
)
276+
const x1 = Math.max(0, x1Min) * 2 * Math.PI
277+
const y1 = Math.max(0, d.y1 - node.depth)
278+
279+
// update the cords for the node
280+
d.current = { x0, y0, x1, y1 }
281+
})
282+
)
283+
}
284+
}
285+
)
256286
}
257-
}
258-
}
287+
)
259288

260-
renderArcs()
289+
renderSunburst()
261290

262291
return () => {
263292
// On cleanup remove the root DOM generated by D3

0 commit comments

Comments
 (0)