Compare commits

...

176 Commits

Author SHA1 Message Date
Hykilpikonna c97a202bd5 [U] Release v0.4.6.1087 2019-11-11 22:42:42 -05:00
Hykilpikonna b11332953c [F] Fix dark theme logo displaying issue 2019-11-11 22:41:26 -05:00
Hykilpikonna d5f8649b80 [S] Adjust logo font weight 2019-11-11 22:32:01 -05:00
Hykilpikonna fab173afa3 [S] Adjust logo font size 2019-11-11 22:31:48 -05:00
Hykilpikonna 41fbf70428 [S] Create a gradient for logo 2019-11-11 22:30:41 -05:00
Hykilpikonna 1df427b4b4 [S] Add right margin to icon 2019-11-11 22:15:22 -05:00
Hykilpikonna 96a0d3bef6 [S] Remove old logo style 2019-11-11 22:15:13 -05:00
Hykilpikonna 2d917d7858 [S] Limit logo height 2019-11-11 22:15:03 -05:00
Hykilpikonna 2e851e5541 [S] Add logo in nav bar 2019-11-11 22:14:44 -05:00
Hykilpikonna b5ff93bf58 [+] Import Favicon into index.html 2019-11-11 22:09:02 -05:00
Hykilpikonna a10350756f [+] Generate 32px favicon 2019-11-11 22:08:43 -05:00
Hykilpikonna 133765131c [O] Limit logo image width 2019-11-11 21:57:18 -05:00
Hykilpikonna 723a72c1ab [+] Copy logo to assets 2019-11-11 21:53:48 -05:00
Hykilpikonna 6567aeda7d [+] Export logo as png 2019-11-11 21:53:00 -05:00
Hykilpikonna 2182ff8e74 [M] Move vue logo to icon making dir 2019-11-11 21:52:41 -05:00
Hykilpikonna 25865fb372 [+] Remake logo again 2019-11-11 21:52:21 -05:00
Hykilpikonna 81ffeb8d85 [+] Remake logo 2019-11-11 21:51:40 -05:00
Hykilpikonna 19203f3629 [+] Support email login 2019-11-10 17:14:43 -05:00
Hykilpikonna 82ad8f026b [O] Optimize field types 2019-11-10 17:14:33 -05:00
Hykilpikonna 38629a9e34 [O] Specify username format 2019-11-10 17:10:38 -05:00
Hykilpikonna 4499232544 [S] Remove card paddings 2019-11-10 16:39:33 -05:00
Hykilpikonna 960f30295c [S] Specify background color is transparent 2019-11-10 16:39:21 -05:00
Hykilpikonna 415373559c [S] Darken colors 2019-11-10 16:38:17 -05:00
Hykilpikonna a7d22e1620 [S] Add drop shadow to tooltip 2019-11-10 14:27:59 -05:00
Hykilpikonna f88ef7284f [-] Disable dark mode by default 2019-11-10 14:22:18 -05:00
Hykilpikonna 380023668f [S] Apply dark background to course page too 2019-11-10 14:21:41 -05:00
Hykilpikonna fb90900045 [S] Make graph white 2019-11-10 14:15:08 -05:00
Hykilpikonna ab511a706a [S] Compact css entries 2019-11-10 14:14:14 -05:00
Hykilpikonna 407a2889d1 [S] Use css variables 2019-11-10 14:13:38 -05:00
Hykilpikonna 840775314a [S] Fix prev-course and next-course shadow 2019-11-10 14:06:40 -05:00
Hykilpikonna 85c1d93608 [S] Apply darker background to #app-content too 2019-11-10 14:05:23 -05:00
Hykilpikonna 8825886c5c [S] Emphesize course name 2019-11-10 14:04:25 -05:00
Hykilpikonna e924c00f0d [S] Lighten expansion color 2019-11-10 14:00:32 -05:00
Hykilpikonna 03826d108d [S] Add layer effect to course card expansion 2019-11-10 13:59:38 -05:00
Hykilpikonna 1e04b4ad70 [S] Apply light text to buttons too 2019-11-10 13:59:13 -05:00
Hykilpikonna 3ac37d980c [S] Emphesize percent entry box 2019-11-10 13:54:21 -05:00
Hykilpikonna da8cec72b4 [S] Emphesize max entry box 2019-11-10 13:54:10 -05:00
Hykilpikonna 903345b86a [S] Comment overall css 2019-11-10 13:51:24 -05:00
Hykilpikonna 9ed297431d [S] Apply dark background to entry box 2019-11-10 13:50:56 -05:00
Hykilpikonna e763a1ac88 [S] Adjust backtop box shadow 2019-11-10 13:47:50 -05:00
Hykilpikonna 5b3bdc2f1f [S] Apply light text to span too 2019-11-10 13:46:10 -05:00
Hykilpikonna 65b4a68f7e [S] Remove el-card border 2019-11-10 13:44:08 -05:00
Hykilpikonna 7ff885e28b [S] Make overall background darker 2019-11-10 13:44:01 -05:00
Hykilpikonna b936f8a0ca [S] Default use light background for foreground 2019-11-10 13:40:37 -05:00
Hykilpikonna 93a01aeb5d [S] Fix padding under main frame 2019-11-10 13:35:48 -05:00
Hykilpikonna 0326c0bf14 [S] Use gray color with dark background 2019-11-10 13:34:51 -05:00
Hykilpikonna cb91a20844 [S] Apply dark background to all div 2019-11-10 13:34:39 -05:00
Hykilpikonna 9fdb7461ba [S] Create dark css 2019-11-10 13:28:47 -05:00
Hykilpikonna 15b51020fc [O] Make cards clickable 2019-11-10 13:22:26 -05:00
Hykilpikonna 2938621b73 [O] Scroll to top on page change 2019-11-10 13:22:15 -05:00
Hykilpikonna 624158f3d4 [O] Show prev-course and next-course above others 2019-11-10 13:22:02 -05:00
Hykilpikonna 602a8b9c46 [+] Add back-to-top button 2019-11-10 13:21:35 -05:00
Hykilpikonna 62338358d1 [S] Add radial color gradient to the scatter plot points 2019-11-10 13:12:22 -05:00
Hykilpikonna 9bc69a81af [+] Show label on the top 2019-11-10 13:11:51 -05:00
Hykilpikonna c93b1e4eec [+] Resize scatter plot circles according to their weight 2019-11-10 13:09:18 -05:00
Hykilpikonna d30728e2e9 [+] Add assignmentCount to AssignmentType interface 2019-11-10 13:08:22 -05:00
Hykilpikonna 7d86b7a2b9 [+] Import chroma-js instead 2019-11-10 13:02:18 -05:00
Hykilpikonna bb31e18ad5 [+] Add chroma-js dependency 2019-11-10 12:37:52 -05:00
Hykilpikonna 73dc5f6a51 [O] Optimize course-scatter with course.assignmentTypes 2019-11-10 12:05:53 -05:00
Hykilpikonna 6343a10fbc [O] Dynamic radar min value 2019-11-09 23:47:30 -05:00
Hykilpikonna 14beb802b0 [+] Display weight percentage for pie too 2019-11-09 23:29:11 -05:00
Hykilpikonna be71c4ea0b [O] Use the same text style for pie graph 2019-11-09 23:28:46 -05:00
Hykilpikonna 4bd34d46be [O] Separate textStyle to GraphUtils 2019-11-09 23:27:44 -05:00
Hykilpikonna 670ac48516 [O] Use more descriptive title for radar too 2019-11-09 23:23:52 -05:00
Hykilpikonna 4ce7c625ea [O] Use more descriptive title for pie graph 2019-11-09 23:20:22 -05:00
Hykilpikonna 551930f47a [O] Make the center empty 2019-11-09 23:08:05 -05:00
Hykilpikonna 04ca753466 [S] Adjust opacity of the pie areas 2019-11-09 22:58:06 -05:00
Hykilpikonna 4169d5235a [S] Add drop shadow 2019-11-09 22:57:03 -05:00
Hykilpikonna c417698bf2 [S] Adjust y position of pie graph 2019-11-09 22:53:22 -05:00
Hykilpikonna b6bf6373f2 [S] Reduce radius 2019-11-09 22:53:10 -05:00
Hykilpikonna f4326cf9e1 [O] Better label alignment 2019-11-09 22:53:01 -05:00
Hykilpikonna cca9d5a240 [O] Adjust to correct color after sorting 2019-11-09 22:52:52 -05:00
Hykilpikonna 0af3cee18b [O] Sort pie chart data 2019-11-09 22:51:06 -05:00
Hykilpikonna 8d122f0100 [+] Show type name 2019-11-09 22:45:55 -05:00
Hykilpikonna 62373485f5 [O] Use percentage as weight 2019-11-09 22:45:40 -05:00
Hykilpikonna 472d83e9fb [S] Adjust height 2019-11-09 22:44:22 -05:00
Hykilpikonna 34231dc480 [+] Generate pie data 2019-11-09 22:38:36 -05:00
Hykilpikonna 3f6c9c1204 [O] Round weight to 2 digits 2019-11-09 22:38:22 -05:00
Hykilpikonna d0e627bd83 [O] Change title 2019-11-09 22:33:56 -05:00
Hykilpikonna 6e4074b050 [+] Add a type-pie to course-page 2019-11-09 22:30:45 -05:00
Hykilpikonna 963df01b6f [+] Create pie graph component 2019-11-09 22:29:19 -05:00
Hykilpikonna c807d4aecb [-] Remove unused css section 2019-11-09 22:28:40 -05:00
Hykilpikonna e2c9d05a7e [M] Rename type-radar directory too 2019-11-09 22:26:13 -05:00
Hykilpikonna 6204efd453 [U] Release v0.4.3.1006 2019-11-09 22:15:31 -05:00
Hykilpikonna db083732b0 [M] Rename course-type-radar to type-radar 2019-11-09 22:14:41 -05:00
Hykilpikonna 4974049c0b [S] Make background lighter 2019-11-09 22:11:02 -05:00
Hykilpikonna 857192ee6d [S] Add text shadows 2019-11-09 22:10:45 -05:00
Hykilpikonna 90d07b1faa [S] Make font size larger 2019-11-09 22:10:33 -05:00
Hykilpikonna 4abe02da94 [+] Color the indicator texts 2019-11-09 22:10:16 -05:00
Hykilpikonna adca4b41e2 [+] Add percent score after indicator 2019-11-09 22:10:08 -05:00
Hykilpikonna 4d9d8e0be5 [S] Darken line color 2019-11-09 21:58:39 -05:00
Hykilpikonna 546ad81f7c [+] Colorize split area 2019-11-09 21:58:23 -05:00
Hykilpikonna 1eff27ad26 [S] Center color gradient 2019-11-09 21:49:40 -05:00
Hykilpikonna f540e03a56 [S] Adjust the graph down 10% 2019-11-09 21:47:34 -05:00
Hykilpikonna af925741b4 [S] Make graph smaller 2019-11-09 21:47:18 -05:00
Hykilpikonna 08bb24cac4 [O] Use percentage score instead of score 2019-11-09 21:47:06 -05:00
Hykilpikonna 8a9ca83e68 [O] Optimize graph with baseSettings 2019-11-09 21:46:47 -05:00
Hykilpikonna e6b6a73f1f [O] Optimize type adverage with pre-calculated percent 2019-11-09 21:35:18 -05:00
Hykilpikonna ceca351b07 [+] Include percent score in AssignmentType 2019-11-09 21:34:28 -05:00
Hykilpikonna 4e14730db6 [S] Change the graph symbol to circle 2019-11-09 21:24:13 -05:00
Hykilpikonna 3b0e291df4 [S] Make radar only take half the screen 2019-11-09 21:23:54 -05:00
Hykilpikonna 6bcb2577f7 [+] Add area gradient 2019-11-09 21:23:34 -05:00
Hykilpikonna 54e54b89e9 [+] Display tooltip 2019-11-09 21:23:14 -05:00
Hykilpikonna 0085d384fd [+] Display data in radar graph 2019-11-09 21:23:07 -05:00
Hykilpikonna 3b4e40261f [O] Use scoreMax instead 2019-11-09 21:22:31 -05:00
Hykilpikonna eb8715867e [+] Add radar indicator 2019-11-09 21:03:12 -05:00
Hykilpikonna 1bcfdf5648 [+] Add radar component in course page 2019-11-09 20:55:56 -05:00
Hykilpikonna 429de6553b [+] Create course-type-radar 2019-11-09 20:55:39 -05:00
Hykilpikonna fce47648c8 [O] Remove unnecessary getAssignmentTypes() 2019-11-09 20:45:30 -05:00
Hykilpikonna f6d44dd1f2 [O] Remove unnecessary complete verification 2019-11-09 20:45:12 -05:00
Hykilpikonna 4fe0a54277 [O] Remove unnecessary average() method 2019-11-09 20:44:57 -05:00
Hykilpikonna e8bf21e60c [F] Fix type name display 2019-11-09 20:44:37 -05:00
Hykilpikonna 472df39ac8 [O] Pass in AssignmentType object for AssignmentTypeHead component 2019-11-09 20:44:23 -05:00
Hykilpikonna ae94f54a7b [O] Cache assignment types 2019-11-09 20:39:24 -05:00
Hykilpikonna 8c9f0a0e83 [O] Optimize caching 2019-11-09 20:37:56 -05:00
Hykilpikonna cb22baf120 [+] Calculate type score and weight 2019-11-09 20:33:32 -05:00
Hykilpikonna 0525aae98a [+] Create method to get assignment types 2019-11-09 20:25:18 -05:00
Hykilpikonna 4c87cb2947 [F] Fix null pointer 2019-11-09 20:21:57 -05:00
Hykilpikonna 076a1ee52e [O] Return graded assignments by default 2019-11-09 20:21:09 -05:00
Hykilpikonna 7bd9e97396 [S] Remove ugly circle between data points 2019-11-09 20:05:52 -05:00
Hykilpikonna aa014bcdab [+] Create AssignmentType interface 2019-11-09 19:47:56 -05:00
Hykilpikonna ee308d29ff [S] Optimize x axis label 2019-11-09 19:39:53 -05:00
Hykilpikonna 35ef2144b7 [O] Limit minimum zoom 2019-11-09 19:29:45 -05:00
Hykilpikonna 6dc2a21e8f [+] Add back tooltip 2019-11-09 17:42:03 -05:00
Hykilpikonna 343c921eb2 [+] Add y axis label 2019-11-09 17:40:15 -05:00
Hykilpikonna f54dfba058 [F] Fix scatter plot out of bounds issue 2019-11-09 17:39:58 -05:00
Hykilpikonna a7d7ef44a6 [-] Remove debug output 2019-11-09 17:35:29 -05:00
Hykilpikonna 793d7444b2 [O] Add back the term lines and mark areas 2019-11-09 17:35:22 -05:00
Hykilpikonna eaf4d2ce7b [O] Make y max an integer 2019-11-09 17:32:17 -05:00
Hykilpikonna 79e615ee46 [O] Use graded courses only 2019-11-09 17:31:32 -05:00
Hykilpikonna 40353cfd35 [O] Generate settings on created 2019-11-09 17:31:22 -05:00
Hykilpikonna 2be2ce98e6 [F] Fix date conversion 2019-11-09 17:27:45 -05:00
Hykilpikonna 6659f65763 [F] Fix multiple assignment creating multiple dates issue 2019-11-09 17:19:30 -05:00
Hykilpikonna 5b3ba4db07 [+] Create method to convert data points to date points 2019-11-09 17:11:35 -05:00
Hykilpikonna a05281f4e4 [+] Separate method to generate series 2019-11-09 17:10:54 -05:00
Hykilpikonna 2f95548fb3 [+] Create method to get series for course 2019-11-09 17:10:37 -05:00
Hykilpikonna 96ee9e9265 [O] Default to 2 decimal places 2019-11-09 17:05:06 -05:00
Hykilpikonna 9026b9d3a9 [-] Remove deprecated convertChart() 2019-11-09 17:02:53 -05:00
Hykilpikonna 67e38dd554 [F] Fix tooltip date representation 2019-11-09 16:59:52 -05:00
Hykilpikonna 7ffef67e42 [F] Parse time instead of date 2019-11-09 16:57:12 -05:00
Hykilpikonna 0d195cfb7f [F] Fix invalid date 2019-11-09 16:56:22 -05:00
Hykilpikonna 0d3e9c0840 [O] Remove toChartDate, use timestamp instead 2019-11-09 16:53:53 -05:00
Hykilpikonna 6c600f31f8 [O] Change assignment.date to time 2019-11-09 16:50:48 -05:00
Hykilpikonna a05a44aaef [+] Add assignments.length > 0 as a condition 2019-11-09 16:49:30 -05:00
Hykilpikonna 31d3a5a09e [+] Filter graded courses 2019-11-09 16:48:45 -05:00
Hykilpikonna 30293cd261 [O] Encapsulate method to get graded assignments 2019-11-09 16:31:36 -05:00
Hykilpikonna 9a279b3417 [O] Use afterconfig 2019-11-09 16:24:00 -05:00
Hykilpikonna 4a7ff0ea55 [O] Use base settings in overall-line 2019-11-09 16:23:41 -05:00
Hykilpikonna fd341e9d33 [O] Use basesettings in course-scatter 2019-11-09 16:15:21 -05:00
Hykilpikonna bdacc8dd9e [+] Create function to generate base settings 2019-11-09 16:14:58 -05:00
Hykilpikonna ad2c8a1ee8 [M] Move dot to graph utils 2019-11-09 16:12:44 -05:00
Hykilpikonna 1f58818a1a [S] Dim markline brightness 2019-11-09 16:06:28 -05:00
Hykilpikonna 09442cbfba [+] Add mark areas and lines in scatter plot 2019-11-09 16:05:55 -05:00
Hykilpikonna 91e10d1fa8 [+] Add opacity param to markline generation 2019-11-09 16:05:25 -05:00
Hykilpikonna 80267feb54 [O] Separate series creation with the main settings 2019-11-09 16:00:57 -05:00
Hykilpikonna f4ee2dadb6 [M] Move markarea to GraphUtils 2019-11-09 15:51:05 -05:00
Hykilpikonna d698f3d13a [M] Move markline stuff to GraphUtils 2019-11-09 15:47:30 -05:00
Hykilpikonna 82cb845061 [S] Make line animation faster 2019-11-09 15:43:53 -05:00
Hykilpikonna 940738307b [F] Fix term 1 graph displaying dates after term 2 2019-11-09 15:41:54 -05:00
Hykilpikonna df011787e1 [S] Change line color 2019-11-09 15:41:12 -05:00
Hykilpikonna 0af393a1e4 [+] Put in actual term dates 2019-11-09 15:37:52 -05:00
Hykilpikonna 6504cc033b [+] Encapsulate method to get term end date 2019-11-09 15:32:25 -05:00
Hykilpikonna c6ccc5e311 [F] Fix: All year graph begins at term 2 2019-11-09 15:28:48 -05:00
Hykilpikonna 3a8899507f [+] Encapsulate method to get begin date of a term 2019-11-09 15:28:21 -05:00
Hykilpikonna 2b7026c4ce [F] Fix: Make starting time not exceed the beginning of a term 2019-11-09 15:24:41 -05:00
Hykilpikonna a3fd822252 [S] Remove quater line arrow symbol 2019-11-09 15:05:57 -05:00
Hykilpikonna f9d7fa398f [+] Add quater line label 2019-11-09 14:59:38 -05:00
Hykilpikonna 7ffc445bba [+] Show quater lines 2019-11-09 14:55:47 -05:00
Hykilpikonna 8e9f6a4bb7 [O] Optimize date data type 2019-11-09 14:55:37 -05:00
Hykilpikonna 05cb560c8c [-] Disable legend 2019-11-09 14:54:35 -05:00
Hykilpikonna 53a57234a0 [-] Remove unused legend 2019-11-09 14:54:02 -05:00
Hykilpikonna 6af6bb0959 [O] Optimize time display 2019-11-09 14:47:32 -05:00
Hykilpikonna 089aad7398 [O] Optimize date formatting 2019-11-09 14:47:22 -05:00
Hykilpikonna 937e89ce5f [M] Move assignment interface from app.ts to course.ts 2019-11-09 14:38:57 -05:00
Hykilpikonna b0685ffd6b [M] Move util classes to /logic 2019-11-09 14:32:45 -05:00
Hykilpikonna 18dee50b96 [F] Force desktop view on mobile devices 2019-11-09 14:32:10 -05:00
42 changed files with 876 additions and 430 deletions
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+60 -2
View File
@@ -947,6 +947,11 @@
} }
} }
}, },
"@types/chroma-js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-1.4.3.tgz",
"integrity": "sha512-m33zg9cRLtuaUSzlbMrr7iLIKNzrD4+M6Unt5+9mCu4BhR5NwnRjVKblINCwzcBXooukIgld8DtEncP8qpvbNg=="
},
"@types/events": { "@types/events": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/@types/events/download/@types/events-3.0.0.tgz", "resolved": "https://registry.npm.taobao.org/@types/events/download/@types/events-3.0.0.tgz",
@@ -2510,6 +2515,14 @@
"integrity": "sha1-oY8eCyacimpdPIbrKYvrFMPde/Y=", "integrity": "sha1-oY8eCyacimpdPIbrKYvrFMPde/Y=",
"dev": true "dev": true
}, },
"chroma-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.0.tgz",
"integrity": "sha512-uiRdh4ZZy+UTPSrAdp8hqEdVb1EllLtTHOt5TMaOjJUvi+O54/83Fc5K2ld1P+TJX+dw5B+8/sCgzI6eaur/lg==",
"requires": {
"cross-env": "^6.0.3"
}
},
"chrome-trace-event": { "chrome-trace-event": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/chrome-trace-event/download/chrome-trace-event-1.0.2.tgz", "resolved": "https://registry.npm.taobao.org/chrome-trace-event/download/chrome-trace-event-1.0.2.tgz",
@@ -3113,6 +3126,52 @@
"sha.js": "^2.4.8" "sha.js": "^2.4.8"
} }
}, },
"cross-env": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
"integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
"requires": {
"cross-spawn": "^7.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
"integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==",
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"path-key": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz",
"integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg=="
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"which": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz",
"integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"cross-spawn": { "cross-spawn": {
"version": "6.0.5", "version": "6.0.5",
"resolved": "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-6.0.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcross-spawn%2Fdownload%2Fcross-spawn-6.0.5.tgz", "resolved": "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-6.0.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcross-spawn%2Fdownload%2Fcross-spawn-6.0.5.tgz",
@@ -6266,8 +6325,7 @@
"isexe": { "isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz", "resolved": "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
"dev": true
}, },
"isobject": { "isobject": {
"version": "3.0.1", "version": "3.0.1",
+2
View File
@@ -8,6 +8,8 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@types/chroma-js": "^1.4.3",
"chroma-js": "^2.1.0",
"core-js": "^2.6.5", "core-js": "^2.6.5",
"echarts": "^4.2.1", "echarts": "^4.2.1",
"element-ui": "^2.11.1", "element-ui": "^2.11.1",
+3 -2
View File
@@ -3,9 +3,10 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <!--meta name="viewport" content="width=device-width,initial-scale=1.0"-->
<meta name="viewport" content="width=1024">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>logo@32px.png">
<title>Veracross Analyzer</title> <title>Veracross Analyzer</title>
</head> </head>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 60 KiB

+54 -1
View File
@@ -5,7 +5,7 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
text-align: center; text-align: center;
color: #2c3e50; color: #2c3e50;
margin-bottom: 100px; padding-bottom: 100px;
} }
#app-content #app-content
@@ -28,6 +28,59 @@
--font: 'Avenir', Helvetica, Arial, sans-serif; --font: 'Avenir', Helvetica, Arial, sans-serif;
} }
.dark
{
--dark-layer-1: #383838;
--dark-layer-2: #525252;
--dark-layer-3: #6c6c6c;
--dark-foreground: #e9e9e9;
background: var(--dark-layer-1) !important;
div, ul
{
background: var(--dark-layer-2) !important;
color: var(--dark-foreground) !important;
}
span, button
{
color: var(--dark-foreground) !important;
}
.el-card
{
border: none !important;
}
// Overall
#overall, #overall-course, .overall-span, #app-content
{
background: var(--dark-layer-1) !important;
}
// Course card
.entry-box, .none .unread-number {background: #a1a1a1 !important}
.entry-box.max {background-color: #949494 !important}
.entry-box.percent {background-color: #a7a490 !important}
.course-name {color: #cffff6 !important}
.course-card-content.expand, .assignment-entry, .unread-row,
.unread-row .el-col, #assignment-type-head, .course-page-graph.el-col
{
background-color: var(--dark-layer-3) !important;
}
.overall-span.el-col, .course-page-graph.el-col
{
div, span
{
background: #f9f9f9 !important;
color: var(--dark-layer-1) !important;
}
}
}
// ############## // ##############
// # Global CSS # // # Global CSS #
// ############## // ##############
+2 -25
View File
@@ -4,36 +4,13 @@ import Navigation from '@/components/navigation/navigation';
import Overall from '@/pages/overall/overall.vue'; import Overall from '@/pages/overall/overall.vue';
import Constants from '@/constants'; import Constants from '@/constants';
import pWaitFor from 'p-wait-for'; import pWaitFor from 'p-wait-for';
import {HttpUtils} from '@/utils/http-utils'; import {HttpUtils} from '@/logic/utils/http-utils';
import {GPAUtils} from '@/utils/gpa-utils'; import {GPAUtils} from '@/logic/utils/gpa-utils';
import Loading from '@/components/loading/loading.vue'; import Loading from '@/components/loading/loading.vue';
import CoursePage from '@/pages/course/course-page.vue'; import CoursePage from '@/pages/course/course-page.vue';
import Course from '@/logic/course'; import Course from '@/logic/course';
/**
* Objects of this interface represent assignment grades.
*/
export interface Assignment
{
id: number
scoreId: number
type: string
typeId: number
description: string
date: Date
complete: string
include: boolean
display: boolean
unread: boolean
scoreMax: number
score: number
gradingPeriod: number
}
@Component({ @Component({
components: {Login, Navigation, Overall, Loading, CoursePage}, components: {Login, Navigation, Overall, Loading, CoursePage},
}) })
+7
View File
@@ -5,6 +5,13 @@
} }
// Logo image
#login-logo-image
{
width: 80%;
margin-bottom: -15px;
}
// Parent overlay // Parent overlay
.login-overlay .login-overlay
{ {
+7 -4
View File
@@ -1,7 +1,7 @@
import {Component, Vue} from 'vue-property-decorator'; import {Component, Vue} from 'vue-property-decorator';
import Constants from '@/constants'; import Constants from '@/constants';
import App from '@/components/app/app'; import App from '@/components/app/app';
import VersionUtils from '@/utils/version-utils'; import VersionUtils from '@/logic/utils/version-utils';
/** /**
* This component handles user login, and obtains data from the server. * This component handles user login, and obtains data from the server.
@@ -11,11 +11,11 @@ import VersionUtils from '@/utils/version-utils';
}) })
export default class Login extends Vue export default class Login extends Vue
{ {
public username: any = ''; public username: string = '';
public password: any = ''; public password: string = '';
public loading: boolean = false; public loading: boolean = false;
public error: String = ''; public error: string = '';
/** /**
* This is called when the instance is created. * This is called when the instance is created.
@@ -68,6 +68,9 @@ export default class Login extends Vue
// Make login button loading // Make login button loading
this.loading = true; this.loading = true;
// Format it
this.username = this.username.toLowerCase().replace(/ /g, '').replace(/@.*/g, '');
// Fetch request // Fetch request
App.http.post('/login', {username: this.username, password: this.password}) App.http.post('/login', {username: this.username, password: this.password})
.then(response => .then(response =>
+3 -3
View File
@@ -2,18 +2,18 @@
<div id="login" class="login-overlay"> <div id="login" class="login-overlay">
<div class="login-vertical-center"> <div class="login-vertical-center">
<div class="login-panel"> <div class="login-panel">
<img alt="Vue logo" src="../../assets/logo.png"> <img id="login-logo-image" alt="logo" src="../../assets/logo.png">
<h1>Veracross Analyzer</h1> <h1>Veracross Analyzer</h1>
<form id="login-form"> <form id="login-form">
<el-input v-model="username" <el-input v-model="username"
placeholder="School Username" placeholder="SJP Username (Eg. flast21)"
:class="{'input-error': error !== ''}" :class="{'input-error': error !== ''}"
@keyup.enter.native="onEnter"> @keyup.enter.native="onEnter">
</el-input> </el-input>
<el-input v-model="password" <el-input v-model="password"
placeholder="Veracross Password" placeholder="SJP Password"
show-password="" show-password=""
:class="{'input-error': error !== ''}" :class="{'input-error': error !== ''}"
@keyup.enter.native="onEnter"> @keyup.enter.native="onEnter">
+33 -12
View File
@@ -48,24 +48,41 @@
height: 60px; height: 60px;
// Center text // Center text
display: inline-flex;
align-items: center; align-items: center;
// Margins // Margins
margin-left: 20px; margin-left: 20px;
margin-right: 8px; margin-right: 8px;
// Font
font: bold 16px arial;
display: inline-flex;
// Shadow effect
-webkit-background-clip: text;
background-color: #b1b1b1;
color: transparent;
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.85);
// Make it non-clickable // Make it non-clickable
pointer-events: none; pointer-events: none;
#nav-logo
{
height: 70%;
margin-right: 10px;
}
#nav-logo-text
{
// Color
color: #6bbeff !important;
background: linear-gradient(90deg,
rgba(90,177,239,1) 0%,
rgba(25,212,174,1) 100%) !important;
// Font
font-weight: 500;
font-size: 18px;
}
#nav-logo-text.logo-text
{
// Override the background
-webkit-text-fill-color: transparent !important;
-webkit-background-clip: text !important;
}
} }
#next-course #next-course
@@ -76,7 +93,7 @@
bottom: 0; bottom: 0;
left: 25%; left: 25%;
padding-top: 2px; padding-top: 2px;
box-shadow: 0 -2px 9px 0 #ecebeb; box-shadow: 0 -2px 9px 0 #00000029;
} }
footer footer
@@ -85,6 +102,8 @@ footer
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
z-index: 1000;
} }
#prev-course #prev-course
@@ -95,7 +114,9 @@ footer
top: 61px; top: 61px;
left: 25%; left: 25%;
padding-bottom: 2px; padding-bottom: 2px;
box-shadow: 0 2px 9px 0 #ecebeb; box-shadow: 0 2px 9px 0 #00000029;
z-index: 1001;
} }
.nav-course-operations .nav-course-operations
+5 -2
View File
@@ -1,7 +1,7 @@
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import App from '@/components/app/app'; import App from '@/components/app/app';
import {CourseUtils} from '@/utils/course-utils'; import {CourseUtils} from '@/logic/utils/course-utils';
import {FormatUtils} from '@/utils/format-utils'; import {FormatUtils} from '@/logic/utils/format-utils';
import pWaitFor from 'p-wait-for'; import pWaitFor from 'p-wait-for';
import Course from '@/logic/course'; import Course from '@/logic/course';
@@ -106,6 +106,9 @@ export default class Navigation extends Vue
// Update title // Update title
document.title = 'Veracross Analyzer - ' + this.getTitle(newIndex); document.title = 'Veracross Analyzer - ' + this.getTitle(newIndex);
// Scroll to top
window.scrollTo(0, 0);
} }
/** /**
+5 -1
View File
@@ -4,7 +4,8 @@
:default-active="activeIndex" @select="onSelect"> :default-active="activeIndex" @select="onSelect">
<div id="nav-title"> <div id="nav-title">
Veracross Analyzer <img id="nav-logo" alt="logo" src="../../assets/logo.png">
<span id="nav-logo-text" class="logo-text">Veracross Analyzer</span>
</div> </div>
<el-menu-item index="overall">Overall</el-menu-item> <el-menu-item index="overall">Overall</el-menu-item>
@@ -44,6 +45,9 @@
NEXT COURSE NEXT COURSE
</div> </div>
</footer> </footer>
<!-- Back to top -->
<el-backtop style="box-shadow: rgba(0, 0, 0, 0.23) 0 3px 11px 0;"></el-backtop>
</div> </div>
</template> </template>
+9 -2
View File
@@ -7,7 +7,7 @@ export default class Constants
public static API_URL: string = 'https://va.hydev.org/api'; public static API_URL: string = 'https://va.hydev.org/api';
/** Current version */ /** Current version */
public static VERSION: string = '0.4.2.912'; public static VERSION: string = '0.4.6.1087';
/** Minimum version that still supports the same cookies */ /** Minimum version that still supports the same cookies */
public static MIN_SUPPORTED_VERSION: string = '0.3.4.561'; public static MIN_SUPPORTED_VERSION: string = '0.3.4.561';
@@ -56,6 +56,13 @@ export default class Constants
}; };
// Terms (TODO: Actually get the terms dynamically // Terms (TODO: Actually get the terms dynamically
public static TERMS = [new Date('Sep 04 2019'), new Date('Nov 01 2019'), new Date('Feb 02 2020')]; public static TERMS =
[
new Date('Sep 04 2019'),
new Date('Nov 03 2019'),
new Date('Jan 19 2020'),
new Date('Mar 22 2020'),
new Date('Jun 05 2020'),
];
public static CURRENT_TERM = 1; public static CURRENT_TERM = 1;
} }
+121 -34
View File
@@ -1,9 +1,43 @@
import {Assignment} from '@/components/app/app'; import JsonUtils from '@/logic/utils/json-utils';
import JsonUtils from '@/utils/json-utils'; import {FormatUtils} from '@/logic/utils/format-utils';
import {FormatUtils} from '@/utils/format-utils'; import {CourseUtils} from '@/logic/utils/course-utils';
import {CourseUtils} from '@/utils/course-utils';
import Navigation from '@/components/navigation/navigation'; import Navigation from '@/components/navigation/navigation';
import {GPAUtils} from '@/utils/gpa-utils'; import {GPAUtils} from '@/logic/utils/gpa-utils';
/**
* Objects of this interface represent assignment grades.
*/
export interface Assignment
{
id: number
scoreId: number
type: string
typeId: number
description: string
time: number
complete: string
include: boolean
display: boolean
unread: boolean
scoreMax: number
score: number
gradingPeriod: number
}
export interface AssignmentType
{
id: number
name: string
weight: number
scoreMax: number
score: number
percent: number
assignmentCount: number
}
export default class Course export default class Course
{ {
@@ -79,7 +113,7 @@ export default class Course
this.rawAssignments = JsonUtils.filterAssignments(data); this.rawAssignments = JsonUtils.filterAssignments(data);
// Sort by date (Latest is at 0) // Sort by date (Latest is at 0)
this.rawAssignments.sort((a, b) => b.date.getTime() - a.date.getTime()); this.rawAssignments.sort((a, b) => b.time - a.time);
// Filter assignments into terms // Filter assignments into terms
let termAssignments: Assignment[][] = [[], [], [], []]; let termAssignments: Assignment[][] = [[], [], [], []];
@@ -104,7 +138,7 @@ export default class Course
if (this.level == 'None' || this.level == 'Unknown' || this.scaleUp == -1) return false; if (this.level == 'None' || this.level == 'Unknown' || this.scaleUp == -1) return false;
// Skip courses without graded assignments // Skip courses without graded assignments
if (this.assignments.filter(a => a.complete == 'Complete').length == 0) return false; if (this.assignments.length == 0) return false;
// Skip if there are no grading scale // Skip if there are no grading scale
// if (course.grading.method == 'NOT_GRADED') return; // if (course.grading.method == 'NOT_GRADED') return;
@@ -116,7 +150,7 @@ export default class Course
/** /**
* Get assignments of the selected time * Get assignments of the selected time
*/ */
get assignments(): Assignment[] get rawSelectedAssignments(): Assignment[]
{ {
let timeCode = Navigation.instance.getSelectedGradingPeriod(); let timeCode = Navigation.instance.getSelectedGradingPeriod();
@@ -130,30 +164,42 @@ export default class Course
return this.computed.termAssignments[timeCode]; return this.computed.termAssignments[timeCode];
} }
// TODO: Optimize this private _cacheAssignments: Assignment[];
private letterGradeComputed = false;
/**
* Get graded assignments
*/
get assignments(): Assignment[]
{
if (this._cacheAssignments == null)
this._cacheAssignments = this.rawSelectedAssignments.filter(a => a.complete == 'Complete');
return this._cacheAssignments;
}
private _cacheLetterGrade: string;
/** /**
* Get letter grade * Get letter grade
*/ */
get letterGrade(): string get letterGrade(): string
{ {
// Cached if (this._cacheLetterGrade == null)
if (this.rawLetterGrade != undefined && this.letterGradeComputed) {
return this.rawLetterGrade; // Get scale
this.letterGradeComputed = true; let scale = GPAUtils.findScale(this.numericGrade);
// Get scale // Scale not found
let scale = GPAUtils.findScale(this.numericGrade); if (scale == undefined) return this._cacheLetterGrade = '--';
// Scale not found // Cache
if (scale == undefined) return this.rawLetterGrade = '--'; this._cacheLetterGrade = scale.letter;
}
// Return return this._cacheLetterGrade;
return this.rawLetterGrade = scale.letter;
} }
private numericGradeComputed = false; private _cacheNumericGrade: number;
/** /**
* Get numeric grade * Get numeric grade
@@ -161,21 +207,62 @@ export default class Course
get numericGrade(): number get numericGrade(): number
{ {
// Cached // Cached
if (this.rawNumericGrade != undefined && this.numericGradeComputed) if (this._cacheNumericGrade == null)
return this.rawNumericGrade;
this.numericGradeComputed = true;
// Calculate
if (this.grading.method == 'PERCENT_TYPE')
{ {
return this.rawNumericGrade = GPAUtils.getPercentTypeAverage(this, this.assignments); // Calculate
} if (this.grading.method == 'PERCENT_TYPE')
if (this.grading.method == 'TOTAL_MEAN') {
{ this._cacheNumericGrade = GPAUtils.getPercentTypeAverage(this, this.assignments);
return this.rawNumericGrade = GPAUtils.getTotalMeanAverage(this.assignments); }
else if (this.grading.method == 'TOTAL_MEAN')
{
this._cacheNumericGrade = GPAUtils.getTotalMeanAverage(this.assignments);
}
else this._cacheNumericGrade = -1;
} }
// Error return this._cacheNumericGrade;
return -1; }
private _cacheAssignmentTypes: AssignmentType[];
/**
* Get assignment types
*/
get assignmentTypes(): AssignmentType[]
{
if (this._cacheAssignmentTypes == null)
{
// Get all types
let types = this.assignments.map(a => a.type);
// Remove duplicates
types = types.filter((type, i, a) => a.indexOf(type) == i);
// Get total possible score for weight calculation
let totalScoreMax = this.assignments.reduce((sum, a) => sum + a.scoreMax, 0);
// For every type...
this._cacheAssignmentTypes = types.map(type =>
{
// Get assignments of the type
let typeAssignments = this.assignments.filter(a => a.type == type);
// Count scores and max scores
let score = typeAssignments.reduce((sum, a) => sum + a.score, 0);
let scoreMax = typeAssignments.reduce((sum, a) => sum + a.scoreMax, 0);
// Calculate weight
let weight = this.grading.method == 'PERCENT_TYPE'
? this.grading.weightingMap[type] : scoreMax / totalScoreMax;
// Return
return {name: type, id: typeAssignments[0].typeId, weight: +(weight * 100).toFixed(2),
scoreMax: scoreMax, score: score, percent: +(score / scoreMax * 100).toFixed(2),
assignmentCount: typeAssignments.length}
})
}
return this._cacheAssignmentTypes;
} }
} }
@@ -1,4 +1,6 @@
import Course from '@/logic/course'; import Course from '@/logic/course';
import Navigation from '@/components/navigation/navigation';
import Constants from '@/constants';
const LEVEL_AP = {level: 'AP', scaleUp: 1}; const LEVEL_AP = {level: 'AP', scaleUp: 1};
const LEVEL_H = {level: 'H', scaleUp: 0.75}; const LEVEL_H = {level: 'H', scaleUp: 0.75};
@@ -64,4 +66,24 @@ export class CourseUtils
// Really unknown // Really unknown
return undefined; return undefined;
} }
/**
* Get the begin date of the selected term
*/
static getTermBeginDate()
{
let selected = Navigation.instance.getSelectedGradingPeriod();
return selected == -1 ? Constants.TERMS[0] : Constants.TERMS[selected];
}
/**
* Get the end date of the selected term
*/
static getTermEndDate()
{
let selected = Navigation.instance.getSelectedGradingPeriod();
return selected == -1 ? Constants.TERMS[3] : Constants.TERMS[selected + 1];
}
} }
@@ -2,20 +2,6 @@ import moment from 'moment';
export class FormatUtils export class FormatUtils
{ {
/**
* Convert date format to yyyy-mm-dd
*
* @param _date Date
*/
public static toChartDate(_date: string | Date)
{
// Convert to Date
let date: Date = _date instanceof Date ? _date : new Date(_date);
// Convert to yyyy-mm-dd
return moment(date).format('YYYY-MM-DD');
}
/** /**
* Limit string length * Limit string length
* *
@@ -1,5 +1,4 @@
import Course from '@/logic/course'; import Course, {Assignment} from '@/logic/course';
import {Assignment} from '@/components/app/app';
export interface Scale export interface Scale
{ {
@@ -136,7 +135,7 @@ export class GPAUtils
}); });
// Return // Return
return score / max * 100; return +(score / max * 100).toFixed(2);
} }
/** /**
@@ -186,6 +185,6 @@ export class GPAUtils
} }
// Add average to the row // Add average to the row
return score * 100; return +(score * 100).toFixed(2);
} }
} }
+99
View File
@@ -0,0 +1,99 @@
import Constants from '@/constants';
export default class GraphUtils
{
static DOT = '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:{color}"></span>';
/**
* Base settings
*
* @param title
* @param subtitle
*/
static getBaseSettings(title?: String, subtitle?: String)
{
return {
// Color
color: Constants.THEME.colors,
backgroundColor: 'transparent',
// Title
title:
{
show: title != null,
textStyle:
{
fontSize: 13
},
text: title,
subtext: subtitle,
x: 'center'
},
}
}
/**
* Get term mark lines
*/
static getTermLines()
{
return {
silent: true,
symbol: 'none',
lineStyle: {color: Constants.THEME.colors[2]},
animationDuration: 500,
data: Constants.TERMS.map((term, index) =>
{
return {xAxis: term.getTime(), label: {formatter: `Term ${index + 1}`}}
})
}
}
/**
* Get mark areas for percentage scores
*/
static getGradeMarkAreas(opacity: number)
{
return {
silent: true,
data:
[
// Above 100
[{itemStyle: {color: 'rgba(230,253,255)', opacity: opacity}, yAxis: 120}, {yAxis: 100}],
// 90 to 100
[{itemStyle: {color: 'rgba(241,255,237)', opacity: opacity}, yAxis: 100}, {yAxis: 90}],
// 80 to 90
[{itemStyle: {color: 'rgba(255,250,216)', opacity: opacity}, yAxis: 90}, {yAxis: 80}],
// 70 to 80
[{itemStyle: {color: 'rgba(255,225,199)', opacity: opacity}, yAxis: 80}, {yAxis: 70}],
// Below 70 (Fail)
[{itemStyle: {color: 'rgb(255,190,184)', opacity: opacity}, yAxis: 70}, {yAxis: -100}]
]
}
}
/**
* Text style for pie graphs or radar graphs
*/
static pieTextStyle()
{
return {
fontSize: 14,
textShadowColor: '#cfcfcf',
textShadowBlur: 2,
textShadowOffsetX: 1,
textShadowOffsetY: 1,
backgroundColor: '#f6f6f6',
borderRadius: 3,
padding: [3, 5]
}
}
/**
* CSS shadow string (extraCssText) for tooltip
*/
static tooltipCssShadow()
{
return {extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);'}
}
}
@@ -1,4 +1,4 @@
import {Assignment} from '@/components/app/app'; import {Assignment} from '@/logic/course';
export default class JsonUtils export default class JsonUtils
{ {
@@ -18,7 +18,7 @@ export default class JsonUtils
type: assignment.assignment_type, type: assignment.assignment_type,
typeId: assignment.assignment_type_id, typeId: assignment.assignment_type_id,
description: assignment.assignment_description, description: assignment.assignment_description,
date: new Date(assignment._date), time: new Date(assignment._date).getTime(),
complete: assignment.completion_status, complete: assignment.completion_status,
include: assignment.include_in_calculated_grade == 1, include: assignment.include_in_calculated_grade == 1,
display: assignment.display_grade == 1, display: assignment.display_grade == 1,
@@ -2,8 +2,8 @@
<div id="assignment-type-head"> <div id="assignment-type-head">
<el-card :body-style="{padding: '0px'}"> <el-card :body-style="{padding: '0px'}">
<div id="type-info-card"> <div id="type-info-card">
<span id="type-name">{{typeName}}</span> <span id="type-name">{{type.name}}</span>
<span id="type-average">Average: {{average.toFixed(2)}}%</span> <span id="type-average">Average: {{type.percent}}%</span>
</div> </div>
<AssignmentEntry v-for="assignment of filteredAssignments" :key="assignment.id" <AssignmentEntry v-for="assignment of filteredAssignments" :key="assignment.id"
@@ -16,27 +16,21 @@
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import {Assignment} from '@/components/app/app';
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue'; import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
import {Assignment, AssignmentType} from '@/logic/course';
@Component({ @Component({
components: {AssignmentEntry} components: {AssignmentEntry}
}) })
export default class AssignmentTypeHead extends Vue export default class AssignmentTypeHead extends Vue
{ {
@Prop({required: true}) typeName: string; @Prop({required: true}) type: AssignmentType;
@Prop({required: true}) assignments: Assignment[]; @Prop({required: true}) assignments: Assignment[];
get filteredAssignments() get filteredAssignments()
{ {
// Filter assignments to only this type // Filter assignments to only this type
return this.assignments.filter(a => a.complete == 'Complete' && a.type == this.typeName); return this.assignments.filter(a => a.typeId == this.type.id);
}
get average()
{
return this.filteredAssignments.reduce((a, b) => a + b.score, 0) /
this.filteredAssignments.reduce((a, b) => a + b.scoreMax, 0) * 100;
} }
} }
</script> </script>
+6
View File
@@ -18,3 +18,9 @@
// https://stackoverflow.com/questions/17572619/inset-box-shadow-only-on-one-side // https://stackoverflow.com/questions/17572619/inset-box-shadow-only-on-one-side
box-shadow: inset 0 7px 9px -7px rgba(0,0,0,0.1); box-shadow: inset 0 7px 9px -7px rgba(0,0,0,0.1);
} }
.type-graph
{
padding-top: 23px;
height: 420px !important;
}
+20 -22
View File
@@ -4,18 +4,30 @@
<div class="course-card-content expand"> <div class="course-card-content expand">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24" class="course-page-graph">
<el-card class="large overall-line-card vertical-center"> <el-card class="large overall-line-card vertical-center">
<course-scatter :course="course"></course-scatter> <course-scatter :course="course"></course-scatter>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="0"> </el-row>
<el-row>
<el-col :span="12" class="course-page-graph">
<el-card class="large overall-line-card vertical-center type-graph"
body-style="padding: 0">
<TypeRadar :course="course"></TypeRadar>
</el-card>
</el-col>
<el-col :span="12" class="course-page-graph">
<el-card class="large overall-line-card vertical-center type-graph"
body-style="padding: 0">
<TypePie :course="course"></TypePie>
</el-card>
</el-col> </el-col>
</el-row> </el-row>
<AssignmentTypeHead v-for="type in getAssignmentTypes()" :key="type" <AssignmentTypeHead v-for="type in course.assignmentTypes" :key="type.id"
:type-name="type" :assignments="course.assignments"> :type="type" :assignments="course.assignments">
</AssignmentTypeHead> </AssignmentTypeHead>
</div> </div>
</el-card> </el-card>
@@ -23,15 +35,16 @@
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import {Assignment} from '@/components/app/app';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue'; import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
import CourseScatter from '@/pages/course/course-scatter/course-scatter'; import CourseScatter from '@/pages/course/course-scatter/course-scatter';
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue'; import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
import AssignmentTypeHead from '@/pages/course/assignment-type-head/assignment-type-head.vue'; import AssignmentTypeHead from '@/pages/course/assignment-type-head/assignment-type-head.vue';
import Course from '@/logic/course'; import Course, {Assignment} from '@/logic/course';
import TypeRadar from '@/pages/course/type-radar/type-radar';
import TypePie from '@/pages/course/type-pie/type-pie';
@Component({ @Component({
components: {AssignmentEntry, CourseHead, CourseScatter, AssignmentTypeHead} components: {TypeRadar, TypePie, AssignmentEntry, CourseHead, CourseScatter, AssignmentTypeHead}
}) })
export default class CoursePage extends Vue export default class CoursePage extends Vue
{ {
@@ -52,21 +65,6 @@
} }
else return this.unread; else return this.unread;
} }
/**
* Get all the types of the assignments.
*/
getAssignmentTypes(): string[]
{
// Get all types
let types = this.course.assignments.map(a => a.type);
// Remove duplicates
types = types.filter((type, i, a) => a.indexOf(type) == i);
// Return it
return types;
}
} }
</script> </script>
@@ -1,16 +1,15 @@
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import {Assignment} from '@/components/app/app';
import Constants from '@/constants'; import Constants from '@/constants';
import {FormatUtils} from '@/utils/format-utils'; import {FormatUtils} from '@/logic/utils/format-utils';
import moment from 'moment'; import moment from 'moment';
import Course from '@/logic/course'; import Course, {Assignment} from '@/logic/course';
import GraphUtils from '@/logic/utils/graph-utils';
import chroma from 'chroma-js';
@Component({ @Component({
}) })
export default class CourseScatter extends Vue export default class CourseScatter extends Vue
{ {
private static DOT = '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:{color}"></span>';
@Prop({required: true}) course: Course; @Prop({required: true}) course: Course;
/** /**
@@ -28,40 +27,11 @@ export default class CourseScatter extends Vue
*/ */
get chartSettings() get chartSettings()
{ {
// Map assignments
let map = this.mapAssignments();
// Scatter data point style
let itemStyle =
{
normal:
{
opacity: 0.8,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowColor: 'rgba(0, 0, 0, 0.2)'
}
};
// Create settings // Create settings
let settings = let settings =
{ {
// Color // Base settings
color: Constants.THEME.colors, ...GraphUtils.getBaseSettings('Assignments', 'Assignment scores for ' + this.course.name),
// Title
title:
{
show: true,
textStyle:
{
fontSize: 13
},
text: 'Assignments',
subtext: 'Assignment scores for ' + this.course.name,
x: 'center'
},
// X axis represents course names // X axis represents course names
xAxis: xAxis:
@@ -71,7 +41,7 @@ export default class CourseScatter extends Vue
{ {
formatter: (name: any) => moment(name).format('MMM DD') formatter: (name: any) => moment(name).format('MMM DD')
}, },
max: FormatUtils.toChartDate(new Date()) max: new Date().getTime()
}, },
// Y axis represents GPAs and MaxGPAs // Y axis represents GPAs and MaxGPAs
@@ -85,21 +55,23 @@ export default class CourseScatter extends Vue
{ {
formatter: (name: any) => name + '%' formatter: (name: any) => name + '%'
}, },
max: 100, min: (value: any) => Math.floor(value.min) - 5,
min: (value: any) => Math.floor(value.min) - 5 max: (value: any) => Math.min(Math.ceil(value.max), 110)
}, },
// Tooltip // Tooltip
tooltip: tooltip:
{ {
...GraphUtils.tooltipCssShadow(),
trigger: 'axis', trigger: 'axis',
axisPointer: axisPointer:
{ {
type: 'cross' type: 'cross'
}, },
formatter: (ps: any[]) => ps[0].data[0] + '<br>' + ps.map(p => formatter: (ps: any[]) => moment(ps[0].data[0]).format('MMM DD, YYYY') + '<br>' + ps.map(p =>
`${CourseScatter.DOT.replace('{color}', p.color)} `${GraphUtils.DOT.replace('{color}', p.color)}
${FormatUtils.limit(p.data[2], 22)}: ${p.data[1]}%<br>`).join('') ${FormatUtils.limit(p.data[2].description, 22)}: ${p.data[1]}%<br>`).join('')
}, },
// Legend // Legend
@@ -115,39 +87,70 @@ export default class CourseScatter extends Vue
}, },
// Data // Data
series: Array.from(map, ([type, assignments]) => series: this.series()
{
return {
type: 'scatter',
name: type,
data: CourseScatter.assignmentsData(assignments),
itemStyle: itemStyle
}
})
}; };
return settings; return settings;
} }
/** /**
* Map assignments to {assignmentType, [assignment]} format. * Get series data
*/ */
private mapAssignments(): Map<string, Assignment[]> private series()
{ {
// Define map // Create scatter plots
let map = new Map(); let series: any[] = this.course.assignmentTypes.map((type, i) =>
// Move data to map
this.course.assignments.forEach(a =>
{ {
// Null case, create empty array return {
if (!map.has(a.type)) map.set(a.type, []); type: 'scatter',
name: type.name,
data: CourseScatter.assignmentsData(this.course.assignments.filter(a => a.typeId == type.id)),
symbolSize: (data: any) => Math.max(Math.sqrt(type.weight * data[2].scoreMax / type.scoreMax) * 12, 12),
// Put data label:
map.get(a.type).push(a); {
emphasis:
{
show: true,
formatter: (p: any) => p.data[2].description,
position: 'top'
}
},
itemStyle:
{
normal:
{
opacity: 0.7,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowColor: 'rgba(0, 0, 0, 0.2)',
color:
{
type: 'radial',
x: 0.4,
y: 0.3,
colorStops:
[
{offset: 0, color: chroma(Constants.THEME.colors[i]).set('hsl.l', 0.9).css()},
{offset: 1, color: Constants.THEME.colors[i]}
]
}
}
}
}
}); });
return map; // Push other stuff
series.push(
{
type: 'line',
markLine: GraphUtils.getTermLines(),
markArea: GraphUtils.getGradeMarkAreas(0.4)
});
return series;
} }
/** /**
@@ -158,6 +161,6 @@ export default class CourseScatter extends Vue
private static assignmentsData(assignments: Assignment[]) private static assignmentsData(assignments: Assignment[])
{ {
return assignments.filter(a => a.complete == 'Complete') return assignments.filter(a => a.complete == 'Complete')
.map(a => [FormatUtils.toChartDate(a.date), (a.score / a.scoreMax * 100).toFixed(2), a.description]); .map(a => [a.time, (a.score / a.scoreMax * 100).toFixed(2), a]);
} }
} }
@@ -1,16 +1,8 @@
<template> <template>
<div id="course-scatter"> <div id="course-scatter">
<ve-scatter height="450px" class="graph" :extend="{heyIUsedCourseObject: this.course.name}" :after-config="afterConfig"></ve-scatter> <ve-scatter height="450px" class="graph" :extend="{a: this.course.name}" :after-config="afterConfig"></ve-scatter>
</div> </div>
</template> </template>
<script src="./course-scatter.ts" lang="ts"></script> <script src="./course-scatter.ts" lang="ts"></script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
#overall-bar
{
.graph
{
margin-top: 50px;
}
}
</style>
+60
View File
@@ -0,0 +1,60 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {FormatUtils} from '@/logic/utils/format-utils';
import moment from 'moment';
import Course, {Assignment} from '@/logic/course';
import GraphUtils from '@/logic/utils/graph-utils';
@Component
export default class TypePie extends Vue
{
@Prop({required: true}) course: Course;
/**
* Override options
*
* @param options Original options (Unused)
*/
afterConfig(options: any)
{
return this.chartSettings;
}
/**
* Generate settings
*/
get chartSettings()
{
// Create settings
let settings =
{
...GraphUtils.getBaseSettings('Assignment Type Weight',
'How much each type of assignment affect your average'),
tooltip: GraphUtils.tooltipCssShadow(),
// Data
series:
{
type: 'pie',
avoidLabelOverlap: false,
radius: ['40%', '60%'],
center: ['50%', '55%'],
label: GraphUtils.pieTextStyle(),
data: this.course.assignmentTypes.map((t, i) => {return {
value: t.weight,
name: `${t.name}\n${t.weight}%`,
itemStyle:
{
color: Constants.THEME.colors[i],
opacity: 0.8,
shadowColor: 'rgba(0,0,0,0.22)',
shadowBlur: 10
}
}}).sort((a, b) => a.value - b.value)
}
};
return settings;
}
}
+8
View File
@@ -0,0 +1,8 @@
<template>
<div id="type-pie">
<ve-pie height="420px" class="graph" :extend="{a: this.course.name}" :after-config="afterConfig"></ve-pie>
</div>
</template>
<script src="./type-pie.ts" lang="ts"></script>
<style lang="scss" scoped></style>
+106
View File
@@ -0,0 +1,106 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {FormatUtils} from '@/logic/utils/format-utils';
import moment from 'moment';
import Course, {Assignment} from '@/logic/course';
import GraphUtils from '@/logic/utils/graph-utils';
@Component
export default class TypeRadar extends Vue
{
@Prop({required: true}) course: Course;
/**
* Override options
*
* @param options Original options (Unused)
*/
afterConfig(options: any)
{
return this.chartSettings;
}
/**
* Generate settings
*/
get chartSettings()
{
let min = this.course.assignmentTypes.reduce((min, t) => Math.min(min, t.percent), 100);
// Create settings
let settings =
{
...GraphUtils.getBaseSettings('Assignment Type Radar',
'How are you doing for different types of assignment'),
// Radar settings
radar:
{
// shape: 'circle',
name:
{
textStyle: GraphUtils.pieTextStyle()
},
splitArea:
{
areaStyle:
{
color:
[
'rgb(255,161,151)',
'rgb(255,190,184)',
'rgba(255,225,199)',
'rgba(255,250,216)',
'rgba(241,255,237)',
],
opacity: 0.4
}
},
indicator: this.course.assignmentTypes.map((t, i) => {return {
name: `${t.name}\n${t.percent}%`,
max: 100,
min: min - 30,
color: Constants.THEME.colors[i]
}}),
radius: '60%',
center: ['50%', '55%']
},
tooltip: GraphUtils.tooltipCssShadow(),
// Data
series:
{
type: 'radar',
data:
[
{
name: 'Score',
symbol: 'circle',
areaStyle:
{
color:
{
type: 'radial',
x: 0.5, y: 0.55, r: 0.5,
colorStops:
[
{offset: 0, color: '#ffa0a0'},
{offset: 0.5, color: '#fffead'},
{offset: 1, color: '#d1ffde'}
],
global: false // 缺省为 false
},
opacity: 0.2
},
value: this.course.assignmentTypes.map(t => t.percent)
}
]
},
color: '#6771c1'
};
return settings;
}
}
@@ -0,0 +1,8 @@
<template>
<div id="type-radar">
<ve-radar height="420px" class="graph" :extend="{a: this.course.name}" :after-config="afterConfig"></ve-radar>
</div>
</template>
<script src="./type-radar.ts" lang="ts"></script>
<style lang="scss" scoped></style>
+2 -2
View File
@@ -1,8 +1,8 @@
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import Course from '@/logic/course'; import Course from '@/logic/course';
import {GPAUtils} from '@/utils/gpa-utils'; import {GPAUtils} from '@/logic/utils/gpa-utils';
import Constants from '@/constants'; import Constants from '@/constants';
import {FormatUtils} from '@/utils/format-utils'; import {FormatUtils} from '@/logic/utils/format-utils';
@Component({ @Component({
}) })
@@ -5,8 +5,8 @@
<el-row class="unread-row"> <el-row class="unread-row">
<el-col :span="3" class="date"> <el-col :span="3" class="date">
<span class="month">{{getMoment(assignment.date).format("MMM D")}}</span> <span class="month">{{getMoment(assignment.time).format("MMM D")}}</span>
<span class="now">({{getMoment(assignment.date).fromNow()}})</span> <span class="now">({{getMoment(assignment.time).fromNow()}})</span>
</el-col> </el-col>
<el-col :span="15" class="description"> <el-col :span="15" class="description">
@@ -36,8 +36,8 @@
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import {Assignment} from '@/components/app/app';
import moment from 'moment'; import moment from 'moment';
import {Assignment} from '@/logic/course';
@Component @Component
export default class AssignmentEntry extends Vue export default class AssignmentEntry extends Vue
@@ -53,7 +53,7 @@
* *
* @param date Date * @param date Date
*/ */
getMoment(date: string) getMoment(date: number)
{ {
return moment(new Date(date)); return moment(new Date(date));
} }
@@ -1,5 +1,5 @@
<template> <template>
<div id="course-head" class="course-card-content main vertical-center"> <div id="course-head" class="course-card-content main vertical-center" @click="redirect">
<el-row> <el-row>
<el-col :span="12" class="course-col-name"> <el-col :span="12" class="course-col-name">
<div v-if="clickable" class="course-name clickable" @click="redirect">{{course.name}}</div> <div v-if="clickable" class="course-name clickable" @click="redirect">{{course.name}}</div>
@@ -26,7 +26,7 @@
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import Course from '@/logic/course'; import Course from '@/logic/course';
import {CourseUtils} from '@/utils/course-utils'; import {CourseUtils} from '@/logic/utils/course-utils';
import Navigation from '@/components/navigation/navigation'; import Navigation from '@/components/navigation/navigation';
@Component @Component
@@ -1,8 +1,8 @@
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import App, {Assignment} from '@/components/app/app'; import App from '@/components/app/app';
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue'; import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue'; import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
import Course from '@/logic/course'; import Course, {Assignment} from '@/logic/course';
@Component({ @Component({
components: {UnreadEntry: AssignmentEntry, CourseHead} components: {UnreadEntry: AssignmentEntry, CourseHead}
+139 -197
View File
@@ -1,224 +1,166 @@
import {Component, Prop, Vue} from 'vue-property-decorator'; import {Component, Prop, Vue} from 'vue-property-decorator';
import moment from 'moment'; import moment from 'moment';
import Course from '@/logic/course'; import Course, {Assignment} from '@/logic/course';
import Constants from '@/constants';
import Navigation from '@/components/navigation/navigation';
import {CourseUtils} from '@/logic/utils/course-utils';
import GraphUtils from '@/logic/utils/graph-utils';
import {GPAUtils} from '@/logic/utils/gpa-utils';
@Component({ @Component
})
export default class OverallLine extends Vue export default class OverallLine extends Vue
{ {
@Prop({required: true}) courses: Course[]; @Prop({required: true}) courses: Course[];
private settings = filteredCourses: Course[];
{ settings: any;
// Title
title:
{
show: true,
textStyle:
{
fontSize: 12
},
text: 'Average Grade',
subtext: 'Average score trend for every course',
x: 'center'
},
// Legend
legend:
{
show: false,
textStyle:
{
fontSize: 11
},
icon: 'circle'
},
// Zoom bar
dataZoom:
[
{
startValue: moment().subtract(30, 'days').format('M/D/YYYY')
},
{
type: 'inside'
}
],
series:
{
smooth: true,
// Mark area
markArea:
{
silent: true,
data:
[
// Above 100
[
{
yAxis: 120,
itemStyle: {color: 'rgba(230,253,255,0.09)'}
}, {yAxis: 100}
],
// 90 to 100
[
{
yAxis: 100,
itemStyle: {color: 'rgba(241,255,237,0.09)'}
}, {yAxis: 90}
],
// 80 to 90
[
{
yAxis: 90,
itemStyle: {color: 'rgba(255,250,216,0.09)'}
}, {yAxis: 80}
],
// 70 to 80
[
{
yAxis: 80,
itemStyle: {color: 'rgba(255,225,199,0.1)'}
}, {yAxis: 70}
],
// Below 70 (Fail)
[
{
yAxis: 70,
itemStyle: {color: 'rgb(255,190,184, 0.09)'}
}, {yAxis: -100}
]
]
}
},
xAxis:
{
//type: 'time'
},
yAxis:
{
min: (value: any) => Math.floor(value.min),
max: (value: any) => Math.min(value.max, 110)
}
};
chartCache: any;
/** /**
* Convert assignments list to a graph dataset. * When this component is created
*/ */
get convertChart() created()
{ {
// Caching // Filter courses
if (this.chartCache != undefined) return this.chartCache; this.filteredCourses = this.courses.filter(c => c.isGraded && c.assignments.length > 0);
let courses = this.courses.filter(c => c.assignments.length > 0); // Generate settings
this.settings =
{
...GraphUtils.getBaseSettings('Average Grade', 'Average score trend for every course'),
// Compute the column names // Zoom bar
let columns = courses.map(course => course.name); dataZoom:
columns.unshift('date'); [
{
type: 'slider',
startValue: Math.max(moment().subtract(30, 'days').toDate().getTime(),
CourseUtils.getTermBeginDate().getTime()),
// Minimum zoom: 1 week
minValueSpan: 7 * 24 * 60 * 60 * 1000
}
],
// Tooltip
tooltip:
{
... GraphUtils.tooltipCssShadow(),
trigger: 'axis'
},
// Axis
xAxis:
{
type: 'time',
axisLabel:
{
formatter: (name: any) => moment(name).format('MMM DD')
},
},
yAxis:
{
axisLabel:
{
formatter: (name: any) => name + '%'
},
min: (value: any) => Math.floor(value.min),
max: (value: any) => Math.min(Math.ceil(value.max), 110)
},
// Series data
series: this.series()
}
}
/**
* Override options
*
* @param options Original options (Unused)
*/
afterConfig(options: any)
{
return this.settings;
}
/**
* Generate series data
*/
private series()
{
// Each course
let series: any[] = this.filteredCourses.map(course => this.getCourseSeries(course));
// Push other stuff
series.push(
{
type: 'line',
markLine: GraphUtils.getTermLines(),
markArea: GraphUtils.getGradeMarkAreas(0.4)
});
return series
}
/**
* Generate series data for a course
*
* @param course
*/
private getCourseSeries(course: Course)
{
// Graded assignments
let assignments = course.assignments.slice().reverse();
// Create series
return {
name: course.name,
type: 'line',
smooth: true,
symbol: 'circle', // circle, diamond, emptyCircle, none
data: this.toDateRange([...assignments.map(a => a.time)].map((time, i) =>
{
// Find subset before this assignment
let subset = assignments.filter(a => a.time <= time);
// Find grade
if (course.grading.method == 'PERCENT_TYPE')
return [time, GPAUtils.getPercentTypeAverage(course, subset)];
if (course.grading.method == 'TOTAL_MEAN')
return [time, GPAUtils.getTotalMeanAverage(subset)];
}))
}
}
/**
* Convert point data to date range data.
* Eg. [[Mon, 10], [Wed, 5]] to [[Mon, 10], [Tue, 10], [Wed, 5]]
*
* @param data
*/
private toDateRange(data: any[])
{
// Find the min date // Find the min date
let minDates = courses.map(course => course.assignments[course.assignments.length - 1].date.getTime()); let minDates = this.courses.map(course => course.assignments[course.assignments.length - 1].time);
let minDate: Date = new Date(Math.min.apply(null, minDates)); let minDate: Date = new Date(Math.min.apply(null, minDates));
// Find the dates in between // Find the dates in between
let now = new Date(); let now = new Date(Math.min(new Date().getTime(), CourseUtils.getTermEndDate().getTime()));
let dates = []; let dates: number[] = [];
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1)) for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
{ {
dates.push(new Date(date)); dates.push(new Date(date).getTime());
} }
// Compute the rows data let lastValue: any = null;
let rows: {[index: string]: any}[] = []; return dates.map(date =>
dates.forEach(date =>
{ {
// Define row object // Data point on this specific date
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')}; let thisValue = data.find(a => a[0] == date);
// Loop through courses // None
courses.forEach(course => if (thisValue == null) return [date, lastValue == null ? null : lastValue[1]];
{ else return [date, (lastValue = thisValue)[1]];
// 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
if (assignment.date.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
if (assignment.date.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] ++;
}
});
// Count total percentage (This is to avoid less than expected cases)
// Eg. If HW = 25% and Quiz = 75%, I have 1 hw and 0 quiz
// Without total percentage, the avg grade I get is 25%.
let totalPercentage = 0;
for (let type in course.grading.weightingMap)
{
if (typeScores[type] != undefined)
{
totalPercentage += course.grading.weightingMap[type];
}
}
// Count
let score = 0;
for (let type in typeScores)
{
let typeFactor = course.grading.weightingMap[type] / totalPercentage;
score += typeScores[type] * typeFactor / typeCounts[type];
}
// Add average to the row
if (score != 0) row[course.name] = score * 100;
}
});
// Add it to the array
rows.push(row);
}); });
return this.chartCache =
{
columns: columns,
rows: rows
}
} }
} }
@@ -1,6 +1,6 @@
<template> <template>
<div id="overall-line"> <div id="overall-line">
<ve-line :data="convertChart" :extend="settings"></ve-line> <ve-line :extend="{a: this.courses}" :after-config="afterConfig"></ve-line>
</div> </div>
</template> </template>
+7 -7
View File
@@ -1,8 +1,8 @@
<template> <template>
<div id="overall"> <div id="overall">
<el-row v-if="getGPA().gpa !== -1"> <el-row v-if="getGPA().gpa !== -1">
<el-col :span="4"> <el-col :span="4" class="overall-span">
<el-card class="large gpa-card vertical-center"> <el-card class="large gpa-card vertical-center" body-style="padding: 0">
<div style="padding: 14px;"> <div style="padding: 14px;">
<span class="gpa header">GPA:</span> <span class="gpa header">GPA:</span>
<span class="gpa text">{{getGPA().gpa}}</span> <span class="gpa text">{{getGPA().gpa}}</span>
@@ -13,13 +13,13 @@
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="14"> <el-col :span="14" class="overall-span">
<el-card class="large overall-line-card vertical-center"> <el-card class="large overall-line-card vertical-center" body-style="padding: 0 10px">
<overall-line :courses="courses"></overall-line> <overall-line :courses="courses"></overall-line>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6" class="overall-span">
<el-card class="large overall-bar-card vertical-center"> <el-card class="large overall-bar-card vertical-center" body-style="padding: 0 10px">
<overall-bar :courses="courses"></overall-bar> <overall-bar :courses="courses"></overall-bar>
</el-card> </el-card>
</el-col> </el-col>
@@ -44,7 +44,7 @@
import OverallBar from '@/pages/overall/overall-bar/overall-bar'; import OverallBar from '@/pages/overall/overall-bar/overall-bar';
import OverallCourse from '@/pages/overall/overall-course/overall-course'; import OverallCourse from '@/pages/overall/overall-course/overall-course';
import Course from '@/logic/course'; import Course from '@/logic/course';
import {GPAUtils} from '@/utils/gpa-utils'; import {GPAUtils} from '@/logic/utils/gpa-utils';
@Component({ @Component({
components: {OverallLine, OverallBar, OverallCourse} components: {OverallLine, OverallBar, OverallCourse}