Compare commits

...

81 Commits

Author SHA1 Message Date
Hykilpikonna b5b9f14a49 [U] Pre-release v0.2.2.303 2019-09-30 21:16:53 -04:00
Hykilpikonna fc93cd1248 [+] Skip if not graded 2019-09-30 21:05:39 -04:00
Hykilpikonna 6e58c634a1 [F] Correct course average grade graph calculation 2019-09-30 21:05:29 -04:00
Hykilpikonna 10cca344c7 [M] Copy old course calculation method to deprecated 2019-09-30 21:05:07 -04:00
Hykilpikonna cdf58ea3a0 [O] Specify type for weighting map 2019-09-30 21:04:47 -04:00
Hykilpikonna d97c80afbb [U] Update course model to match the update 2019-09-30 19:25:59 -04:00
Hykilpikonna 8c7d028f5b [R] Update screenshot 2019-09-29 19:51:19 -04:00
Hykilpikonna 19ba8ec7e6 [U] Pre-release v0.2.1.295 2019-09-29 19:45:22 -04:00
Hykilpikonna 6937952f3a [O] Optimize alignment 2019-09-29 19:44:38 -04:00
Hykilpikonna 485717d3ef [+] Dynamic min value 2019-09-29 19:42:26 -04:00
Hykilpikonna 3f3e46bec5 [O] Make graph-overall smoother 2019-09-29 19:42:16 -04:00
Hykilpikonna 1494315e0c [O] Optimize wording 2019-09-29 19:14:39 -04:00
Hykilpikonna 76eb8168ce [O] Align the two graph titles 2019-09-29 19:14:27 -04:00
Hykilpikonna e4a1c4973d [O] Rotate text to 90 degrees 2019-09-29 19:14:12 -04:00
Hykilpikonna b22fc41582 [+] Add title to course gpa graph 2019-09-29 19:14:02 -04:00
Hykilpikonna 0e1264d938 [O] Make the full grade color gray 2019-09-29 18:29:52 -04:00
Hykilpikonna a94a6db4b7 [O] Make the bar color match the line graph color 2019-09-29 18:29:32 -04:00
Hykilpikonna 18b5b7de77 [O] Separate the generation of the gp dataset 2019-09-29 18:28:33 -04:00
Hykilpikonna 28d4e5d871 [F] Fix typo on assignment sign 2019-09-29 18:09:32 -04:00
Hykilpikonna cc5b7b2793 [O] Remove dataset, move data to series 2019-09-29 18:02:40 -04:00
Hykilpikonna 7b4ced4e94 [O] Display larger data on farthest layer 2019-09-29 17:46:00 -04:00
Hykilpikonna 70e6debe82 [+] Stack the bars in graph 2019-09-29 17:44:08 -04:00
Hykilpikonna e0c4426ebb [O] Optimize series generation with [].map 2019-09-29 17:43:01 -04:00
Hykilpikonna 495e776971 [O] Optimize code length 2019-09-29 17:39:21 -04:00
Hykilpikonna fc625c8edb [+] Truncate text if too long 2019-09-29 17:38:53 -04:00
Hykilpikonna 6c1623cec5 [+] Show axis label 2019-09-29 17:31:37 -04:00
Hykilpikonna a9a9cf5f11 [O] Make bar graph vertical 2019-09-29 17:31:29 -04:00
Hykilpikonna d8f276005d [O] Make the two graphs the same width 2019-09-29 17:20:07 -04:00
Hykilpikonna 5c9e67478f [+] Rebuild entire average graph from settings 2019-09-29 17:17:23 -04:00
Hykilpikonna 5e0394f11c [+] Create a theme constant 2019-09-29 16:28:44 -04:00
Hykilpikonna 774e436c9c [F] Fix typo in :data bind method 2019-09-29 16:14:17 -04:00
Hykilpikonna 8b653e3317 [+] GraphAverage: Return graph 2019-09-29 16:13:57 -04:00
Hykilpikonna 9731d4e380 [+] GraphAverage: Map GPA and MaxGPA for each course 2019-09-29 16:13:52 -04:00
Hykilpikonna e3ca21ec29 [+] GraphAverage: Make column list 2019-09-29 16:13:14 -04:00
Hykilpikonna a89c29dc53 [F] Fix class name typo 2019-09-29 16:04:48 -04:00
Hykilpikonna 87a59416ec [O] Optimize minDate calculation with array.map() and Math.min 2019-09-29 16:04:38 -04:00
Hykilpikonna 8243312179 [O] Optimize column generation with array.map() 2019-09-29 15:48:28 -04:00
Hykilpikonna 3dcc1b8319 [+] Add zome bar 2019-09-29 15:34:17 -04:00
Hykilpikonna 899ff22677 [O] Hide legend for now 2019-09-29 15:33:14 -04:00
Hykilpikonna 788f51399a [O] Center title 2019-09-29 15:33:05 -04:00
Hykilpikonna 575dbdf765 [-] Remove duplicate title in html 2019-09-29 15:32:58 -04:00
Hykilpikonna 1659964011 [O] Show title in graph settings 2019-09-29 15:32:46 -04:00
Hykilpikonna 02533f6b07 [+] Add graph-average to overall page 2019-09-29 15:23:51 -04:00
Hykilpikonna 68a91e5eb8 [+] Create average graphs 2019-09-29 15:18:46 -04:00
Hykilpikonna 1eb6128aac [O] Optimize type declaration 2019-09-29 15:17:14 -04:00
Hykilpikonna fbd59f9ec3 [-] Remove unnecessary var declaration in getGPA() 2019-09-29 15:12:47 -04:00
Hykilpikonna 7eb7838f66 [M] Move convertChart() to graph-overall.ts 2019-09-29 15:12:05 -04:00
Hykilpikonna 6459e1c09b [O] Make the legend icon a circle 2019-09-29 15:09:15 -04:00
Hykilpikonna 4461ef835f [U] Pass in filtered courses instead 2019-09-29 15:08:30 -04:00
Hykilpikonna 6ee207d754 [+] Filter courses after assignments are retrieved 2019-09-29 15:07:01 -04:00
Hykilpikonna 3c0c0d5f1b [-] Remove unnecessary jsdoc @param declaration 2019-09-29 15:02:24 -04:00
Hykilpikonna bc8fcc021d [M] Move course filtering to course-utils.ts 2019-09-29 15:01:47 -04:00
Hykilpikonna e9a6cfe5c5 [O] Make legend font size smaller 2019-09-29 14:47:47 -04:00
Hykilpikonna 0e92cedf54 [+] Add title but doesn't show 2019-09-29 14:27:25 -04:00
Hykilpikonna a73d6b0399 Revert "[O] Use vue-echarts instead of v-charts"
This reverts commit d254706a21.
2019-09-29 12:45:06 -04:00
Hykilpikonna 8042765ff5 Revert "[-] Remove v-charts dependency"
This reverts commit 8978b7ca7b.
2019-09-29 12:45:03 -04:00
Hykilpikonna 74a033fb5d Revert "[+] Import vue-echarts"
This reverts commit b78c5b4f5b.
2019-09-29 12:44:57 -04:00
Hykilpikonna cc4378d905 Revert "[+] Add vue-echarts transpile dependencies"
This reverts commit a526946b83.
2019-09-29 12:44:55 -04:00
Hykilpikonna 0fd3696342 Revert "[U] Update overall graph component"
This reverts commit c262584e2f.
2019-09-29 12:44:52 -04:00
Hykilpikonna 3de4b1f6e6 Revert "[+] Add a graph title"
This reverts commit 610d1d190d.
2019-09-29 12:44:50 -04:00
Hykilpikonna 610d1d190d [+] Add a graph title 2019-09-28 17:55:44 -04:00
Hykilpikonna c262584e2f [U] Update overall graph component 2019-09-28 17:55:35 -04:00
Hykilpikonna a526946b83 [+] Add vue-echarts transpile dependencies 2019-09-28 17:52:32 -04:00
Hykilpikonna b78c5b4f5b [+] Import vue-echarts 2019-09-28 17:50:42 -04:00
Hykilpikonna 8978b7ca7b [-] Remove v-charts dependency 2019-09-28 17:47:18 -04:00
Hykilpikonna d254706a21 [O] Use vue-echarts instead of v-charts 2019-09-28 17:47:02 -04:00
Hykilpikonna 8dd7d35abe [U] Pre-release v0.1.2.236 2019-09-28 17:38:33 -04:00
Hykilpikonna 2b989fb137 [-] Remove placeholder button 2019-09-28 17:35:55 -04:00
Hykilpikonna e2a13e90e0 [O] Optimize font size for max gpa 2019-09-28 17:35:46 -04:00
Hykilpikonna 3546a57711 [O] Use class to add style to card 2019-09-28 17:35:17 -04:00
Hykilpikonna 770e26b0cf [F] Fix warning: " should be ' 2019-09-28 16:33:00 -04:00
Hykilpikonna fff60f5754 [F] Fix the newline display for button 2019-09-27 20:12:41 -04:00
Hykilpikonna a46e011c90 [O] Use css to produce new-line effect instead of <br> 2019-09-27 16:47:22 -04:00
Hykilpikonna 48224d9e34 [O] Optimize jsdocs 2019-09-27 16:40:59 -04:00
Hykilpikonna eb311e9f2d [+] Display the maximum GPA 2019-09-27 16:38:52 -04:00
Hykilpikonna 7b53e65a1b [O] Return raw GPA in overall 2019-09-27 16:38:39 -04:00
Hykilpikonna 065d6d31a3 [O] Hit enter to login 2019-09-27 16:38:26 -04:00
Hykilpikonna d8eb160123 [F] Fix max gpa calculation 2019-09-27 16:33:50 -04:00
Hykilpikonna 8e924d8e33 [+] Also calculate max gpa when calculating gpa 2019-09-24 22:22:18 -04:00
Hykilpikonna 1e45b418b8 [O] Encapsulate getGP() with letter grade passed in 2019-09-24 22:20:58 -04:00
Hykilpikonna e42c8be76f [O] Detect letter grade for presence instead of numeric grade 2019-09-24 22:19:48 -04:00
17 changed files with 544 additions and 168 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ This is a website that generates visual representation of students' grade data o
**Here's how it looks like right now:** *(Now all of you know my grades ;-;)*
![](https://i.imgur.com/xl3Q4Nt.jpg)
![](https://user-images.githubusercontent.com/22280294/65841599-155ead00-e2f2-11e9-9d9f-c2f23c45d9a4.png)
<br>
+14 -2
View File
@@ -6,6 +6,7 @@ import Constants from '@/constants';
import JsonUtils from '@/utils/json-utils';
import pWaitFor from 'p-wait-for';
import {HttpUtils} from '@/utils/http-utils';
import {CourseUtils} from '@/utils/course-utils';
/**
* Objects of this interface represent assignment grades.
@@ -40,6 +41,12 @@ export interface Course
level: string,
scaleUp: number,
grading:
{
method: string,
weightingMap: {[index: string]: number}
}
assignments: Grade[]
}
@@ -54,6 +61,9 @@ export default class App extends Vue
// List of course that the student takes
public courses: Course[] = [];
// List of course that should be displayed
public filteredCourses: Course[] = [];
// Currently selected tab
public selectedTab: string = 'overall';
@@ -122,8 +132,6 @@ export default class App extends Vue
/**
* Load the assignments of the courses
*
* @param courses Courses Json
*/
public loadAssignments()
{
@@ -153,7 +161,11 @@ export default class App extends Vue
pWaitFor(() => this.isAssignmentsReady()).then(() =>
{
// When the assignments are ready
// TODO: Display loading
this.assignmentsReady = true;
// Filter courses
this.filteredCourses = CourseUtils.getGradedCourses(this.courses);
});
}
+4 -2
View File
@@ -1,13 +1,15 @@
<template>
<div id="app">
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
<navigation :courses="courses"
<navigation :courses="filteredCourses"
v-on:sign-out="signOut()"
v-on:navigation:select="onNavigate">
</navigation>
<div id="app-content">
<overall :courses="courses" v-if="selectedTab === 'overall' && assignmentsReady"></overall>
<overall :courses="filteredCourses"
v-if="selectedTab === 'overall' && assignmentsReady">
</overall>
</div>
</div>
</template>
+9
View File
@@ -4,6 +4,7 @@ import {HttpUtils} from '@/utils/http-utils';
/**
* This component handles user login, and obtains data from the server.
* TODO: Press enter to login
*/
@Component({
components: {},
@@ -70,4 +71,12 @@ export default class Login extends Vue
this.loading = false;
});
}
/**
* This is called when the user hits enter in the input boxes.
*/
public onEnter()
{
this.onLoginClick();
}
}
+4 -2
View File
@@ -8,13 +8,15 @@
<el-input v-model="username"
placeholder="School Username"
:class="{'input-error': error !== ''}">
:class="{'input-error': error !== ''}"
@keyup.enter.native="onEnter">
</el-input>
<el-input v-model="password"
placeholder="Veracross Password"
show-password=""
:class="{'input-error': error !== ''}">
:class="{'input-error': error !== ''}"
@keyup.enter.native="onEnter">
</el-input>
<div class="el-form-item__error custom">{{error}}</div>
+27 -1
View File
@@ -13,5 +13,31 @@ export default class Constants
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
' v0.1.1.0 `---\' '
' v0.2.2.303 `---\' ';
// Graph Theme
public static THEME =
{
// Colors
colors:
[
'#18cea5',
'#4fa8ed',
'#f9627b',
'#ffb075',
'#005c9c',
'#bcabe0',
'#d36e75',
'#fc97af',
'#919e8b',
'#d7ab82',
'#6e7074',
'#61a0a8',
'#efa18d',
'#787464',
'#cc7e63',
'#724e58',
'#4b565b'
]
}
}
@@ -0,0 +1,7 @@
#graph-average
{
.graph
{
margin-top: 50px;
}
}
@@ -0,0 +1,112 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import {Course} from '@/components/app/app';
import {GPAUtils} from '@/utils/gpa-utils';
import Constants from '@/constants';
@Component({
})
export default class GraphAverage extends Vue
{
// @ts-ignore
@Prop({required: true}) courses: Course[];
/**
* Generate settings
*/
get chartSettings()
{
let settings =
{
// Title
title:
{
show: true,
textStyle:
{
fontSize: 12
},
text: 'Course GPA',
subtext: 'Current GPA for every course',
x: 'center'
},
// X axis represents course names
xAxis:
{
type: 'category',
axisLabel: {
interval: 0,
inside: false,
rotate: 90,
// Truncate text length
formatter: (value: string) => value.length <= 16 ? value : value.substr(0, 14) + '...'
},
},
// Y axis represents GPAs and MaxGPAs
yAxis:
{
type: 'value'
},
// Data
series:
[
{
type: 'bar',
barGap: '-100%',
data: this.courses.map(course =>
{
return {value: [course.name, GPAUtils.getGP(course, 'A+')],
itemStyle: {color: '#d8d8d8'}}
}),
},
{
type: 'bar',
barGap: '-100%',
data: this.generateGPData(),
label:
{
show: true,
rotate: 90
}
}
],
// Disable tooltip
tooltip:
{
show: false
}
};
// TODO: Remove this
console.log(settings);
return settings;
}
/**
* Generate GP data for each course
*/
private generateGPData()
{
let data: any = [];
this.courses.forEach(course =>
{
data.push(
{
value: [course.name, GPAUtils.getGP(course, course.letterGrade)],
itemStyle:
{
color: Constants.THEME.colors[data.length]
}
});
});
return data;
}
}
@@ -0,0 +1,9 @@
<template>
<div id="graph-average">
<ve-bar height="450px" class="graph"
:extend="chartSettings"></ve-bar>
</div>
</template>
<script src="./graph-average.ts" lang="ts"></script>
<style src="./graph-average.scss" lang="scss"></style>
@@ -5,17 +5,166 @@ import {Course} from '@/components/app/app';
})
export default class GraphOverall extends Vue
{
@Prop({required: true}) chart: any;
// @ts-ignore
@Prop({required: true}) courses: Course[];
private settings =
{
// Title
title:
{
show: true,
textStyle:
{
fontSize: 12
},
text: 'Average Grade',
subtext: 'Average score trend for every course',
x: 'center'
},
// Legend
legend:
{
show: false,
//left: 'auto',
//align: 'left',
//orient: 'vertical'
textStyle:
{
fontSize: 11
},
icon: 'circle'
},
// Zoom bar
dataZoom:
[
// TODO: Calculate real value for startValue
{
startValue: '9/13/2019'
},
{
type: 'inside'
}
],
series:
{
smooth: false
smooth: true
},
xAxis:
{
//type: 'time'
},
yAxis:
{
min: 70
min: (value: any) => value.min - 10
}
};
/**
* Convert assignments list to a graph dataset.
*/
get convertChart()
{
let courses = this.courses;
// Compute the column names
let columns = courses.map(course => course.name);
columns.unshift('date');
// Find the min date
let minDates = courses.map(course => new Date(course.assignments[course.assignments.length - 1].date).getTime());
let minDate: Date = new Date(Math.min.apply(null, minDates));
// Find the dates in between
let now = new Date();
let dates = [];
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
{
dates.push(new Date(date));
}
// Compute the rows data
let rows: {[index: string]: any}[] = [];
dates.forEach(date =>
{
// Define row object
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
// Loop through courses
courses.forEach(course =>
{
// Total Mean
if (course.grading.method == 'TOTAL_MEAN')
{
let score = 0;
let max = 0;
// Loop through assignments
course.assignments.forEach(assignment =>
{
// If assignment should be displayed
if (assignment.complete != 'Complete') return;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() < date.getTime())
{
// Record scores
score += assignment.score;
max += assignment.scoreMax;
}
});
// Add average to the row
row[course.name] = score / max * 100;
}
else if (course.grading.method == 'PERCENT_TYPE')
{
let typeScores: {[index: string]: any} = {};
let typeCounts: {[index: string]: any} = {};
// Loop through assignments
course.assignments.forEach(assignment =>
{
// If assignment should be displayed
if (assignment.complete != 'Complete') return;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() < date.getTime())
{
// Record scores
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
typeScores[assignment.type] += assignment.score / assignment.scoreMax;
if (typeCounts[assignment.type] == undefined) typeCounts[assignment.type] = 0;
typeCounts[assignment.type] ++;
}
});
let score = 0;
// Count
for (let type in typeScores)
{
score += typeScores[type] * course.grading.weightingMap[type] / typeCounts[type];
console.log(type);
}
// Add average to the row
row[course.name] = score * 100;
}
});
// Add it to the array
rows.push(row);
});
console.log(rows);
return {
columns: columns,
rows: rows
}
}
}
@@ -1,7 +1,6 @@
<template>
<div id="graph-overall">
<p>Your average score graph all time:</p>
<ve-line :data="chart" :extend="settings"></ve-line>
<ve-line :data="convertChart" :extend="settings"></ve-line>
</div>
</template>
+37 -4
View File
@@ -12,18 +12,51 @@
flex-direction: column;
}
.span-gpa-header
.gpa-card
{
margin-left: 20px;
min-width: 136px;
}
.gpa
{
display: block;
}
.gpa.header
{
font-size: 14px;
}
.span-gpa
.gpa.text
{
font-size: 35px;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
.gpa-time
.gpa.max
{
font-size: 14px;
margin-top: -10px;
margin-bottom: 10px;
font-size: 12px;
color: #409eff;
}
.gpa.time
{
font-size: 11px;
}
// Graph average
.graph-average-card
{
margin-right: 20px;
min-width: 170px;
}
// Fix padding
.el-card__body
{
padding-top: 0 !important;
padding-bottom: 0 !important;
}
+5 -133
View File
@@ -1,151 +1,23 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
import GraphAverage from '@/pages/overall/graph-average/graph-average';
import {Course} from '@/components/app/app';
import {GPAUtils} from '@/utils/gpa-utils';
@Component({
components: {GraphOverall}
components: {GraphOverall, GraphAverage}
})
export default class Overall extends Vue
{
// @ts-ignore
@Prop({required: true}) courses: Course[];
get convertCharts()
{
// Null case
if (this.courses == null) return [];
// Filter it
let courses: Course[] = this.filterCourses();
// Compute the column names
let columns = ['date'];
courses.forEach(course =>
{
columns.push(course.name);
});
// Find the min date
let minDate: Date = new Date();
courses.forEach(course =>
{
if (course.assignments.length == 0) return;
let date = new Date(course.assignments[course.assignments.length - 1].date);
if (date < minDate) minDate = date;
});
// Find the dates in between
let now = new Date();
let dates = [];
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
{
dates.push(new Date(date));
}
// Initialize course specific variables
let courseScores: {[index: string]: any} = {};
let courseMaxScores: {[index: string]: any} = {};
let courseIndexes: {[index: string]: any} = {};
courses.forEach(course =>
{
courseScores[course.name] = 0;
courseMaxScores[course.name] = 0;
courseIndexes[course.name] = course.assignments.length - 1;
});
// Compute the rows data
let rows: {[index: string]: any}[] = [];
dates.forEach(date =>
{
// Define row object
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
// Loop through courses
courses.forEach(course =>
{
// Reversed loop through the assignments
for (let r = courseIndexes[course.name]; r >= 0; r--)
{
let assignment = course.assignments[r];
// If assignment should be displayed
if (assignment.complete != 'Complete') continue;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() == date.getTime())
{
// Record scores
courseScores[course.name] += assignment.score;
courseMaxScores[course.name] += assignment.scoreMax;
}
// Not now
else if (assignmentDate > date)
{
courseIndexes[course.name] = r;
break;
}
}
// Add average to the row
row[course.name] = courseScores[course.name] / courseMaxScores[course.name] * 100;
});
// Add it to the array
rows.push(row);
});
console.log(rows);
return {
columns: columns,
rows: rows
}
}
/**
* Return a list of courses that are graphed
*/
private filterCourses(): Course[]
{
// Define result
let result: Course[] = [];
// Filter through courses
this.courses.forEach(course =>
{
// Skip future or past courses
if (course.status != 'active') return;
// Skip courses without levels
if (course.level == 'None') return;
// Skip courses without assignments
if (course.assignments.length == 0) return;
// Add it to the list
result.push(course);
});
return result;
}
/**
* This function is called to get gpa as a string.
* This function is called to get gpa since I can't import another
* class in the Vue file.
*/
public getGPA()
{
let gpa = GPAUtils.getGPA(this.courses);
let result = '' + gpa.gpa;
/* Not accurate
if (!gpa.accurate)
{
result = `(${result})`;
}*/
return result;
return GPAUtils.getGPA(this.courses);
}
}
+14 -11
View File
@@ -2,22 +2,25 @@
<div id="overall">
<el-row>
<el-col :span="4">
<el-card style="margin-left: 20px">
<el-card class="gpa-card">
<div style="padding: 14px;">
<span class="span-gpa-header">GPA:</span>
<br>
<span class="span-gpa">{{getGPA()}}</span>
<div class="bottom clearfix gpa-time">
<time class="time">{{ new Date().toDateString() }}</time>
<br>
<el-button type="text" class="button">Button</el-button>
<span class="gpa header">GPA:</span>
<span class="gpa text">{{getGPA().gpa}}</span>
<span class="gpa max">(Out of {{getGPA().max}})</span>
<div class="bottom clearfix gpa time">
<time>{{ new Date().toDateString() }}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="20">
<el-card style="margin-right: 20px">
<graph-overall :chart="convertCharts"></graph-overall>
<el-col :span="14">
<el-card>
<graph-overall :courses="courses"></graph-overall>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="graph-average-card">
<graph-average :courses="courses"></graph-average>
</el-card>
</el-col>
</el-row>
+37
View File
@@ -0,0 +1,37 @@
import {Course} from '@/components/app/app';
export class CourseUtils
{
/**
* Return a list of courses that are graphed
*
* @param original Original course list
* @return Course[] Filtered course list
*/
public static getGradedCourses(original: Course[]): Course[]
{
// Define result
let result: Course[] = [];
// Filter through courses
original.forEach(course =>
{
// Skip future or past courses
if (course.status != 'active') return;
// Skip courses without levels
if (course.level == 'None') return;
// Skip courses without assignments
if (course.assignments.length == 0) return;
// Skip if there are no grading scale
if (course.grading.method == 'NOT_GRADED') return;
// Add it to the list
result.push(course);
});
return result;
}
}
+100
View File
@@ -0,0 +1,100 @@
// Initialize course specific variables
let courseScores: {[index: string]: any} = {};
let courseMaxScores: {[index: string]: any} = {};
let courseIndexes: {[index: string]: any} = {};
courses.forEach(course =>
{
courseScores[course.name] = 0;
courseMaxScores[course.name] = 0;
courseIndexes[course.name] = course.assignments.length - 1;
});
// Compute the rows data
let rows: {[index: string]: any}[] = [];
dates.forEach(date =>
{
// Define row object
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
// Loop through courses
courses.forEach(course =>
{
// Reversed loop through the assignments
for (let r = courseIndexes[course.name]; r >= 0; r--)
{
let assignment = course.assignments[r];
// If assignment should be displayed
if (assignment.complete != 'Complete') continue;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() == date.getTime())
{
// Detect grading method and record scores
if (course.grading.method == 'TOTAL_MEAN')
{
courseScores[course.name] += assignment.score;
courseMaxScores[course.name] += assignment.scoreMax;
}
else if (course.grading.method == 'PERCENT_TYPE')
{
let scale = course.grading.weightingMap[assignment.type];
courseScores[course.name] += assignment.score * scale;
courseMaxScores[course.name] += assignment.scoreMax * scale;
}
}
// Not now
else if (assignmentDate > date)
{
courseIndexes[course.name] = r;
break;
}
}
// Add average to the row
row[course.name] = courseScores[course.name] / courseMaxScores[course.name] * 100;
});
// Add it to the array
rows.push(row);
});
else if (course.grading.method == 'PERCENT_TYPE')
{
let typeScores: {[index: string]: any} = {};
let typeCounts: {[index: string]: any} = {};
// Loop through assignments
course.assignments.forEach(assignment =>
{
// If assignment should be displayed
if (assignment.complete != 'Complete') return;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() < date.getTime())
{
// Record scores
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
typeScores[assignment.type] += assignment.score / assignment.scoreMax;
if (typeCounts[assignment.type] == undefined) typeCounts[assignment.type] = 0;
typeCounts[assignment.type] ++;
}
});
let score = 0;
// Count
for (let type in typeScores)
{
score += typeScores[type] * course.grading.weightingMap[type] / typeCounts[type];
console.log(type);
}
// Add average to the row
row[course.name] = score * 100;
}
+11 -7
View File
@@ -31,7 +31,7 @@ export class GPAUtils
*
* @param coursesOriginal List of courses
*/
public static getGPA(coursesOriginal: Course[]): {gpa: number, accurate: boolean}
public static getGPA(coursesOriginal: Course[]): {gpa: number, accurate: boolean, max: number}
{
// Clone array
let courses: Course[] = [];
@@ -42,7 +42,7 @@ export class GPAUtils
// Remove all courses that does not have a grade
coursesOriginal.forEach(course =>
{
if (course.numericGrade == null || course.numericGrade == 0)
if (course.letterGrade == null || course.letterGrade == '')
{
accurate = false;
}
@@ -55,35 +55,39 @@ export class GPAUtils
// If no course have grade, return -1
if (courses.length == 0)
{
return {gpa: -1, accurate: false};
return {gpa: -1, accurate: false, max: -1};
}
// Count total GPA
let totalGPA = 0;
let maxTotal = 0;
courses.forEach(course =>
{
totalGPA += this.getGP(course);
totalGPA += this.getGP(course, course.letterGrade);
maxTotal += this.getGP(course, 'A+');
});
// Get average GPA, round to two decimal places
let gpa = Math.round(totalGPA / courses.length * 100) / 100;
let maxGPA = Math.round(maxTotal / courses.length * 100) / 100;
// Return results
return {gpa: gpa, accurate: accurate};
return {gpa: gpa, accurate: accurate, max: maxGPA};
}
/**
* Calculate GPA for a course
*
* @param course Course
* @param letterGrade Letter grade
*/
public static getGP(course: Course): number
public static getGP(course: Course, letterGrade?: string): number
{
// Find the GPA for this course.
for (let scale of this.SCALE)
{
// Letter grades are the same
if (scale[this.LETTER] == course.letterGrade)
if (scale[this.LETTER] == letterGrade)
{
// Get grade and add it
let grade = <number> scale[this.GPA];