# -*- coding: utf-8 -*-
"""
Created on Thu Oct 10 17:13:50 2024
@author: thoma
"""
# Libraries for the data
from collections import defaultdict #Make it is easier to create data strcuture for bar graph
import os #Talk to the operating system
import glob #Searches directories and files
import json #Reads javascript files
import numpy as np #Mathematical library
import pandas as pd #Dataframes -- python's Excel
# Dashboard Library
import solara # Key dashboard
import ipywidgets as widgets #Makes the dropdowns and sliders
# Plotting library
import plotly.express as px
import plotly.graph_objects as go
######################################################
# HELPER FUNCTIONS
###############################################
with open('data/drop_down_small.json', 'r') as json_file:
drop_downs = json.load(json_file)
def score_to_grade(score):
if score >= 93:
return 'A'
elif score >= 90:
return 'A-'
elif score >= 83:
return 'B'
elif score >= 80:
return 'B-'
elif score >= 78:
return 'C+'
elif score >= 70:
return 'C'
else:
return 'F'
def final_grades(section, assignment):
grades = {}
all_section = pd.read_csv(f"data/course_grades/{section}/{assignment}")
if "final" in assignment:
grade_list = all_section['Final Grade'].apply(score_to_grade)
else:
grade_list = all_section['Grades'].apply(score_to_grade)
for g in grade_list:
if g not in grades.keys():
grades[g] = 1
else:
grades[g] += 1
df = pd.DataFrame([grades])
return df
def make_overall(df, title):
# Get rid of unanmed
df.columns = [col if 'Unnamed' not in col else 'unknown' for col in df.columns]
# Ensure all expected columns (A, B, C, D, F) are present, add missing ones with default value (e.g., None)
expected_columns = ['A', 'A-','B','B-','C+', 'C','F', 'Unknown']
for col in expected_columns:
if col not in df.columns:
df[col] = 0
# Reorder the columns in grade order
df = df[expected_columns]
fig = go.Figure(data=[
go.Bar(x=list(df.columns), y=df.iloc[0].tolist()) # x for categories, y for values
])
# Customize the layout (optional)
fig.update_layout(
title=title,
xaxis_title="Grades",
yaxis_title="Frequency"
)
# Set the figure height to display all rows
fig.update_layout(height=500)
return solara.FigurePlotly(fig)
def plot_grades(program, course,section,assignment):
dfs = []
folders=[]
file=f"data/course_grades/{program}/{course}/{section}/{assignment}.csv"
df = pd.read_csv(file)
df = df.replace({pd.NA: None, np.nan: None})
df["Student ID"] = df['Student ID'].astype(str)
fig = px.bar(df,
x='Grades',
y='Student ID',
orientation='h',
hover_data=["Feedback"],
title=assignment)
# Set the figure height to display all rows
fig.update_layout(height=1000)
# Set the x-axis range to start from the lowest grade minus 10
min_grade = df['Grades'].min()
max_grade = df["Grades"].max()
fig.update_xaxes(range=[min_grade - 5, max_grade]) # Lower bound set, upper bound auto
return solara.FigurePlotly(fig)
def grade_distribution(level, target, details):
if level=="all":
return make_overall(pd.read_csv("data/course_grades/overall_grades.csv"), "Overall Grade Distribution")
if level=="program":
return make_overall(pd.read_csv(f"data/course_grades/{target}/overall_grades.csv"), f"{target} Grade Distribution")
if level=="course":
try: #account for faculty who dont use blackboard
return make_overall(pd.read_csv(f"data/course_grades/{target}/overall_grades.csv"), f"{target} Grade Distribution")
except:
return solara.Markdown("## This course did not input grades in blackboard.")
if level=="section":
section_grades = final_grades(target, "final_grade.csv")
return make_overall(section_grades, f"{target} Grade Distribution")
if level == "assignment":
section_grades = final_grades(target,details)
return make_overall(section_grades, "Assignment Grade Distribution")
############################################################
################### DASHBOARD
###########################################################
# Reactive state management for dropdowns
selected_program = solara.reactive(None)
selected_course = solara.reactive(None)
selected_section = solara.reactive(None)
selected_assignments = solara.reactive(None)
graph_grades = solara.reactive(False)
reset_selection =solara.reactive(False)
def reset_controls():
selected_program.value = None
selected_course.value = None
selected_section.value = None
selected_assignments.value = None
reset_selection.value=False
def program_selected(program):
if program != None:
courses = list(drop_downs[program].keys())
courses.sort()
return courses
def course_selected(course):
if course != None:
if course not in list(drop_downs[selected_program.value].keys()):
return None
else:
sections = list(drop_downs[selected_program.value][course]["Sections"].keys())
sections.sort()
#plot
return sections
else:
return None
def section_selected(section):
if section != None:
if section not in list(drop_downs[selected_program.value][selected_course.value]["Sections"].keys()):
return None
else:
assignments = drop_downs[selected_program.value][selected_course.value]["Sections"][section]
assignments.sort()
return assignments
else:
return None
def plot_grade_status():
graph_grades.set(not graph_grades.value)
@solara.component
def View():
try:
if selected_program.value == None:
grade_distribution("all", None, None)
elif selected_program.value != None and selected_course.value == None:
grade_distribution("program", selected_program.value, drop_downs[selected_program.value])
elif selected_course.value != None and selected_section.value == None:
target =selected_program.value+"/"+selected_course.value
grade_distribution("course", target, drop_downs[selected_program.value][selected_course.value])
elif selected_section.value != None and selected_assignments.value == None:
target =selected_program.value+"/"+selected_course.value+"/"+selected_section.value
grade_distribution("section", target, None)
elif selected_assignments != None:
target = selected_program.value+"/"+selected_course.value+"/"+selected_section.value
assignment = selected_assignments.value +".csv"
grade_distribution("assignment", target,assignment)
solara.Button(label="Show Grades by Student ID", on_click=plot_grade_status)
if graph_grades.value== True:
plot_grades(selected_program.value, selected_course.value, selected_section.value, selected_assignments.value)
except:
solara.Markdown("## Selections do not compute, please update selections")
reset_controls()
@solara.component
def Controls():
solara.Button(label="Reset Selection", on_click=reset_controls)
try:
solara.Select(label="Program", value=selected_program, values=list(drop_downs.keys()))
solara.Select(label="Course", value=selected_course, values = program_selected(selected_program.value))
#TODO change to SelectMultiple
solara.Select(label="Section", value=selected_section, values = course_selected(selected_course.value))
#TODO change to SelectMultiple
solara.Select(label="Assignments", value = selected_assignments, values = section_selected(selected_section.value))
except:
solara.Markdown("## Selections do not compute, please update selections")
@solara.component
def Title():
solara.Markdown("## Assignment Grade Dashboard")
@solara.component
def Page():
Title()
with solara.Sidebar():
Controls()
View()
Page()