Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5b9f14a49 | |||
| fc93cd1248 | |||
| 6e58c634a1 | |||
| 10cca344c7 | |||
| cdf58ea3a0 | |||
| d97c80afbb | |||
| 8c7d028f5b | |||
| 19ba8ec7e6 | |||
| 6937952f3a | |||
| 485717d3ef | |||
| 3f3e46bec5 | |||
| 1494315e0c | |||
| 76eb8168ce | |||
| e4a1c4973d | |||
| b22fc41582 | |||
| 0e1264d938 | |||
| a94a6db4b7 | |||
| 18b5b7de77 | |||
| 28d4e5d871 | |||
| cc5b7b2793 | |||
| 7b4ced4e94 | |||
| 70e6debe82 | |||
| e0c4426ebb | |||
| 495e776971 | |||
| fc625c8edb | |||
| 6c1623cec5 | |||
| a9a9cf5f11 | |||
| d8f276005d | |||
| 5c9e67478f | |||
| 5e0394f11c | |||
| 774e436c9c | |||
| 8b653e3317 | |||
| 9731d4e380 | |||
| e3ca21ec29 | |||
| a89c29dc53 | |||
| 87a59416ec | |||
| 8243312179 | |||
| 3dcc1b8319 | |||
| 899ff22677 | |||
| 788f51399a | |||
| 575dbdf765 | |||
| 1659964011 | |||
| 02533f6b07 | |||
| 68a91e5eb8 | |||
| 1eb6128aac | |||
| fbd59f9ec3 | |||
| 7eb7838f66 | |||
| 6459e1c09b | |||
| 4461ef835f | |||
| 6ee207d754 | |||
| 3c0c0d5f1b | |||
| bc8fcc021d | |||
| e9a6cfe5c5 | |||
| 0e92cedf54 | |||
| a73d6b0399 | |||
| 8042765ff5 | |||
| 74a033fb5d | |||
| cc4378d905 | |||
| 0fd3696342 | |||
| 3de4b1f6e6 | |||
| 610d1d190d | |||
| c262584e2f | |||
| a526946b83 | |||
| b78c5b4f5b | |||
| 8978b7ca7b | |||
| d254706a21 | |||
| 8dd7d35abe | |||
| 2b989fb137 | |||
| e2a13e90e0 | |||
| 3546a57711 | |||
| 770e26b0cf | |||
| fff60f5754 | |||
| a46e011c90 | |||
| 48224d9e34 | |||
| eb311e9f2d | |||
| 7b53e65a1b | |||
| 065d6d31a3 | |||
| d8eb160123 | |||
| 8e924d8e33 | |||
| 1e45b418b8 | |||
| e42c8be76f |
@@ -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 ;-;)*
|
||||
|
||||

|
||||

|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user