@@ -20,7 +20,7 @@ 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 ) ;
@@ -36,7 +36,9 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
36
36
const setBoxPlot = ( ) => setChartType ( 'boxplot' ) ;
37
37
const setViolin = ( ) => setChartType ( 'violin' ) ;
38
38
39
- /*useEffect(() => {
39
+ const aggregateModes = [ 'sum' , 'count' , 'average' , 'median' , 'mode' ]
40
+
41
+ useEffect ( ( ) => {
40
42
fetch ( `/api/download/csv/${ project . expId } ` ) . then ( ( response ) => response . json ( ) ) . then ( ( record ) => {
41
43
setExperimentChartData ( record ) ;
42
44
setLoading ( false ) ;
@@ -51,11 +53,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
51
53
} ) ;
52
54
}
53
55
) ;
54
- }, [project.expId]);*/
55
-
56
- useEffect ( ( ) => {
57
- setExperimentChartData ( { _id : '3' , experimentId : project . expId , resultContent : 'xData,yData,Classification\n1,7,A\n2,16,B\n3,12,C\n4,10,D\n5,9,A\n6,18,B\n7,12,C\n8,11,D\n9,7,A\n10,5,B\n11,16,C\n12,20,D\n13,0,A\n14,12,B\n15,18,C\n16,3,D\n17,7,A\n18,8,B\n19,19,C\n20,4,D' } ) ;
58
- setLoading ( false ) ;
59
56
} , [ project . expId ] ) ;
60
57
61
58
const downloadImage = ( ) => {
@@ -90,62 +87,89 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
90
87
dataDict [ headers [ i ] ] = [ ] ;
91
88
}
92
89
93
- if ( aggregateData )
94
- {
95
- const xList = [ ] as any ;
96
- const xIndex = headers . includes ( xAxis ) ? headers . indexOf ( xAxis ) : 0 ;
97
-
98
- //initialize xList with all of the x values
99
- for ( let i = 0 ; i < splitRows . length ; i ++ )
100
- {
101
- const xValue = splitRows [ i ] [ xIndex ] ;
102
- if ( ! xList . includes ( xValue ) )
103
- {
104
- xList . push ( xValue ) ;
105
- //add new array for aggregate data
106
- for ( let j = 0 ; j < headers . length ; j ++ ) {
107
- dataDict [ headers [ j ] ] . push ( [ ] ) ;
108
- }
109
- }
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 ] ) ;
110
94
}
95
+ }
111
96
112
- for ( let i = 0 ; i < splitRows . length ; i ++ )
113
- {
114
- const xValue = splitRows [ i ] [ xIndex ] ;
115
- const xValueIndex = xList . indexOf ( xValue ) ;
116
- for ( let j = 0 ; j < splitRows . length ; j ++ )
117
- {
118
- dataDict [ headers [ j ] ] [ xValueIndex ] . push ( splitRows [ i ] [ j ] ) ;
119
- }
97
+ //Remove items with empty arrays
98
+ for ( let i = 0 ; i < headers . length ; i ++ ) {
99
+ if ( dataDict [ headers [ i ] ] . length == 0 ) {
100
+ delete dataDict [ headers [ i ] ] ;
120
101
}
121
-
122
- const returnHeaders = Object . keys ( dataDict ) ;
123
- const yLists = headers . map ( ( header ) => dataDict [ header ] ) ;
124
- return { returnHeaders, xList, yLists, xIndex } ;
125
102
}
126
- else
127
- {
128
103
129
- // Iterate through the rows and put them under the correct header
130
- for ( let i = 1 ; i < splitRows . length ; i ++ ) {
131
- for ( let j = 0 ; j < splitRows [ i ] . length ; j ++ ) {
132
- dataDict [ headers [ j ] ] . push ( splitRows [ i ] [ j ] ) ;
104
+ const returnHeaders = Object . keys ( dataDict ) ;
105
+ const xIndex = headers . indexOf ( xAxis ) ;
106
+ const xList = headers . includes ( xAxis ) ? dataDict [ xAxis ] : dataDict [ headers [ 0 ] ] ;
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
+ }
133
148
}
149
+ uniqueY . push ( uniqueYList ) ;
134
150
}
151
+ return { returnHeaders, xList : uniqueX , yLists : uniqueY , xIndex } ;
152
+ } ;
135
153
136
- //Remove items with empty arrays
137
- for ( let i = 0 ; i < headers . length ; i ++ ) {
138
- if ( dataDict [ headers [ i ] ] . length == 0 ) {
139
- delete dataDict [ headers [ i ] ] ;
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 ) ;
140
167
}
168
+ uniqueY . push ( uniqueYList ) ;
141
169
}
142
-
143
- const returnHeaders = Object . keys ( dataDict ) ;
144
- const xIndex = headers . indexOf ( xAxis ) ;
145
- const xList = headers . includes ( xAxis ) ? dataDict [ xAxis ] : dataDict [ headers [ 0 ] ] ;
146
- const yLists = headers . map ( ( header ) => dataDict [ header ] ) ;
147
- return { returnHeaders, xList, yLists, xIndex } ;
170
+ return { returnHeaders, xList : uniqueX , yLists : uniqueY , xIndex } ;
148
171
}
172
+ return { returnHeaders, xList, yLists, xIndex } ;
149
173
} ;
150
174
151
175
@@ -183,7 +207,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
183
207
backgroundColor : colors
184
208
} ) ) ;
185
209
186
-
187
210
const newChartInstance = new Chart ( ctx , {
188
211
type : chartType ,
189
212
data : {
@@ -216,11 +239,28 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
216
239
}
217
240
}
218
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
+
219
259
setChartInstance ( newChartInstance ) ;
220
260
221
261
setHeaders ( headers ) ;
222
262
}
223
- } , [ loading , experimentChartData , chartType , xAxis , isFullscreen , aggregateData ] ) ;
263
+ } , [ loading , experimentChartData , chartType , xAxis , isFullscreen , aggregateData , aggregateMode ] ) ;
224
264
225
265
const regenerateCanvas = ( ) => {
226
266
setCanvasKey ( prevKey => prevKey + 1 ) ;
@@ -260,7 +300,7 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
260
300
< div className = 'p-4' >
261
301
< p className = "font-bold" > X-Axis Column:</ p >
262
302
< select
263
- className = "p-2 border rounded-md font-bold"
303
+ className = "p-2 border rounded-md font-bold w-auto "
264
304
onChange = { ( e ) => setXAxis ( e . target . value ) }
265
305
name = "xaxis"
266
306
defaultValue = { headers [ 0 ] }
@@ -274,7 +314,29 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
274
314
275
315
</ div >
276
316
< div className = 'p-4' >
277
- < label > < input id = 'aggregate-data-box' type = "checkbox" checked = { aggregateData } onChange = { ( ) => setAggregateData ( ! aggregateData ) } > </ input > Aggregate data?</ label >
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
+ }
278
340
</ div >
279
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' >
280
342
Download Image
0 commit comments