Skip to content

Commit 677e573

Browse files
committed
complete data aggregation
1 parent fb8731c commit 677e573

File tree

1 file changed

+118
-56
lines changed
  • apps/frontend/app/components/flows/ViewExperiment

1 file changed

+118
-56
lines changed

apps/frontend/app/components/flows/ViewExperiment/Chart.tsx

Lines changed: 118 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
2020
const [experimentChartData, setExperimentChartData] = useState({ _id: '', experimentId: '', resultContent: '' });
2121
const [loading, setLoading] = useState(true);
2222
const [xAxis, setXAxis] = useState('X');
23-
//const [yAxis, setYAxis] = useState('Y');
23+
const [aggregateMode, setAggregateMode] = useState('sum');
2424
const [headers, setHeaders] = useState<string[]>([]);
2525
const [img, setImg] = useState<string>('');
2626
const [isFullscreen, setIsFullscreen] = useState(false);
@@ -36,7 +36,9 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
3636
const setBoxPlot = () => setChartType('boxplot');
3737
const setViolin = () => setChartType('violin');
3838

39-
/*useEffect(() => {
39+
const aggregateModes = ['sum', 'count', 'average', 'median', 'mode']
40+
41+
useEffect(() => {
4042
fetch(`/api/download/csv/${project.expId}`).then((response) => response.json()).then((record) => {
4143
setExperimentChartData(record);
4244
setLoading(false);
@@ -51,11 +53,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
5153
});
5254
}
5355
);
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);
5956
}, [project.expId]);
6057

6158
const downloadImage = () => {
@@ -90,62 +87,89 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
9087
dataDict[headers[i]] = [];
9188
}
9289

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]);
11094
}
95+
}
11196

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]];
120101
}
121-
122-
const returnHeaders = Object.keys(dataDict);
123-
const yLists = headers.map((header) => dataDict[header]);
124-
return { returnHeaders, xList, yLists, xIndex };
125102
}
126-
else
127-
{
128103

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+
}
133148
}
149+
uniqueY.push(uniqueYList);
134150
}
151+
return { returnHeaders, xList: uniqueX, yLists: uniqueY, xIndex };
152+
};
135153

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);
140167
}
168+
uniqueY.push(uniqueYList);
141169
}
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 };
148171
}
172+
return { returnHeaders, xList, yLists, xIndex };
149173
};
150174

151175

@@ -183,7 +207,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
183207
backgroundColor: colors
184208
}));
185209

186-
187210
const newChartInstance = new Chart(ctx, {
188211
type: chartType,
189212
data: {
@@ -216,11 +239,28 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
216239
}
217240
}
218241
});
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+
219259
setChartInstance(newChartInstance);
220260

221261
setHeaders(headers);
222262
}
223-
}, [loading, experimentChartData, chartType, xAxis, isFullscreen, aggregateData]);
263+
}, [loading, experimentChartData, chartType, xAxis, isFullscreen, aggregateData, aggregateMode]);
224264

225265
const regenerateCanvas = () => {
226266
setCanvasKey(prevKey => prevKey + 1);
@@ -260,7 +300,7 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
260300
<div className='p-4'>
261301
<p className="font-bold">X-Axis Column:</p>
262302
<select
263-
className="p-2 border rounded-md font-bold"
303+
className="p-2 border rounded-md font-bold w-auto"
264304
onChange={(e) => setXAxis(e.target.value)}
265305
name="xaxis"
266306
defaultValue={headers[0]}
@@ -274,7 +314,29 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
274314

275315
</div>
276316
<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+
}
278340
</div>
279341
<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'>
280342
Download Image

0 commit comments

Comments
 (0)