@@ -20,10 +20,11 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
20
20
const [ experimentChartData , setExperimentChartData ] = useState ( { _id : '' , experimentId : '' , resultContent : '' } ) ;
21
21
const [ loading , setLoading ] = useState ( true ) ;
22
22
const [ xAxis , setXAxis ] = useState ( 'X' ) ;
23
- // const [yAxis, setYAxis ] = useState('Y ');
23
+ const [ aggregateMode , setAggregateMode ] = useState ( 'sum ' ) ;
24
24
const [ headers , setHeaders ] = useState < string [ ] > ( [ ] ) ;
25
25
const [ img , setImg ] = useState < string > ( '' ) ;
26
26
const [ isFullscreen , setIsFullscreen ] = useState ( false ) ;
27
+ const [ aggregateData , setAggregateData ] = useState ( false ) ;
27
28
28
29
const toggleFullscreen = ( ) => {
29
30
setIsFullscreen ( ! isFullscreen ) ;
@@ -35,6 +36,8 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
35
36
const setBoxPlot = ( ) => setChartType ( 'boxplot' ) ;
36
37
const setViolin = ( ) => setChartType ( 'violin' ) ;
37
38
39
+ const aggregateModes = [ 'sum' , 'count' , 'average' , 'median' , 'mode' ]
40
+
38
41
useEffect ( ( ) => {
39
42
fetch ( `/api/download/csv/${ project . expId } ` ) . then ( ( response ) => response . json ( ) ) . then ( ( record ) => {
40
43
setExperimentChartData ( record ) ;
@@ -64,13 +67,8 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
64
67
const headers = rows [ 0 ] . split ( ',' ) as string [ ] ;
65
68
//Create a dictionary to store the data
66
69
const dataDict = { } as any ;
70
+ const splitRows = [ ] as any ;
67
71
68
- // Iterate through the rows and put them under the correct header
69
- for ( let i = 0 ; i < headers . length ; i ++ ) {
70
- dataDict [ headers [ i ] ] = [ ] ;
71
- }
72
-
73
- // Iterate through the rows and put them under the correct header
74
72
for ( let i = 1 ; i < rows . length ; i ++ ) {
75
73
// Split the row by commas when not inside quotes
76
74
const row = rows [ i ] . split ( / , (? = (?: (?: [ ^ " ] * " ) { 2 } ) * [ ^ " ] * $ ) / ) ;
@@ -79,9 +77,21 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
79
77
// If the value is a number, convert it to a number
80
78
if ( ! isNaN ( val ) ) {
81
79
row [ j ] = parseFloat ( val ) ;
82
- dataDict [ headers [ j ] ] . push ( row [ j ] ) ;
83
80
}
84
81
}
82
+ splitRows . push ( row ) ;
83
+ }
84
+
85
+ // Initialize dataDict with arrays
86
+ for ( let i = 0 ; i < headers . length ; i ++ ) {
87
+ dataDict [ headers [ i ] ] = [ ] ;
88
+ }
89
+
90
+ // Iterate through the rows and put them under the correct header
91
+ for ( let i = 1 ; i < splitRows . length ; i ++ ) {
92
+ for ( let j = 0 ; j < splitRows [ i ] . length ; j ++ ) {
93
+ dataDict [ headers [ j ] ] . push ( splitRows [ i ] [ j ] ) ;
94
+ }
85
95
}
86
96
87
97
//Remove items with empty arrays
@@ -95,6 +105,70 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
95
105
const xIndex = headers . indexOf ( xAxis ) ;
96
106
const xList = headers . includes ( xAxis ) ? dataDict [ xAxis ] : dataDict [ headers [ 0 ] ] ;
97
107
const yLists = headers . map ( ( header ) => dataDict [ header ] ) ;
108
+
109
+ //If we need to aggregate the data, go through and sum the values of duplicates
110
+ if ( aggregateData && chartType != 'boxplot' && chartType != 'violin' ) {
111
+ const uniqueX = Array . from ( new Set ( xList ) ) ;
112
+ const uniqueY = [ ] as any [ ] ;
113
+ for ( let i = 0 ; i < yLists . length ; i ++ ) {
114
+ const yList = yLists [ i ] ;
115
+ const uniqueYList = [ ] as any [ ] ;
116
+ for ( let j = 0 ; j < uniqueX . length ; j ++ ) {
117
+ const x = uniqueX [ j ] ;
118
+ const indices = xList . reduce ( ( acc , e , i ) => ( e === x ? acc . concat ( i ) : acc ) , [ ] ) ;
119
+ if ( aggregateMode == 'sum' ) {
120
+ const sum = indices . reduce ( ( acc , e ) => acc + yList [ e ] , 0 ) ;
121
+ uniqueYList . push ( sum ) ;
122
+ }
123
+ else if ( aggregateMode == 'count' ) {
124
+ uniqueYList . push ( indices . length ) ;
125
+ }
126
+ else if ( aggregateMode == 'average' ) {
127
+ const sum = indices . reduce ( ( acc , e ) => acc + yList [ e ] , 0 ) ;
128
+ uniqueYList . push ( sum / indices . length ) ;
129
+ }
130
+ else if ( aggregateMode == 'median' ) {
131
+ const values = indices . map ( ( e ) => yList [ e ] ) ;
132
+ values . sort ( ( a , b ) => a - b ) ;
133
+ const half = Math . floor ( values . length / 2 ) ;
134
+ if ( values . length % 2 ) {
135
+ uniqueYList . push ( values [ half ] ) ;
136
+ }
137
+ else {
138
+ uniqueYList . push ( ( values [ half - 1 ] + values [ half ] ) / 2.0 ) ;
139
+ }
140
+ }
141
+ else if ( aggregateMode == 'mode' ) {
142
+ const values = indices . map ( ( e ) => yList [ e ] ) ;
143
+ const mode = values . sort ( ( a , b ) =>
144
+ values . filter ( ( v ) => v === a ) . length - values . filter ( ( v ) => v === b ) . length
145
+ ) . pop ( ) ;
146
+ uniqueYList . push ( mode ) ;
147
+ }
148
+ }
149
+ uniqueY . push ( uniqueYList ) ;
150
+ }
151
+ return { returnHeaders, xList : uniqueX , yLists : uniqueY , xIndex } ;
152
+ } ;
153
+
154
+ //These need to be formatted like
155
+ // {label: 'x1', data: [[1, 2, 3], [3, 4, 5]]}
156
+ if ( chartType == 'boxplot' || chartType == 'violin' ) {
157
+ const uniqueX = Array . from ( new Set ( xList ) ) ;
158
+ const uniqueY = [ ] as any [ ] ;
159
+ for ( let i = 0 ; i < yLists . length ; i ++ ) {
160
+ const yList = yLists [ i ] ;
161
+ const uniqueYList = [ ] as any [ ] ;
162
+ for ( let j = 0 ; j < uniqueX . length ; j ++ ) {
163
+ const x = uniqueX [ j ] ;
164
+ const indices = xList . reduce ( ( acc , e , i ) => ( e === x ? acc . concat ( i ) : acc ) , [ ] ) ;
165
+ const values = indices . map ( ( e ) => yList [ e ] ) ;
166
+ uniqueYList . push ( values ) ;
167
+ }
168
+ uniqueY . push ( uniqueYList ) ;
169
+ }
170
+ return { returnHeaders, xList : uniqueX , yLists : uniqueY , xIndex } ;
171
+ }
98
172
return { returnHeaders, xList, yLists, xIndex } ;
99
173
} ;
100
174
@@ -133,7 +207,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
133
207
backgroundColor : colors
134
208
} ) ) ;
135
209
136
-
137
210
const newChartInstance = new Chart ( ctx , {
138
211
type : chartType ,
139
212
data : {
@@ -166,11 +239,28 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
166
239
}
167
240
}
168
241
} ) ;
242
+
243
+ //Set all of the datasets to be unselected
244
+ //If it is a pie chart you have to use meta
245
+ if ( chartType == 'pie' ) {
246
+ var meta = newChartInstance . getDatasetMeta ( 0 ) ;
247
+ meta . data . forEach ( function ( ds ) {
248
+ ( ds as any ) . hidden = true ;
249
+ } ) ;
250
+ }
251
+ else {
252
+ newChartInstance . data . datasets . forEach ( ( dataset ) => {
253
+ dataset . hidden = true ;
254
+ } ) ;
255
+ }
256
+ newChartInstance . update ( ) ;
257
+ console . log ( newChartInstance ?. data . datasets ) ;
258
+
169
259
setChartInstance ( newChartInstance ) ;
170
260
171
261
setHeaders ( headers ) ;
172
262
}
173
- } , [ loading , experimentChartData , chartType , xAxis , isFullscreen ] ) ;
263
+ } , [ loading , experimentChartData , chartType , xAxis , isFullscreen , aggregateData , aggregateMode ] ) ;
174
264
175
265
const regenerateCanvas = ( ) => {
176
266
setCanvasKey ( prevKey => prevKey + 1 ) ;
@@ -189,12 +279,12 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
189
279
< button onClick = { setPieChart } className = 'inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 xl:w-full' >
190
280
Pie Chart
191
281
</ button >
192
- { /* <button onClick={setBoxPlot} className='inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 xl:w-full'>
282
+ < button onClick = { setBoxPlot } className = 'inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 xl:w-full' >
193
283
Box Plot
194
284
</ button >
195
285
< button onClick = { setViolin } className = 'inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 xl:w-full' >
196
286
Violin Plot
197
- </button> */ }
287
+ </ button >
198
288
</ div >
199
289
< div
200
290
className = { isFullscreen ? 'p-4 h-[65vh]' : 'p-4 h-[50vh]' } >
@@ -210,7 +300,7 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
210
300
< div className = 'p-4' >
211
301
< p className = "font-bold" > X-Axis Column:</ p >
212
302
< select
213
- className = "p-2 border rounded-md font-bold"
303
+ className = "p-2 border rounded-md font-bold w-auto "
214
304
onChange = { ( e ) => setXAxis ( e . target . value ) }
215
305
name = "xaxis"
216
306
defaultValue = { headers [ 0 ] }
@@ -223,6 +313,31 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
223
313
</ select >
224
314
225
315
</ div >
316
+ < div className = 'p-4' >
317
+ < label className = 'p-2' htmlFor = 'aggregate-data-box' > Aggregate data?</ label >
318
+ < input className = 'p-2' id = 'aggregate-data-box' type = "checkbox" checked = { aggregateData } onChange = { ( ) => setAggregateData ( ! aggregateData ) } > </ input >
319
+ {
320
+ aggregateData ?
321
+ ( < div className = 'p-4' >
322
+ < select
323
+ id = 'aggregate-select'
324
+ className = "p-2 border rounded-md font-bold w-auto"
325
+ disabled = { ! aggregateData }
326
+ name = "aggregate"
327
+ defaultValue = 'sum'
328
+ onChange = { ( e ) => setAggregateMode ( e . target . value ) }
329
+ >
330
+ { aggregateModes . map ( ( mode ) => (
331
+ < option key = { mode } value = { mode } >
332
+ { mode }
333
+ </ option >
334
+ ) ) }
335
+ </ select >
336
+ < label className = 'p-2' htmlFor = 'aggregate-select' > Aggregate Mode:</ label >
337
+ </ div > )
338
+ : null
339
+ }
340
+ </ div >
226
341
< button onClick = { downloadImage } className = 'inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 xl:w-full' >
227
342
Download Image
228
343
</ button >
0 commit comments