Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add weekly progress to dashboard visualization #93

Merged
merged 24 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b2fed8d
Start weekly-progress
zMendes Aug 14, 2023
dc6fe89
Get right data ok, visualization pending
zMendes Aug 16, 2023
4f0eb40
Change week logic to be easier to understand (WeekX-<Month>) and tag …
zMendes Aug 18, 2023
5ec58d6
Initial histogram exercise visualization
zMendes Aug 22, 2023
ff9ab04
Trying to make it prettier?...
zMendes Aug 24, 2023
32f5f31
Fixing >50 label
zMendes Aug 24, 2023
90202dc
Make every column in hist gray except of the student's
zMendes Aug 27, 2023
7ad0953
initial table viz
zMendes Aug 27, 2023
0cfce11
Improving html structure
zMendes Aug 28, 2023
54de70a
Exploring refactor on views.py
zMendes Aug 28, 2023
50db3ae
Simplifying student_weekly_data
zMendes Aug 29, 2023
c3d5382
Fix exercise count when 50>
zMendes Aug 29, 2023
601974f
Add course class model and filter students by class in progress dashb…
toshikurauchi Aug 15, 2023
3bb08f3
Adding new visualization option for student progress (#89)
mfstabile Aug 29, 2023
973f032
Toggle view (#91)
mfstabile Aug 29, 2023
ff08732
Initial merge inside one view: progress + progress_weekly
zMendes Aug 31, 2023
65dced9
Dashboard uniting two views working initially
zMendes Sep 2, 2023
ed314a5
Add course class to weekly viz
zMendes Sep 4, 2023
d169e8d
Removing course class code duplication
zMendes Sep 4, 2023
924c096
Refactor weekly student data query/logic
zMendes Sep 4, 2023
6110377
Merge main
zMendes Sep 4, 2023
d769ff6
Add submission data
zMendes Sep 6, 2023
c999c33
Fix histogramChart always showing all data even with a class set
zMendes Sep 6, 2023
63c29a2
Clear data when class changed
zMendes Sep 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/app/dashboard/static/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#headers>* {
margin: 0.2rem;
}

#headers {
width: 99%;
}

.error-msg {
color: red;
}
8 changes: 8 additions & 0 deletions backend/app/dashboard/static/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function courseChanged(select) {
let newValue = select.value;
let baseURL = window.location.href.split("/");
if (baseURL.length > 5)
baseURL = baseURL.slice(0, -1)
baseURL = baseURL.join("/");
window.location = `${baseURL}/${newValue}`;
}
170 changes: 170 additions & 0 deletions backend/app/dashboard/static/dashboard-progress-weekly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@

async function updateFilter() {
let student = selectStudent.value;
let week_label = selectWeek.value;
let courseClass = selectClass.value;
let week = weekData[week_label];
let student_value;
await fetch(`${activeCourse}/${courseClass}/${student}/${week}`).then(async (response) => {
const data = await response.json();
createExercisesTable(data.exercises);
createTagChart(data);
createInfo(data);
student_value = data["total"];

});

await fetch(`${activeCourse}/${courseClass}/${week}`).then(async (response) => {
const data = await response.json();
createHistogram(data, student_value);
});


}
function createInfo(data) {
let dash = document.getElementById("data");
dash.style.visibility = "visible";

let name = document.getElementById("name");
name.innerHTML = selectStudent.value;

let week = document.getElementById("week");
week.innerHTML = selectWeek.value;

let total = document.getElementById("total");
total.innerHTML = data["total"]

let avg = document.getElementById("avg");
avg.innerHTML = Math.round(data["average_points"] * 100) + "%";
}

function createHistogram(data, student_value) {

if (histChart != null)
histChart.destroy();

let keys = Object.keys(data);
const colors = Array(keys.length).fill("#808080");
student_value = parseInt(Math.ceil(student_value / 5)) * 5
let index = keys.indexOf(String(student_value));
if (index == -1)
colors[colors.length - 1] = "#0000FF";
else
colors[index] = "#0000FF";

histChart = new Chart("hist", {
type: "bar",
data: {
labels: Object.keys(data),
datasets: [{
label: "Histogram",
data: Object.values(data),
backgroundColor: colors,
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
}
});
}

function createTagChart(data) {
if (tagChart != null)
tagChart.destroy();

tagChart = new Chart("chart", {
type: "pie",
data: {
labels: Object.keys(data.tags),
datasets: [{
data: Object.values(data.tags)
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
}
});
}

function createExercisesTable(data) {
let dataAsArray = Object.values(data);

if (table != null) {
table.updateSettings({
data: dataAsArray
})
return;
}
let table_div = document.getElementById("table");
table = new Handsontable(table_div, {
data: dataAsArray,
colHeaders: ["Slug", "Max points", "Submissions"],
licenseKey: "non-commercial-and-evaluation", // for non-commercial use only
});
}

function getClassSelect() {
return document.getElementById("select-class");
}

function getCurrentStudents() {
const classSelect = getClassSelect();
const selectedClass = courseClasses[classSelect.selectedIndex];
return selectedClass.students;
}

function updateStudents() {
clearData();
let student_datalist = document.getElementById("students");
student_datalist.innerHTML = "";
let currentStudents = getCurrentStudents();
currentStudents.forEach(item => {
let option = document.createElement("option");
option.value = item;
student_datalist.appendChild(option)
})

}
function clearData() {
document.getElementById("data").style.visibility = "hidden";
selectStudent.value = "";
}

var activeCourse;
var selectStudent;
var selectWeek;
var weekData;
var exercise_div;
var tagChart;
var histChart;
var table;
var courseClasses;
var selectClass;

document.addEventListener("DOMContentLoaded", function () {

let activeButton = document.getElementById("weekly");
selectClass = document.getElementById("select-class");
courseClasses = selectClass.getAttribute("data-classes");
activeButton.className += " active";
activeCourse = document.getElementById("select-course").value;
selectStudent = document.getElementById("select-student");
selectWeek = document.getElementById("select-week");
exercise_div = document.getElementById("exercise-data");
weekData = selectWeek.getAttribute("data-week");
weekData = weekData.replace(/'/g, '"');
courseClasses = courseClasses.replace(/'/g, '"');
courseClasses = JSON.parse(courseClasses);
courseClasses.forEach((courseClass) => {
courseClass.students = new Set(courseClass.students);
});


weekData = JSON.parse(weekData);
selectStudent.onchange = updateFilter;
selectWeek.onchange = updateFilter;
selectClass.onchange = updateStudents;

});
12 changes: 8 additions & 4 deletions backend/app/dashboard/static/dashboard-progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ function createHandsontable(data, columns_list) {

function updateHandsontable(data, columns_list) {
data = filterStudentsInClass(data);

ret = generateMatrix(data, columns_list)
usernames = ret[0]
zvals = ret[1]
Expand Down Expand Up @@ -113,7 +112,6 @@ function updateFilter() {
function updateTableContent() {
const clonedData = structuredClone(data);
const clonedColumns = structuredClone(columns);

let filteredColumns = ["Name"];
exerciseList = [];
if (tags.size == 0) {
Expand Down Expand Up @@ -171,7 +169,7 @@ function generateMatrix(tableData, columns){
if (idx >= 0){
studentvals[idx] = val[1]
}
}
}
});
zvals.push(studentvals)
});
Expand Down Expand Up @@ -264,4 +262,10 @@ function toggleVisibility() {
chart.style.display = "";
document.querySelector('[data-title="Autoscale"]').click()
}
}
}

document.addEventListener('DOMContentLoaded', function () {

let active_button = document.getElementById("semester");
active_button.className += " active";
});
29 changes: 29 additions & 0 deletions backend/app/dashboard/static/progress-table-weekly.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.chart {
width: 20rem;
height: 20rem;
}

#data {
visibility: hidden;
height: 12rem;
display: flexbox;
}

.card {
width: 10rem;
height: 7rem;
}

.cards {
display: flex;
justify-content: space-evenly;
}

#data>* {
margin: 0.2rem;
}

#exercise-data {
display: flex;
justify-content: space-evenly;
}
6 changes: 1 addition & 5 deletions backend/app/dashboard/static/progress-table.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ main {
margin: 1.5em 1em 4em;
}

.error-msg {
color: red;
}

.filterset {
display: flex;
gap: 2em;
Expand Down Expand Up @@ -35,4 +31,4 @@ input {
#select-tag {
width: fit-content;
height: fit-content;
}
}
71 changes: 71 additions & 0 deletions backend/app/dashboard/templates/dashboard/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!DOCTYPE html>
{% load static %}
<html lang="en">
<html>

<head>

<script src="{% static '/base.js' %}"></script>
<link rel="stylesheet" href="{% static '/base.css' %}">

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css" />

{% block head-content %} {% endblock %}


</head>

<body>
<div id="headers">
{% block base-header %}
<h1 class="h1">Dashboard</h1>


<select id="select-course" class="form-select" list="courses" onchange="courseChanged(this)">
<datalist id="courses">{% for course in courses %}
{% if course.name == activeCourse %}
<option selected="selected">{{ course.name }} </option>
{% else %}
<option>{{ course.name }} </option>
{% endif %}
{% endfor %}
</datalist>
</select>

<div class="btn-group">
<a href="/dashboard/instructor/{{ activeCourse }}" id="semester" class="btn btn-primary"
aria-current="page">Semester</a>
<a href="/dashboard/instructor/weekly/{{ activeCourse }}" id="weekly" class="btn btn-primary">Weekly</a>
</div>

{% if course_classes|length == 0 %}
<p class="error-msg">This course has no related classes. Create at least one class using Django Admin.</p>
{% else %}
<div class="filterset">
<div class="filter">
<label for="select-class">Class</label>
<select id="select-class" data-classes="{{ course_classes | safe }}"
{% if course_classes|length == 1 %}
disabled
{% endif %}>
{% for course_class in course_classes %}
<option value="{{ course_class.name }}">{{ course_class.name }}</option>
{% endfor %}
</select>
</div>
</div>
{% block specific-header %} {% endblock %}
{% endif %}
{% endblock %}
</div>
<div id="data">
{% block data %} {% endblock %}
</div>

</body>

</html>
13 changes: 0 additions & 13 deletions backend/app/dashboard/templates/dashboard/instructor-courses.html

This file was deleted.

Loading
Loading