displaying bar graph, fetching distributions message

This commit is contained in:
Sriram Hariharan
2023-03-08 00:26:56 -06:00
parent f3bf746c6e
commit fe8c2378d2
11 changed files with 193 additions and 6 deletions

27
package-lock.json generated
View File

@@ -11,6 +11,8 @@
"chrome-extension-toolkit": "^0.0.23", "chrome-extension-toolkit": "^0.0.23",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"highcharts": "^10.3.3",
"highcharts-react-official": "^3.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass": "^1.57.1", "sass": "^1.57.1",
@@ -8213,6 +8215,20 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/highcharts": {
"version": "10.3.3",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.3.tgz",
"integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw=="
},
"node_modules/highcharts-react-official": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.0.tgz",
"integrity": "sha512-71IJZsLmEboYFjONpwC3NRsg6JKvtKYtS5Si3e6s6MLRSOFNOY8KILTkzvO36kjpeR/A0X3/kvvewE+GMPpkjw==",
"peerDependencies": {
"highcharts": ">=6.0.0",
"react": ">=16.8.0"
}
},
"node_modules/hook-std": { "node_modules/hook-std": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
@@ -23388,6 +23404,17 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true "dev": true
}, },
"highcharts": {
"version": "10.3.3",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.3.tgz",
"integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw=="
},
"highcharts-react-official": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.0.tgz",
"integrity": "sha512-71IJZsLmEboYFjONpwC3NRsg6JKvtKYtS5Si3e6s6MLRSOFNOY8KILTkzvO36kjpeR/A0X3/kvvewE+GMPpkjw==",
"requires": {}
},
"hook-std": { "hook-std": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",

View File

@@ -16,6 +16,8 @@
"chrome-extension-toolkit": "^0.0.23", "chrome-extension-toolkit": "^0.0.23",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"highcharts": "^10.3.3",
"highcharts-react-official": "^3.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass": "^1.57.1", "sass": "^1.57.1",

View File

@@ -9,6 +9,7 @@ import { SessionStore } from './storage/SessionStore';
import browserActionHandler from './handler/browserActionHandler'; import browserActionHandler from './handler/browserActionHandler';
import hotReloadingHandler from './handler/hotReloadingHandler'; import hotReloadingHandler from './handler/hotReloadingHandler';
import tabManagementHandler from './handler/tabManagementHandler'; import tabManagementHandler from './handler/tabManagementHandler';
import courseDataHandler from './handler/courseDataHandler';
onServiceWorkerAlive(); onServiceWorkerAlive();
@@ -34,6 +35,7 @@ const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler, ...browserActionHandler,
...hotReloadingHandler, ...hotReloadingHandler,
...tabManagementHandler, ...tabManagementHandler,
...courseDataHandler,
}); });
messageListener.listen(); messageListener.listen();

View File

@@ -0,0 +1,14 @@
import { MessageHandler } from 'chrome-extension-toolkit';
import CourseDataMessages from 'src/shared/messages/CourseDataMessages';
const courseDataHandler: MessageHandler<CourseDataMessages> = {
getDistribution({ data, sendResponse }) {
const { course } = data;
const dummyData = Array.from({ length: 18 }, () => Math.floor(Math.random() * 100));
sendResponse(dummyData);
},
};
export default courseDataHandler;

View File

@@ -0,0 +1,9 @@
import { Course } from '../types/Course';
export default interface CourseDataMessages {
/**
* Gets the distribution of grades for the given course
* @returns an array of the number of students in each grade range from A+ to F, with the index of the array corresponding to the grade range
*/
getDistribution: (data: { course: Course }) => number[] | undefined;
}

View File

@@ -1,6 +1,4 @@
/** /**
* This is a type with all the message definitions that can be sent TO specific tabs * This is a type with all the message definitions that can be sent TO specific tabs
*/ */
export default interface TAB_MESSAGES { export default interface TAB_MESSAGES {}
reAnalyzePage: (data: { url: string }) => void;
}

View File

@@ -3,18 +3,21 @@ import TAB_MESSAGES from './TabMessages';
import BrowserActionMessages from './BrowserActionMessages'; import BrowserActionMessages from './BrowserActionMessages';
import HotReloadingMessages from './HotReloadingMessages'; import HotReloadingMessages from './HotReloadingMessages';
import TabManagementMessages from './TabManagementMessages'; import TabManagementMessages from './TabManagementMessages';
import CourseDataMessages from './CourseDataMessages';
/** /**
* This is a type with all the message definitions that can be sent TO the background script * This is a type with all the message definitions that can be sent TO the background script
*/ */
export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & HotReloadingMessages; export type BACKGROUND_MESSAGES = BrowserActionMessages &
TabManagementMessages &
HotReloadingMessages &
CourseDataMessages;
/** /**
* A utility object that can be used to send type-safe messages to the background script * A utility object that can be used to send type-safe messages to the background script
*/ */
export const bMessenger = createMessenger<BACKGROUND_MESSAGES>('background'); export const bMessenger = createMessenger<BACKGROUND_MESSAGES>('background');
/** /**
* A utility object that can be used to send type-safe messages to specific tabs * A utility object that can be used to send type-safe messages to specific tabs
*/ */

View File

@@ -33,7 +33,6 @@ export type Semester = {
/** /**
* The internal representation of a course for the extension * The internal representation of a course for the extension
*/ */
export class Course { export class Course {
/** Every course has a uniqueId within UT's registrar system corresponding to each course section */ /** Every course has a uniqueId within UT's registrar system corresponding to each course section */
uniqueId: number; uniqueId: number;

View File

@@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import { Course } from 'src/shared/types/Course'; import { Course } from 'src/shared/types/Course';
import Card from '../../common/Card/Card';
import Popup from '../../common/Popup/Popup'; import Popup from '../../common/Popup/Popup';
import CourseDescription from './CourseDescription/CourseDescription'; import CourseDescription from './CourseDescription/CourseDescription';
import CourseHeader from './CourseHeader/CourseHeader'; import CourseHeader from './CourseHeader/CourseHeader';
import styles from './CoursePopup.module.scss'; import styles from './CoursePopup.module.scss';
import GradeDistribution from './GradeDistribution/GradeDistribution';
interface Props { interface Props {
course: Course; course: Course;
@@ -19,6 +21,7 @@ export default function CoursePopup({ course, onClose }: Props) {
<Popup className={styles.popup} overlay onClose={onClose}> <Popup className={styles.popup} overlay onClose={onClose}>
<CourseHeader course={course} onClose={onClose} /> <CourseHeader course={course} onClose={onClose} />
<CourseDescription course={course} /> <CourseDescription course={course} />
<GradeDistribution course={course} />
</Popup> </Popup>
); );
} }

View File

@@ -0,0 +1,14 @@
.container {
max-width: 100%;
height: 250px;
margin: 20px;
padding: 12px;
> div {
overflow: hidden;
min-width: auto;
max-width: 100%;
height: 250px;
margin: 0 auto;
}
}

View File

@@ -0,0 +1,116 @@
import React, { useEffect, useState } from 'react';
import HighchartsReact from 'highcharts-react-official';
import Highcharts from 'highcharts';
import Card from 'src/views/components/common/Card/Card';
import { bMessenger } from 'src/shared/messages';
import { Course } from 'src/shared/types/Course';
import styles from './GradeDistribution.module.scss';
import colors from 'src/views/styles/colors.module.scss';
enum DataStatus {
LOADING = 'LOADING',
DONE = 'DONE',
ERROR = 'ERROR',
}
interface Props {
course: Course;
}
export default function GradeDistribution({ course }: Props) {
const [chartOptions, setChartOptions] = useState<Highcharts.Options>({
title: {
text: undefined,
},
subtitle: {
text: undefined,
},
legend: {
enabled: false,
},
xAxis: {
title: {
text: 'Grades',
},
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'],
crosshair: true,
},
yAxis: {
min: 0,
title: {
text: 'Students',
},
},
chart: {
style: {
fontFamily: 'Inter',
fontWeight: '600',
},
},
credits: {
enabled: false,
},
tooltip: {
headerFormat: '<span style="font-size:small; font-weight:bold">{point.key}</span><table>',
pointFormat:
'<td style="color:{black};padding:0;font-size:small; font-weight:bold;"><b>{point.y:.0f} Students</b></td>',
footerFormat: '</table>',
shared: true,
useHTML: true,
},
plotOptions: {
bar: {
pointPadding: 0.2,
borderWidth: 0,
},
series: {
animation: {
duration: 700,
},
},
},
series: [
{
type: 'column',
name: 'Grades',
data: [],
},
],
});
const [status, setStatus] = useState<DataStatus>(DataStatus.LOADING);
useEffect(() => {
bMessenger.getDistribution({ course }).then(distribution => {
if (!distribution) {
return setStatus(DataStatus.ERROR);
}
setChartOptions(options => ({
...options,
series: [
{
type: 'column',
name: 'Grades',
data: distribution.map((y, i) => ({
y,
// eslint-disable-next-line no-nested-ternary
// color: i < 8 ? '#2ECC71' : i < 10 ? '#F1C40F' : '#E74C3C',
// eslint-disable-next-line no-nested-ternary
color: i < 8 ? colors.cactus : i < 10 ? colors.sunshine : colors.speedway_brick,
})),
},
],
}));
});
}, [course]);
if (!chartOptions.series?.length) {
return null;
}
return (
<Card className={styles.container}>
<HighchartsReact highcharts={Highcharts} options={chartOptions} />
</Card>
);
}