@@ -67,24 +67,26 @@ function SunburstChart({
67
67
const radius = width / 6
68
68
69
69
// 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
+ } )
79
80
80
81
// A color function you can pass a number from 0-100 to and get a color back from the specified color range
81
82
// 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
+ } )
88
90
89
91
// Tracks previous location for rendering .. in the breadcrumb.
90
92
let previous
@@ -97,167 +99,194 @@ function SunburstChart({
97
99
. append ( 'g' )
98
100
. attr ( 'transform' , `translate(${ width / 2 } ,${ width / 2 } )` )
99
101
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
+ )
212
132
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
+ } )
233
145
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' } )
254
152
} )
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
+ )
256
286
}
257
- }
258
- }
287
+ )
259
288
260
- renderArcs ( )
289
+ renderSunburst ( )
261
290
262
291
return ( ) => {
263
292
// On cleanup remove the root DOM generated by D3
0 commit comments