@@ -15,11 +15,20 @@ import {
15
15
barsSpacing ,
16
16
columnWidth ,
17
17
contentSidePadding ,
18
- intervalDimension ,
19
18
XAxisHeight ,
20
19
} from "./constants" ;
21
20
import { Bar } from "./Bar" ;
22
21
22
+ // When displaying the chart we must consider the time intervals to display the
23
+ // data. For example, if the total time is 10 seconds, we should display the
24
+ // data in 200ms intervals. However, if the total time is 1 minute, we should
25
+ // display the data in 5 seconds intervals. To achieve this, we define the
26
+ // dimensions object that contains the time intervals for the chart.
27
+ const dimensions = {
28
+ small : 500 ,
29
+ default : 5_000 ,
30
+ } ;
31
+
23
32
export type ChartProps = {
24
33
data : DataSection [ ] ;
25
34
onBarClick : ( label : string , section : string ) => void ;
@@ -54,8 +63,33 @@ export type Timing = Duration & {
54
63
} ;
55
64
56
65
export const Chart : FC < ChartProps > = ( { data, onBarClick } ) => {
57
- const totalDuration = calcTotalDuration ( data . flatMap ( ( d ) => d . timings ) ) ;
58
- const intervals = createIntervals ( totalDuration , intervalDimension ) ;
66
+ const totalDuration = duration ( data . flatMap ( ( d ) => d . timings ) ) ;
67
+ const totalTime = durationTime ( totalDuration ) ;
68
+ // Use smaller dimensions for the chart if the total time is less than 10
69
+ // seconds; otherwise, use default intervals.
70
+ const dimension = totalTime < 10_000 ? dimensions . small : dimensions . default ;
71
+
72
+ // XAxis intervals
73
+ const intervalsCount = Math . ceil ( totalTime / dimension ) ;
74
+ const intervals = Array . from (
75
+ { length : intervalsCount } ,
76
+ ( _ , i ) => i * dimension + dimension ,
77
+ ) ;
78
+
79
+ // Helper function to convert time into pixel size, used for setting bar width
80
+ // and offset
81
+ const calcSize = ( time : number ) : number => {
82
+ return ( columnWidth * time ) / dimension ;
83
+ } ;
84
+
85
+ const formatTime = ( time : number ) : string => {
86
+ if ( dimension === dimensions . small ) {
87
+ return `${ time . toLocaleString ( ) } ms` ;
88
+ }
89
+ return `${ ( time / 1_000 ) . toLocaleString ( undefined , {
90
+ maximumFractionDigits : 2 ,
91
+ } ) } s`;
92
+ } ;
59
93
60
94
return (
61
95
< div css = { styles . chart } >
@@ -65,7 +99,10 @@ export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
65
99
< YAxisCaption > { section . name } </ YAxisCaption >
66
100
< YAxisLabels >
67
101
{ section . timings . map ( ( t ) => (
68
- < YAxisLabel key = { t . label } id = { `${ t . label } -label` } >
102
+ < YAxisLabel
103
+ key = { t . label }
104
+ id = { `${ encodeURIComponent ( t . label ) } -label` }
105
+ >
69
106
{ t . label }
70
107
</ YAxisLabel >
71
108
) ) }
@@ -75,28 +112,28 @@ export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
75
112
</ YAxis >
76
113
77
114
< div css = { styles . main } >
78
- < XAxis labels = { intervals . map ( formatAsTimer ) } />
115
+ < XAxis labels = { intervals . map ( formatTime ) } />
79
116
< div css = { styles . content } >
80
117
{ data . map ( ( section ) => {
81
118
return (
82
119
< div key = { section . name } css = { styles . bars } >
83
120
{ section . timings . map ( ( t ) => {
84
- // The time this timing started relative to the initial timing
85
- const offset = diffInSeconds (
86
- t . startedAt ,
87
- totalDuration . startedAt ,
88
- ) ;
89
- const size = secondsToPixel ( durationToSeconds ( t ) ) ;
121
+ const offset =
122
+ t . startedAt . getTime ( ) - totalDuration . startedAt . getTime ( ) ;
123
+ const size = calcSize ( durationTime ( t ) ) ;
90
124
return (
91
125
< Bar
92
126
key = { t . label }
93
- x = { secondsToPixel ( offset ) }
127
+ x = { calcSize ( offset ) }
94
128
width = { size }
95
- afterLabel = { ` ${ durationToSeconds ( t ) . toFixed ( 2 ) } s` }
129
+ afterLabel = { formatTime ( durationTime ( t ) ) }
96
130
aria-labelledby = { `${ t . label } -label` }
97
131
ref = { applyBarHeightToLabel }
98
132
disabled = { t . count <= 1 }
99
133
onClick = { ( ) => {
134
+ if ( t . count <= 1 ) {
135
+ return ;
136
+ }
100
137
onBarClick ( t . label , section . name ) ;
101
138
} }
102
139
>
@@ -130,41 +167,22 @@ const applyBarHeightToLabel = (bar: HTMLDivElement | null) => {
130
167
// #coder_metadata.container_info[0]) will fail because it is not a valid
131
168
// selector. To handle this, we need to query by the id attribute and escape
132
169
// it with quotes.
133
- const label = document . querySelector < HTMLSpanElement > ( `[id="${ labelId } "]` ) ;
170
+ const label = document . querySelector < HTMLSpanElement > (
171
+ `[id="${ encodeURIComponent ( labelId ) } "]` ,
172
+ ) ;
134
173
if ( ! label ) {
135
174
return ;
136
175
}
137
176
label . style . height = `${ bar . clientHeight } px` ;
138
177
} ;
139
178
140
- // Format a number in seconds to 00:00:00 format
141
- const formatAsTimer = ( seconds : number ) : string => {
142
- const hours = Math . floor ( seconds / 3600 ) ;
143
- const minutes = Math . floor ( ( seconds % 3600 ) / 60 ) ;
144
- const remainingSeconds = seconds % 60 ;
145
-
146
- return `${ hours . toString ( ) . padStart ( 2 , "0" ) } :${ minutes
147
- . toString ( )
148
- . padStart ( 2 , "0" ) } :${ remainingSeconds . toString ( ) . padStart ( 2 , "0" ) } `;
149
- } ;
150
-
151
- const durationToSeconds = ( duration : Duration ) : number => {
152
- return ( duration . endedAt . getTime ( ) - duration . startedAt . getTime ( ) ) / 1000 ;
153
- } ;
154
-
155
- // Create the intervals to be used in the XAxis
156
- const createIntervals = ( duration : Duration , range : number ) : number [ ] => {
157
- const intervals = Math . ceil ( durationToSeconds ( duration ) / range ) ;
158
- return Array . from ( { length : intervals } , ( _ , i ) => i * range + range ) ;
159
- } ;
160
-
161
- const secondsToPixel = ( seconds : number ) : number => {
162
- return ( columnWidth * seconds ) / intervalDimension ;
179
+ const durationTime = ( duration : Duration ) : number => {
180
+ return duration . endedAt . getTime ( ) - duration . startedAt . getTime ( ) ;
163
181
} ;
164
182
165
183
// Combine multiple durations into a single duration by using the initial start
166
184
// time and the final end time.
167
- export const calcTotalDuration = ( durations : readonly Duration [ ] ) : Duration => {
185
+ export const duration = ( durations : readonly Duration [ ] ) : Duration => {
168
186
const sortedDurations = durations
169
187
. slice ( )
170
188
. sort ( ( a , b ) => a . startedAt . getTime ( ) - b . startedAt . getTime ( ) ) ;
@@ -177,10 +195,6 @@ export const calcTotalDuration = (durations: readonly Duration[]): Duration => {
177
195
return { startedAt : start , endedAt : end } ;
178
196
} ;
179
197
180
- const diffInSeconds = ( b : Date , a : Date ) : number => {
181
- return ( b . getTime ( ) - a . getTime ( ) ) / 1000 ;
182
- } ;
183
-
184
198
const styles = {
185
199
chart : {
186
200
display : "flex" ,
@@ -216,6 +230,8 @@ const styles = {
216
230
flexDirection : "column" ,
217
231
flex : 1 ,
218
232
borderLeft : `1px solid ${ theme . palette . divider } ` ,
233
+ height : "fit-content" ,
234
+ minHeight : "100%" ,
219
235
} ) ,
220
236
content : {
221
237
flex : 1 ,
0 commit comments