@@ -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