grapher/analyzer.py
2025-01-31 14:34:57 +01:00

467 lines
18 KiB
Python

# Custom
from model import *
from appstate import AppState
# External
from imgui_bundle import (
imgui,
immapp,
hello_imgui,
imgui_md,
implot,
implot3d,
immvision,
ImVec2,
ImVec4,
im_file_dialog
)
from PIL import ImageColor
import numpy as np
from numpy.typing import NDArray
# Built In
from typing import List, Any
def student_list(app_state: AppState) -> None:
statics = student_list
if not app_state.current_class_id:
imgui.text("No Class found in Database")
return
if not hasattr(statics, "select"):
statics.select = 0
statics.students = Student.select().where(Student.class_id == app_state.current_class_id)
statics.students = statics.students if statics.students else None
if not statics.students:
imgui.text(f"No Stundents found in {Class.get_by_id(app_state.current_class_id).name}")
return
for n, student in enumerate(statics.students, start=1):
display = f"{n}. {student.prename} {student.surname}"
_, clicked = imgui.selectable(display, statics.select == n-1)
if clicked:
statics.select = n-1
app_state.current_student_id = statics.students[statics.select].id
def group_list(app_state: AppState) -> None:
statics = group_list
if not app_state.current_class_id:
imgui.text("No Class found in Database")
return
if not hasattr(statics, "select"):
statics.select = 0
statics.groups = Group.select().where(Group.class_id == app_state.current_class_id)
statics.groups = statics.groups if statics.groups else None
if not statics.groups:
imgui.text("No Group found")
return
for n, group in enumerate(statics.groups, start=1):
display = f"{n}. {group.name}"
_, clicked = imgui.selectable(display, statics.select == n-1)
if clicked:
statics.select = n-1
def lecture_list(app_state: AppState) -> None:
statics = lecture_list
if not app_state.current_class_id:
imgui.text("No class found in Database")
return
lectures = Lecture.select().where(Lecture.class_id == app_state.current_class_id)
if not lectures:
imgui.text(f"No Lectures found for {Class.get_by_id(app_state.current_class_id).name}")
return
if not hasattr(statics, "select"):
statics.select = 0
for n, lecture in enumerate(lectures, start=1):
display = f"{n}. {lecture.title}"
_, clicked = imgui.selectable(display, statics.select == n-1)
if clicked:
statics.select = n-1
app_state.current_lecture_id = lectures[statics.select].id
def class_list(app_state: AppState) -> None:
statics = class_list
if db.is_closed():
imgui.text("No Database loaded")
return
classes = Class.select() if db else None
if not classes:
imgui.text("No Classes currently in Database")
return
if not hasattr(statics, "select"):
statics.select = 0
for n, clas in enumerate(classes, start=1):
display = f"{n}. {clas.name}"
_, clicked = imgui.selectable(display, statics.select == n-1)
if clicked:
statics.select = n-1
app_state.current_class_id = classes[statics.select].id
def submissions_list(app_state: AppState) -> None:
statics = submissions_list
if not app_state.current_lecture_id:
imgui.text("No Lecture found")
return
if not app_state.current_student_id:
imgui.text("No Student found")
return
submissions = Submission.select().where(Submission.lecture_id == app_state.current_lecture_id and Submission.student_id == app_state.current_student_id)
if not submissions:
student = Student.get_by_id(app_state.current_student_id)
lecture = Lecture.get_by_id(app_state.current_lecture_id)
imgui.text(f"{student.prename} {student.surname} didn't submitted for {lecture.title}")
return
if not hasattr(statics, "select"):
statics.select = 0
for n, sub in enumerate(submissions, start=1):
lecture = Lecture.get_by_id(sub.lecture_id)
points = sub.points
if points.is_integer():
points = int(points)
display = f"{n}. {lecture.title} {points}/{lecture.points}"
_, clicked = imgui.selectable(display, statics.select == n-1)
if clicked:
statics.select = n-1
app_state.current_submission_id = submissions[statics.select].id
def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None:
if not data.size > 0:
imgui.text("No Data available")
return
name = hash(avg)
if avg.is_integer():
avg = int(avg)
avg = np.ones(len(data)) * avg
w, h = imgui.get_window_size()
implot.push_colormap(implot.Colormap_.hot.value)
if implot.begin_plot(f"Performance##{name}", ImVec2(-1, h*0.4), implot.Flags_.no_mouse_text.value | implot.Flags_.no_inputs.value):
implot.setup_axes("Lectures", "Percentage")
implot.setup_axes_limits(-1, len(data), 0, 110)
implot.push_style_var(implot.StyleVar_.fill_alpha.value, 0.6)
implot.push_style_var(implot.StyleVar_.line_weight.value, 3)
implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(labels), len(labels), [" " for _ in labels], False)
implot.plot_bars("Submissions", data)
implot.plot_line("Average", avg)
implot.push_style_color(implot.Col_.inlay_text, ImVec4(190,190,40,255)/255)
for x_pos, label in enumerate(labels):
y_pos = 50
implot.plot_text(label, x_pos, y_pos//2, ImVec2(0,0), implot.TextFlags_.vertical.value)
implot.pop_style_color()
implot.pop_style_var()
implot.end_plot()
COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")])
COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")])
COLOR_TEXT_PROJECT = tuple([e/255 for e in ImageColor.getcolor("#0A9CF5","RGBA")])
@immapp.static(inited=False)
def student_graph(app_state: AppState) -> None:
statics = student_graph
if not statics.inited:
statics.id = -1
statics.student = None
statics.group = None
statics.lectures = None
statics.points = None
statics.sub_points = None
statics.subs_data = None
statics.subs_labels = None
statics.max_points = None
statics.avg = None
statics.inited = True
if id != app_state.current_student_id:
statics.id = app_state.current_student_id
if not app_state.current_student_id:
imgui.text("No Students in Database")
return
statics.student = Student.get_by_id(app_state.current_student_id)
submissions = Submission.select().where(Submission.student_id == statics.student.id)
statics.group = Group.get_by_id(statics.student.group_id)
statics.lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions]
statics.max_points = np.sum([l.points for l in statics.lectures])
statics.sub_points = [sub.points for sub in submissions]
statics.points = np.sum(statics.sub_points)
if statics.points.is_integer():
statics.points = int(statics.points)
statics.subs_data = np.array([p/mp.points for p, mp in zip(statics.sub_points, statics.lectures)], dtype=np.float32)*100
statics.subs_labels = [f"{l.title} {int(points) if points.is_integer() else points}/{l.points}" for l, points in zip(statics.lectures, statics.sub_points)]
statics.avg = statics.points/statics.max_points*100
w, h = imgui.get_window_size()
imgui_md.render(f"# {statics.student.prename} {statics.student.surname} ({statics.group.name})")
imgui_md.render(f"### {statics.points}/{statics.max_points}")
imgui.text(" ")
imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}")
plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg)
imgui.separator()
imgui.text_colored(COLOR_TEXT_PROJECT, f"{statics.group.name}: {statics.group.project}")
for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1):
lecture, points = data
COLOR = COLOR_TEXT_PASSED if points >= lecture.points*0.3 else COLOR_TEXT_FAILED
imgui.text_colored(COLOR, f"{n}. {lecture.title}")
@immapp.static(inited=False)
def sex_graph(app_state: AppState) -> None:
statics = sex_graph
if db.is_closed():
imgui.text("No Database loaded")
return
if not statics.inited:
statics.max_points = None
statics.male_points = None
statics.female_points = None
statics.male_percentage = None
statics.female_percentage = None
statics.male_labels = None
statics.female_labels = None
statics.lectures = None
statics.class_id = -1
statics.state = 0
statics.inited = True
if not app_state.current_class_id:
imgui.text("No Class found")
return
if statics.class_id != app_state.current_class_id:
statics.class_id = app_state.current_class_id
lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
statics.lectures = lectures
statics.max_points = np.empty(len(lectures))
statics.male_points = np.empty(len(lectures))
statics.female_points = np.empty(len(lectures))
for n, lecture in enumerate(lectures):
statics.max_points[n] = lecture.points # Acc points
m_count = 0
f_count = 0
m_points = 0
f_points = 0
for sub in Submission.select().where(Submission.lecture_id == lecture.id):
if Student.get_by_id(sub.student_id).sex == 'Male':
m_points += sub.points
m_count += 1
else:
f_points += sub.points
f_count += 1
statics.male_points[n] = m_points/m_count/lecture.points
statics.female_points[n] = f_points/f_count/lecture.points
statics.male_percentage = np.sum(statics.male_points)/len(statics.male_points)
statics.female_percentage = np.sum(statics.female_points)/len(statics.female_points)
statics.male_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.male_points)]
statics.female_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.female_points)]
w, h = imgui.get_window_size()
if statics.state == 0:
imgui_md.render("# Male")
imgui.progress_bar(statics.male_percentage, ImVec2(w*0.9, h*0.05), f"{statics.male_percentage:.1%} n={len(Student.select().where(Student.sex == 'Male'))}")
plot_bar_line_percentage(statics.male_points*100, statics.male_labels, statics.male_percentage*100)
imgui_md.render("# Female")
imgui.progress_bar(statics.female_percentage, ImVec2(w*0.9, h*0.05), f"{statics.female_percentage:.1%} n={len(Student.select().where(Student.sex == 'Female'))}")
plot_bar_line_percentage(statics.female_points*100, statics.female_labels, statics.female_percentage*100)
if statics.state == 1:
male_xs = np.arange(1, len(statics.male_points)+1, dtype=np.float32)
male_ys = male_xs*0
male_zs = np.array(statics.male_points*100, dtype=np.float32)
female_xs = np.arange(1, len(statics.female_points)+1, dtype=np.float32)
female_ys = np.ones(len(statics.female_points), dtype=np.float32)
female_zs = np.array(statics.female_points*100, dtype=np.float32)
if implot3d.begin_plot("3D Gender Plot", ImVec2(w*0.9, h*0.9)):
implot3d.setup_axes("Lecture", "Gender", "Percentage")
implot3d.setup_box_scale(1.1, 1.1, 1.1)
implot3d.setup_axes_limits(0, len(statics.male_points)+1, -0.2, 1.2, 0, 110)
implot3d.set_next_marker_style(implot3d.Marker_.square.value)
implot3d.plot_line("Male", male_xs, male_ys, male_zs)
implot3d.set_next_marker_style(implot3d.Marker_.circle.value)
implot3d.plot_line("Female", female_xs, female_ys, female_zs)
for n, l in enumerate(statics.lectures, start=1):
implot3d.plot_text(l.title, n, np.clip(n/len(statics.lectures)), 50, np.pi/4)
implot3d.end_plot()
if imgui.button("Change 2D/3D"):
statics.state = not statics.state
COLOR_TEXT_FIRST = tuple([e/255 for e in ImageColor.getcolor("#FFCF40","RGBA")])
COLOR_TEXT_SECOND = tuple([e/255 for e in ImageColor.getcolor("#A5A9B4","RGBA")])
COLOR_TEXT_THIRD = tuple([e/255 for e in ImageColor.getcolor("#97564A","RGBA")])
COLOR_TEXT_ELSE = tuple([e/255 for e in ImageColor.getcolor("#85EBD9","RGBA")])
@immapp.static(inited=False)
def ranking(app_state: AppState) -> None:
statics = ranking
if not statics.inited:
statics.id = -1
statics.rank = None
statics.state = 1
statics.inited = True
imgui_md.render("# Student Ranking")
if not app_state.current_class_id:
imgui.text("No class selected")
return
if statics.id != app_state.current_class_id:
statics.id = app_state.current_class_id
students = Student.select().where(Student.class_id == statics.id)
max_points = np.sum([l.points for l in Lecture.select().where(Lecture.class_id == statics.id)])
statics.rank = list()
for student in students:
points = list()
for sub in Submission.select().where(Submission.student_id == student.id):
points.append(sub.points)
statics.rank.append((student, sum(points)/max_points))
statics.rank = sorted(statics.rank, key=lambda item: item[1], reverse=True)
if statics.state == 0:
for n, data in enumerate(statics.rank, start=1):
student, points = data
display = f"{n}. {student.prename} {student.surname} {points:.1%}"
COLOR = COLOR_TEXT_ELSE
if n == 1:
COLOR = COLOR_TEXT_FIRST
if n == 2:
COLOR = COLOR_TEXT_SECOND
if n == 3:
COLOR = COLOR_TEXT_THIRD
imgui.text_colored(COLOR, display)
if statics.state == 1:
w, h = imgui.get_window_size()
if implot.begin_plot("Ranking", ImVec2(w*0.9, h*0.9)):
implot.plot_bars("Ranking", np.array([p[1] for p in reversed(statics.rank)])*100, 0.67, 0, implot.BarsFlags_.horizontal.value)
for n, s in enumerate(reversed(statics.rank)):
student = s[0]
implot.plot_text(f"{student.prename} {student.surname}", s[1]*50, n)
implot.end_plot()
if imgui.button("Change"):
statics.state = not statics.state
def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]:
split_main_misc = hello_imgui.DockingSplit()
split_main_misc.initial_dock = "MainDockSpace"
split_main_misc.new_dock = "MiscSpace"
split_main_misc.direction = imgui.Dir.down
split_main_misc.ratio = 0.25
# Then, add a space to the left which occupies a column whose width is 25% of the app width
split_main_command = hello_imgui.DockingSplit()
split_main_command.initial_dock = "MainDockSpace"
split_main_command.new_dock = "CommandSpace"
split_main_command.direction = imgui.Dir.left
split_main_command.ratio = 0.2
# Then, add CommandSpace2 below MainDockSpace
split_main_command2 = hello_imgui.DockingSplit()
split_main_command2.initial_dock = "MainDockSpace"
split_main_command2.new_dock = "CommandSpace2"
split_main_command2.direction = imgui.Dir.down
split_main_command2.ratio = 0.25
splits = [split_main_misc, split_main_command, split_main_command2]
return splits
def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
student_selector = hello_imgui.DockableWindow()
student_selector.label = "Students"
student_selector.dock_space_name = "CommandSpace"
student_selector.gui_function = lambda: student_list(app_state)
group_selector = hello_imgui.DockableWindow()
group_selector.label = "Groups"
group_selector.dock_space_name = "CommandSpace"
group_selector.gui_function = lambda: group_list(app_state)
lecture_selector = hello_imgui.DockableWindow()
lecture_selector.label = "Lectures"
lecture_selector.dock_space_name = "CommandSpace2"
lecture_selector.gui_function = lambda: lecture_list(app_state)
class_selector = hello_imgui.DockableWindow()
class_selector.label = "Classes"
class_selector.dock_space_name = "CommandSpace2"
class_selector.gui_function = lambda: class_list(app_state)
submission_selector = hello_imgui.DockableWindow()
submission_selector.label = "Submissions"
submission_selector.dock_space_name = "CommandSpace"
submission_selector.gui_function = lambda: submissions_list(app_state)
student_info = hello_imgui.DockableWindow()
student_info.label = "Student Analyzer"
student_info.dock_space_name = "MainDockSpace"
student_info.gui_function = lambda: student_graph(app_state)
sex_info = hello_imgui.DockableWindow()
sex_info.label = "Analyze by Gender"
sex_info.dock_space_name = "MainDockSpace"
sex_info.gui_function = lambda: sex_graph(app_state)
student_ranking = hello_imgui.DockableWindow()
student_ranking.label = "Ranking"
student_ranking.dock_space_name = "MainDockSpace"
student_ranking.gui_function = lambda: ranking(app_state)
return [
class_selector, student_selector,
lecture_selector, submission_selector,
student_info, sex_info,
student_ranking, group_selector
]
def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams:
docking_params = hello_imgui.DockingParams()
docking_params.layout_name = "Analyzer"
docking_params.docking_splits = analyzer_docking_splits()
docking_params.dockable_windows = set_analyzer_layout(app_state)
return docking_params