Skip to content

Commit 14e8a46

Browse files
Merge pull request #404 from AutomatingSciencePipeline/376-format-data-for-boxplot-and-violin-03
376 format data for boxplot and violin
2 parents 7340ba7 + 677e573 commit 14e8a46

File tree

1 file changed

+128
-13
lines changed
  • apps/frontend/app/components/flows/ViewExperiment

1 file changed

+128
-13
lines changed

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

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ 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);
27+
const [aggregateData, setAggregateData] = useState(false);
2728

2829
const toggleFullscreen = () => {
2930
setIsFullscreen(!isFullscreen);
@@ -35,6 +36,8 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
3536
const setBoxPlot = () => setChartType('boxplot');
3637
const setViolin = () => setChartType('violin');
3738

39+
const aggregateModes = ['sum', 'count', 'average', 'median', 'mode']
40+
3841
useEffect(() => {
3942
fetch(`/api/download/csv/${project.expId}`).then((response) => response.json()).then((record) => {
4043
setExperimentChartData(record);
@@ -64,13 +67,8 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
6467
const headers = rows[0].split(',') as string[];
6568
//Create a dictionary to store the data
6669
const dataDict = {} as any;
70+
const splitRows = [] as any;
6771

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
7472
for (let i = 1; i < rows.length; i++) {
7573
// Split the row by commas when not inside quotes
7674
const row = rows[i].split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
@@ -79,9 +77,21 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
7977
// If the value is a number, convert it to a number
8078
if (!isNaN(val)) {
8179
row[j] = parseFloat(val);
82-
dataDict[headers[j]].push(row[j]);
8380
}
8481
}
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+
}
8595
}
8696

8797
//Remove items with empty arrays
@@ -95,6 +105,70 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
95105
const xIndex = headers.indexOf(xAxis);
96106
const xList = headers.includes(xAxis) ? dataDict[xAxis] : dataDict[headers[0]];
97107
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+
}
98172
return { returnHeaders, xList, yLists, xIndex };
99173
};
100174

@@ -133,7 +207,6 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
133207
backgroundColor: colors
134208
}));
135209

136-
137210
const newChartInstance = new Chart(ctx, {
138211
type: chartType,
139212
data: {
@@ -166,11 +239,28 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
166239
}
167240
}
168241
});
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+
169259
setChartInstance(newChartInstance);
170260

171261
setHeaders(headers);
172262
}
173-
}, [loading, experimentChartData, chartType, xAxis, isFullscreen]);
263+
}, [loading, experimentChartData, chartType, xAxis, isFullscreen, aggregateData, aggregateMode]);
174264

175265
const regenerateCanvas = () => {
176266
setCanvasKey(prevKey => prevKey + 1);
@@ -189,12 +279,12 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
189279
<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'>
190280
Pie Chart
191281
</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'>
193283
Box Plot
194284
</button>
195285
<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'>
196286
Violin Plot
197-
</button> */}
287+
</button>
198288
</div>
199289
<div
200290
className={isFullscreen ? 'p-4 h-[65vh]' : 'p-4 h-[50vh]'}>
@@ -210,7 +300,7 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
210300
<div className='p-4'>
211301
<p className="font-bold">X-Axis Column:</p>
212302
<select
213-
className="p-2 border rounded-md font-bold"
303+
className="p-2 border rounded-md font-bold w-auto"
214304
onChange={(e) => setXAxis(e.target.value)}
215305
name="xaxis"
216306
defaultValue={headers[0]}
@@ -223,6 +313,31 @@ const ChartModal: React.FC<ChartModalProps> = ({ onClose, project }) => {
223313
</select>
224314

225315
</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>
226341
<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'>
227342
Download Image
228343
</button>

0 commit comments

Comments
 (0)