From 894bd1c551c07040d6f9bd66b0f492dd9b254e9d Mon Sep 17 00:00:00 2001 From: "Reid Holmes (MBA)" Date: Wed, 12 Sep 2018 20:28:13 -0700 Subject: [PATCH] see #92 add grade distributions [skip ci] --- .../portal/frontend/html/cs310/admin.html | 1 + .../frontend/src/app/views/AdminGradesTab.ts | 202 +++++++++++++++++- 2 files changed, 201 insertions(+), 2 deletions(-) diff --git a/packages/portal/frontend/html/cs310/admin.html b/packages/portal/frontend/html/cs310/admin.html index cd7275a13..7497f3b32 100644 --- a/packages/portal/frontend/html/cs310/admin.html +++ b/packages/portal/frontend/html/cs310/admin.html @@ -100,6 +100,7 @@ Grade Overview
+
No Results
diff --git a/packages/portal/frontend/src/app/views/AdminGradesTab.ts b/packages/portal/frontend/src/app/views/AdminGradesTab.ts index 215e672b4..4c6181212 100644 --- a/packages/portal/frontend/src/app/views/AdminGradesTab.ts +++ b/packages/portal/frontend/src/app/views/AdminGradesTab.ts @@ -21,6 +21,7 @@ export class AdminGradesTab { // NOTE: this could consider if studentListTable has children, and if they do, don't refresh document.getElementById('gradesListTable').innerHTML = ''; // clear target + document.getElementById('gradesSummaryTable').innerHTML = ''; // clear target UI.showModal('Retrieving grades.'); const delivs = await AdminDeliverablesTab.getDeliverables(this.remote); @@ -132,11 +133,208 @@ export class AdminGradesTab { if (st.numRows() > 0) { UI.showSection('gradesListTable'); + UI.showSection('gradesSummaryTable'); UI.hideSection('gradesListTableNone'); } else { - UI.showSection('gradesListTable'); - UI.hideSection('gradesListTableNone'); + UI.hideSection('gradesListTable'); + UI.hideSection('gradesSummaryTable'); + UI.showSection('gradesListTableNone'); + } + + this.renderSummary(grades, delivs, students); + } + + private renderSummary(grades: GradeTransport[], delivs: DeliverableTransport[], students: StudentTransport[]): void { + Log.trace("AdminGradesTab::renderSummary(..) - start"); + + const headers: TableHeader[] = [ + { + id: 'delivId', + text: 'Deliv Id', + sortable: true, + defaultSort: true, + sortDown: false, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: 'avg', + text: 'Average', + sortable: true, // Whether the column is sortable (sometimes sorting does not make sense). + defaultSort: false, // Whether the column is the default sort for the table. should only be true for one column. + sortDown: false, // Whether the column should initially sort descending or ascending. + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: 'median', + text: 'Median', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '009', + text: '0-9', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '1019', + text: '10-19', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '2029', + text: '20-29', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '3039', + text: '30-39', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '4049', + text: '40-49', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '5059', + text: '50-59', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '6069', + text: '60-69', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '7079', + text: '70-79', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '8089', + text: '80-89', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '9099', + text: '90-99', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + }, + { + id: '100', + text: '100', + sortable: true, + defaultSort: false, + sortDown: true, + style: 'padding-left: 1em; padding-right: 1em;' + } + + ]; + + // for (const deliv of delivs) { + // const col = { + // id: deliv.id, + // text: deliv.id, + // sortable: true, + // defaultSort: false, + // sortDown: true, + // style: 'padding-left: 1em; padding-right: 1em;' + // }; + // headers.push(col); + // } + + const st = new SortableTable(headers, '#gradesSummaryTable'); + + // this loop couldn't possibly be less efficient + const gradeMap: {[delivId: string]: number[]} = {}; + + for (const grade of grades) { + if (grade !== null && typeof grade.score !== 'undefined' && typeof grade.score === 'number') { + if (typeof gradeMap[grade.delivId] === 'undefined') { + gradeMap[grade.delivId] = []; + } + gradeMap[grade.delivId].push(grade.score); + } + } + + const inBin = function(list: number[], lower: number, upper: number): number { + const total = list.reduce(function(accumulator, currentValue) { + if (currentValue >= lower && currentValue <= upper) { + accumulator++; + } + return accumulator; + }, 0); + return total; + }; + + for (const delivId of Object.keys(gradeMap)) { + + const delivGrades: number[] = gradeMap[delivId]; + + const num = delivGrades.length; + if (num > 0) { + const total = delivGrades.reduce(function(accumulator, currentValue) { + return accumulator + currentValue; + }); + const avg = Number((total / num).toFixed(2)); + + delivGrades.sort((a, b) => a - b); + const lowMiddle = Math.floor((num - 1) / 2); + const highMiddle = Math.ceil((num - 1) / 2); + const median = (delivGrades[lowMiddle] + delivGrades[highMiddle]) / 2; + + const row: TableCell[] = [ + {value: delivId, html: delivId}, + {value: avg + '', html: avg + ''}, + {value: median + '', html: median + ''} + ]; + + for (let i = 0; i < 10; i++) { + const lower = i * 10; + const upper = lower + 9; + const numInBin = inBin(delivGrades, lower, upper); + Log.info('inBin( [..], ' + lower + ', ' + upper + '; #: ' + numInBin); + row.push({value: numInBin + '', html: numInBin + ''}); + } + const numPerfect = inBin(delivGrades, 100, 100); + row.push({value: numPerfect + '', html: numPerfect + ''}); + st.addRow(row); + } } + + st.generate(); } public static async getGrades(remote: string): Promise {