Changed: Complete Overall

This commit is contained in:
DerGrumpf 2025-01-24 12:33:03 +01:00
parent 6f3605c450
commit c382aac84d
22 changed files with 844 additions and 1527 deletions

436
analyzer.py Normal file
View File

@ -0,0 +1,436 @@
# 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 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")])
@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.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.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}")
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()
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)
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
]
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

49
appstate.py Normal file
View File

@ -0,0 +1,49 @@
from imgui_bundle import hello_imgui
from model import *
from datetime import datetime
class AppState:
current_class_id: int
current_lecture_id: int
current_student_id: int
current_submission_id: int
def __init__(self):
self.current_class_id = None
self.current_lecture_id = None
self.current_student_id = None
self.current_submission_id = None
def update(self):
clas = Class.select()
self.current_class_id = clas[0].id if clas else None
lectures = Lecture.select().where(Lecture.class_id == self.current_class_id)
self.current_lecture_id = lectures[0].id if lectures else None
students = Student.select().where(Student.class_id == self.current_class_id)
self.current_student_id = students[0].id if students else None
submissions = Submission.select().where(Submission.lecture_id == self.current_lecture_id and Submission.student_id == self.current_student_id)
self.current_submission_id = submissions[0].id if submissions else None
LOG_DEBUG(f"Updated App State {repr(self)}")
def __repr__(self):
return f'''
Class ID: {self.current_class_id}
Lecture ID: {self.current_lecture_id}
Student ID: {self.current_student_id}
Submission ID: {self.current_submission_id}
'''
def log(log_level: hello_imgui.LogLevel, msg: str) -> None:
time = datetime.now().strftime("%X")
hello_imgui.log(log_level, f"[{time}] {msg}")
LOG_DEBUG = lambda msg: log(hello_imgui.LogLevel.debug, msg)
LOG_INFO = lambda msg: log(hello_imgui.LogLevel.info, msg)
LOG_WARNING = lambda msg: log(hello_imgui.LogLevel.warning, msg)
LOG_ERROR = lambda msg: log(hello_imgui.LogLevel.error, msg)

View File

@ -1,34 +1,35 @@
First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22 Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22,0,0
Marleen,Adolphi,Female,29.5,15,18,32,19,0,17,24 Marleen,Adolphi,Female,29.5,15,18,32,19,20,17,24,23,0
Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26 Sarina,Apel,Female,28.5,15,18,32,20,20,21,24,20,0
Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16 Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26,16,0
Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22 Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16,0,0
Nova,Eib,Female,31,15,15,34,20,20,21,27 Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22,12,0
Nele,Grundke,Female,23.5,13,16,28,20,17,21,18 Nova,Eib,Female,31,15,15,34,20,20,21,27,19,0
Anna,Grünewald,Female,12,14,16,29,16,15,19,9 Nele,Grundke,Female,23.5,13,16,28,20,17,21,18,22,0
Yannik,Haupt,Male,18,6,14,21,13,2,9,0 Anna,Grünewald,Female,12,14,16,29,16,15,19,9,0,0
Janna,Heiny,Female,30,15,18,33,18,20,22,25 Yannik,Haupt,Male,18,6,14,21,13,2,9,0,0,0
Milena,Krieger,Female,30,15,18,33,20,20,21.5,26 Janna,Heiny,Female,30,15,18,33,18,20,22,25,24,30
Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26 Milena,Krieger,Female,30,15,18,33,20,20,21.5,26,20,0
Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21 Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26,24,0
Manthey,Leonie,Female,28.5,14,18,29,20,10,18,23 Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21,21,0
Izabel,Mike,Female,29.5,15,15,35,11,4,19,21 Leonie,Manthey,Female,28.5,14,18,29,20,10,18,23,16,28
Lea,Noglik,Female,22.5,15,17,34,13,10,20,0 Izabel,Mike,Female,29.5,15,15,35,11,15,19,21,21,27
Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18 Lea,Noglik,Female,22.5,15,17,34,13,10,20,21,19,0
Julia,Renner,Female,27.5,10,14,0,20,17,11,20 Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18,19,6
Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22 Julia,Renner,Female,27.5,10,14,32,20,17,11,20,24,0
Natascha,Rott,Female,29.5,12,18,32,19,20,21,26 Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22,18,0
Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21 Natascha,Rott,Female,29.5,12,18,32,19,20,21,26,23,0
Melina,Sablotny,Female,31,15,18,33,20,20,21,19 Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21,16,0
Alea,Schleier,Female,27,14,18,34,16,18,21.5,22 Melina,Sablotny,Female,31,15,18,33,20,20,21,19,11,0
Flemming,Schur,Male,29.5,15,17,34,19,20,19,22 Alea,Schleier,Female,27,14,18,34,16,18,21.5,22,15,22
Marie,Seeger,Female,27.5,15,18,32,14,9,17,22 Flemming,Schur,Male,29.5,15,17,34,19,20,19,22,18,0
Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18 Marie,Seeger,Female,27.5,15,18,32,14,9,17,22,9,0
Lara,Troschke,Female,28.5,14,17,28,13,19,21,25 Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18,22,0
Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22 Lara,Troschke,Female,28.5,14,17,28,13,19,21,25,12,0
Alea,Unger,Female,30,12,18,31,20,20,21,22 Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22,17,0
Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24 Alea,Unger,Female,30,12,18,31,20,20,21,22,15,21.5
Katharina,Walz,Female,31,15,18,31,19,19,17,24 Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24,12,0
Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0 Katharina,Walz,Female,31,15,18,31,19,19,17,24,17,14.5
Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14 Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0,0,0
Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14,16,0

1 First Name Last Name Sex Tutorial 1 Tutorial 2 Extended Applications Numpy & MatPlotLib SciPy Monte Carlo Pandas & Seaborn Folium Statistical Test Methods Data Analysis
2 Abdalaziz Abunjaila Male 30.5 15 18 28 17 17 17 22 0 0
3 Marleen Adolphi Female 29.5 15 18 32 19 0 20 17 24 23 0
4 Skofiare Sarina Berisha Apel Female 29.5 28.5 13 15 18 34 32 20 17 20 20 21 26 24 20 0
5 Aurela Skofiare Brahimi Berisha Female 17.5 29.5 15 13 15.5 18 26 34 16 20 17 19 20 16 26 16 0
6 Cam Thu Aurela Do Brahimi Female 31 17.5 15 18 15.5 34 26 19 16 20 17 21.5 19 22 16 0 0
7 Nova Cam Thu Eib Do Female 31 15 15 18 34 20 19 20 21 21.5 27 22 12 0
8 Nele Nova Grundke Eib Female 23.5 31 13 15 16 15 28 34 20 17 20 21 18 27 19 0
9 Anna Nele Grünewald Grundke Female 12 23.5 14 13 16 29 28 16 20 15 17 19 21 9 18 22 0
10 Yannik Anna Haupt Grünewald Male Female 18 12 6 14 14 16 21 29 13 16 2 15 9 19 0 9 0 0
11 Janna Yannik Heiny Haupt Female Male 30 18 15 6 18 14 33 21 18 13 20 2 22 9 25 0 0 0
12 Milena Janna Krieger Heiny Female 30 15 18 33 20 18 20 21.5 22 26 25 24 30
13 Julia Milena Limbach Krieger Female 27.5 30 12 15 18 29 33 11 20 19 20 17.5 21.5 26 20 0
14 Viktoria Julia Litza Limbach Female 21.5 27.5 15 12 18 27 29 13 11 20 19 22 17.5 21 26 24 0
15 Manthey Viktoria Leonie Litza Female 28.5 21.5 14 15 18 29 27 20 13 10 20 18 22 23 21 21 0
16 Izabel Leonie Mike Manthey Female 29.5 28.5 15 14 15 18 35 29 11 20 4 10 19 18 21 23 16 28
17 Lea Izabel Noglik Mike Female 22.5 29.5 15 17 15 34 35 13 11 10 15 20 19 0 21 21 27
18 Donika Lea Nuhiu Noglik Female 31 22.5 13.5 15 18 17 35 34 14 13 10 17 20 18 21 19 0
19 Julia Donika Renner Nuhiu Female 27.5 31 10 13.5 14 18 0 35 20 14 17 10 11 17 20 18 19 6
20 Fabian Julia Rothberger Renner Male Female 30.5 27.5 15 10 18 14 34 32 17 20 17 19 11 22 20 24 0
21 Natascha Fabian Rott Rothberger Female Male 29.5 30.5 12 15 18 32 34 19 17 20 17 21 19 26 22 18 0
22 Isabel Natascha Rudolf Rott Female 27.5 29.5 9 12 17 18 34 32 16 19 19 20 19 21 21 26 23 0
23 Melina Isabel Sablotny Rudolf Female 31 27.5 15 9 18 17 33 34 20 16 20 19 21 19 19 21 16 0
24 Alea Melina Schleier Sablotny Female 27 31 14 15 18 34 33 16 20 18 20 21.5 21 22 19 11 0
25 Flemming Alea Schur Schleier Male Female 29.5 27 15 14 17 18 34 19 16 20 18 19 21.5 22 15 22
26 Marie Flemming Seeger Schur Female Male 27.5 29.5 15 18 17 32 34 14 19 9 20 17 19 22 18 0
27 Lucy Marie Thiele Seeger Female 27.5 15 18 27 32 20 14 17 9 19 17 18 22 9 0
28 Lara Lucy Troschke Thiele Female 28.5 27.5 14 15 17 18 28 27 13 20 19 17 21 19 25 18 22 0
29 Inga-Brit Lara Turschner Troschke Female 25.5 28.5 14 18 17 34 28 20 13 16 19 19 21 22 25 12 0
30 Alea Inga-Brit Unger Turschner Female 30 25.5 12 14 18 31 34 20 20 16 21 19 22 17 0
31 Marie Alea Wallbaum Unger Female 28.5 30 14 12 18 34 31 17 20 20 19 21 24 22 15 21.5
32 Katharina Marie Walz Wallbaum Female 31 28.5 15 14 18 31 34 19 17 19 20 17 19 24 12 0
33 Xiaowei Katharina Wang Walz Male Female 30.5 31 14 15 18 26 31 19 17 19 0 17 0 24 17 14.5
34 Lilly-Lu Xiaowei Warnken Wang Female Male 30 30.5 15 14 18 30 26 14 19 17 19 0 14 0 0 0
35 Lilly-Lu Warnken Female 30 15 18 30 14 17 19 14 16 0

Binary file not shown.

View File

@ -5,6 +5,7 @@ sys.path.append('..')
from model import * from model import *
df = pd.read_csv("Student_list.csv") df = pd.read_csv("Student_list.csv")
df = df.dropna()
courses = { courses = {
'Tutorial 1': 31, 'Tutorial 1': 31,
'Tutorial 2': 15, 'Tutorial 2': 15,
@ -13,9 +14,17 @@ courses = {
'SciPy': 20, 'SciPy': 20,
'Monte Carlo': 20, 'Monte Carlo': 20,
'Pandas & Seaborn': 22, 'Pandas & Seaborn': 22,
'Folium': 27 'Folium': 27,
'Statistical Test Methods': 24,
'Data Analysis': 30
} }
print(df)
db.init("WiSe_24_25.db")
db.connect()
db.create_tables([Class, Student, Lecture, Submission])
# Create Class # Create Class
clas = Class.create(name='WiSe 24/25') clas = Class.create(name='WiSe 24/25')
#print(clas.id) #print(clas.id)
@ -39,4 +48,4 @@ for index, row in df.iterrows():
lecture_id=Lecture.select().where(Lecture.title == title), lecture_id=Lecture.select().where(Lecture.title == title),
points=points points=points
) )

BIN
assets/icons/db_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,35 +0,0 @@
from imgui_bundle import imgui, imgui_ctx
from model import *
class ClassEditor:
def __init__(self):
super().__init__()
self.add_name = str()
self.select = 0
def __call__(self):
classes = Class.select()
with imgui_ctx.begin("Class Editor"):
imgui.text("Add Class")
_, self.add_name = imgui.input_text(" ", self.add_name)
if imgui.button("Add"):
if self.add_name:
Class.create(name=self.add_name)
self.add_name = str()
imgui.separator()
if not classes:
imgui.text("No Dataset could be queried")
return
for n, c in enumerate(classes, start=1):
display = f"{n}. {c.name}"
opened, _ = imgui.selectable(display, self.select == n-1)
if opened:
self.select = n-1
return classes[self.select]

223
database.py Normal file
View File

@ -0,0 +1,223 @@
# Custom
from model import *
from appstate import *
# External
from imgui_bundle import (
imgui,
imgui_ctx,
immapp,
imgui_md,
im_file_dialog,
hello_imgui
)
# Built In
from typing import List
import shelve
from pathlib import Path
from datetime import datetime
def file_info(path: Path) -> None:
stat = path.stat()
modified = datetime.fromtimestamp(stat.st_atime)
created = datetime.fromtimestamp(stat.st_ctime)
format = '%c'
data = {
"File": path.name,
"Size": f"{stat.st_size/100} KB",
"Modified": modified.strftime(format),
"Created": created.strftime(format)
}
if imgui.begin_table("File Info", 2):
imgui.table_setup_column(" ", 0)
imgui.table_setup_column(" ")
for k, v in data.items():
imgui.push_id(k)
imgui.table_next_row()
imgui.table_next_column()
imgui.text(k)
imgui.table_next_column()
imgui.text(v)
imgui.pop_id()
imgui.end_table()
@immapp.static(inited=False, res=False)
def select_file(app_state: AppState):
statics = select_file
if not statics.inited:
statics.res = None
statics.current = None
with shelve.open("state") as state:
statics.current = Path(state["DB"])
statics.inited = True
imgui_md.render("# Database Manager")
file_info(statics.current)
if imgui.button("Open File"):
im_file_dialog.FileDialog.instance().open("SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}")
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
if im_file_dialog.FileDialog.instance().has_result():
statics.res = im_file_dialog.FileDialog.instance().get_result()
LOG_INFO(f"Load File {statics.res}")
im_file_dialog.FileDialog.instance().close()
if statics.res:
filename = statics.res.filename()
info = Path(statics.res.path())
imgui.separator()
file_info(info)
file = None
if imgui.button("Load"):
if not db.is_closed():
db.close()
if statics.res.extension() == '.json':
file = filename.removesuffix('.json')
file = file + '.db'
db.init(file)
db.connect(reuse_if_open=True)
db.create_tables([Class, Student, Lecture, Submission])
load_from_json(str(info))
LOG_INFO(f"Successfully created {file}")
if statics.res.extension() == '.db':
file = str(statics.res.path())
db.init(file)
db.connect(reuse_if_open=True)
db.create_tables([Class, Student, Lecture, Submission])
LOG_INFO(f"Successfully loaded {filename}")
with shelve.open("state") as state:
state["DB"] = file
app_state.update()
statics.res = None
@immapp.static(inited=False)
def table(app_state: AppState) -> None:
statics = table
if not statics.inited:
statics.class_id = None
statics.lectures = None
statics.points = list()
statics.inited = True
if statics.class_id != app_state.current_class_id:
statics.class_id = app_state.current_class_id
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
statics.data = dict()
for student in Student.select().where(Student.class_id == statics.class_id):
subs = Submission.select().where(Submission.student_id == student.id)
points = [sub.points for sub in subs]
statics.data[f"{student.prename} {student.surname}"] = points
if not statics.lectures:
imgui.text("No Lecture queried")
return
table_flags = (
imgui.TableFlags_.row_bg.value
| imgui.TableFlags_.borders.value
| imgui.TableFlags_.resizable.value
| imgui.TableFlags_.sizing_stretch_same.value
)
if imgui.begin_table("Overview", len(statics.lectures)+1, table_flags):
imgui.table_setup_column("Students")
for n, lecture in enumerate(statics.lectures, start=1):
imgui.table_setup_column(f"{n}. {lecture.title} ({lecture.points})")
imgui.table_setup_scroll_freeze(1, 1)
imgui.table_headers_row()
for k, v in statics.data.items():
imgui.push_id(k)
imgui.table_next_row()
imgui.table_next_column()
imgui.text(k)
for points in v:
imgui.table_next_column()
if points.is_integer():
points = int(points)
imgui.text(str(points))
imgui.pop_id()
imgui.end_table()
@immapp.static(inited=False)
def class_editor() -> None:
statics = class_editor
if not statics.inited:
statics.classes = None
statics.selected = 0
statics.inited = True
statics.classes = Class.select()
imgui_md.render("# Edit Classes")
_, statics.selected = imgui.combo("Classes", statics.selected, [c.name for c in statics.classes])
imgui.text(statics.classes[statics.selected].name)
def database_editor(app_state: AppState) -> None:
class_editor()
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
split_main_command = hello_imgui.DockingSplit()
split_main_command.initial_dock = "MainDockSpace"
split_main_command.new_dock = "CommandSpace"
split_main_command.direction = imgui.Dir.down
split_main_command.ratio = 0.3
# Log Space
split_main_command2 = hello_imgui.DockingSplit()
split_main_command2.initial_dock = "CommandSpace"
split_main_command2.new_dock = "CommandSpace2"
split_main_command2.direction = imgui.Dir.right
split_main_command2.ratio = 0.3
split_main_misc = hello_imgui.DockingSplit()
split_main_misc.initial_dock = "MainDockSpace"
split_main_misc.new_dock = "MiscSpace"
split_main_misc.direction = imgui.Dir.left
split_main_misc.ratio = 0.2
splits = [split_main_misc, split_main_command, split_main_command2]
return splits
def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
file_dialog = hello_imgui.DockableWindow()
file_dialog.label = "Database"
file_dialog.dock_space_name = "MiscSpace"
file_dialog.gui_function = lambda: select_file(app_state)
log = hello_imgui.DockableWindow()
log.label = "Logs"
log.dock_space_name = "CommandSpace2"
log.gui_function = hello_imgui.log_gui
table_view = hello_imgui.DockableWindow()
table_view.label = "Table"
table_view.dock_space_name = "MainDockSpace"
table_view.gui_function = lambda: table(app_state)
editor = hello_imgui.DockableWindow()
editor.label = "Editor"
editor.dock_space_name = "CommandSpace"
editor.gui_function = lambda: database_editor(app_state)
return [
file_dialog, log, table_view, editor
]
def database_editor_layout(app_state: AppState) -> hello_imgui.DockingParams:
docking_params = hello_imgui.DockingParams()
docking_params.layout_name = "Database Editor"
docking_params.docking_splits = database_docking_splits()
docking_params.dockable_windows = set_database_editor_layout(app_state)
return docking_params

View File

@ -1,15 +0,0 @@
from PIL import ImageColor
from enum import IntEnum
# Global Color Pallet
COLOR_BACKGROUND = tuple([e/255 for e in ImageColor.getcolor("#29132E","RGBA")])
COLOR_1 = tuple([e/255 for e in ImageColor.getcolor("#321450","RGBA")])
COLOR_2 = tuple([e/255 for e in ImageColor.getcolor("#860029","RGBA")])
COLOR_3 = tuple([e/255 for e in ImageColor.getcolor("#DE004E","RGBA")])
COLOR_TEXT = tuple([e/255 for e in ImageColor.getcolor("#F887FF","RGBA")])
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")])
class LayoutOptions(IntEnum):
EDITOR = 1
GRAPHER = 2

View File

@ -1,926 +0,0 @@
# A more complex app demo
#
# It demonstrates how to:
# - set up a complex docking layouts (with several possible layouts):
# - load additional fonts, possibly colored, and with emojis
# - display a log window
# - use the status bar
# - use default menus (App and view menu), and how to customize them
# - use a specific application state (instead of using static variables)
# - save some additional user settings within imgui ini file
# - use borderless windows, that are movable and resizable
import json
from enum import Enum
import time
from imgui_bundle import hello_imgui, icons_fontawesome_6, imgui, immapp, imgui_ctx, ImVec4, ImVec2
from imgui_bundle.demos_python import demo_utils
from typing import List, Any
##########################################################################
# Our Application State
##########################################################################
class MyAppSettings:
motto: hello_imgui.InputTextData
value: int = 10
def __init__(self):
self.motto = hello_imgui.InputTextData(
"Hello, Dear ImGui\n"
"Unleash your creativity!\n",
True, # multiline
(14.0, 3.0) # initial size (in em)
)
class RocketState(Enum):
Init = 0
Preparing = 1
Launched = 2
# Struct that holds the application's state
class AppState:
f: float
counter: int
rocket_progress: float
my_app_settings: MyAppSettings
rocket_state: RocketState
rocket_launch_time: float
title_font: imgui.ImFont
color_font: imgui.ImFont
emoji_font: imgui.ImFont
large_icon_font: imgui.ImFont
def __init__(self):
self.f = 0
self.counter = 0
self.rocket_progress = 0.0
self.rocket_launch_time = 0.0
self.my_app_settings = MyAppSettings()
self.rocket_state = RocketState.Init
##########################################################################
# Additional fonts handling
##########################################################################
def load_fonts(app_state: AppState): # This is called by runnerParams.callbacks.LoadAdditionalFonts
# First, load the default font (the default font should be loaded first)
# In this example, we instruct HelloImGui to use FontAwesome6 instead of FontAwesome4
hello_imgui.get_runner_params().callbacks.default_icon_font = hello_imgui.DefaultIconFont.font_awesome6
hello_imgui.imgui_default_settings.load_default_font_with_font_awesome_icons()
# Load the title font
# app_state.title_font = hello_imgui.load_font("fonts/DroidSans.ttf", 18.0)
font_loading_params_title_icons = hello_imgui.FontLoadingParams()
font_loading_params_title_icons.merge_font_awesome = True
app_state.title_font = hello_imgui.load_font("fonts/Roboto/Roboto-BoldItalic.ttf", 18, font_loading_params_title_icons)
# Load the emoji font
font_loading_params_emoji = hello_imgui.FontLoadingParams()
font_loading_params_emoji.use_full_glyph_range = True
app_state.emoji_font = hello_imgui.load_font("fonts/NotoEmoji-Regular.ttf", 24., font_loading_params_emoji)
# Load a large icon font
font_loading_params_large_icon = hello_imgui.FontLoadingParams()
font_loading_params_large_icon.use_full_glyph_range = True
app_state.large_icon_font = hello_imgui.load_font("fonts/fontawesome-webfont.ttf", 24., font_loading_params_large_icon)
# Load a colored font
font_loading_params_color = hello_imgui.FontLoadingParams()
font_loading_params_color.load_color = True
app_state.color_font = hello_imgui.load_font("fonts/Playbox/Playbox-FREE.otf", 24., font_loading_params_color)
##########################################################################
# Save additional settings in the ini file
##########################################################################
# This demonstrates how to store additional info in the application settings
# Use this sparingly!
# This is provided as a convenience only, and it is not intended to store large quantities of text data.
# Warning, the save/load function below are quite simplistic!
def my_app_settings_to_string(settings: MyAppSettings) -> str:
as_dict: dict[str, Any] = {}
as_dict["motto"] = hello_imgui.input_text_data_to_dict(settings.motto)
as_dict["value"] = settings.value
return json.dumps(as_dict)
def string_to_my_app_settings(s: str) -> MyAppSettings:
r = MyAppSettings()
try:
as_dict = json.loads(s)
r.motto = hello_imgui.input_text_data_from_dict(as_dict["motto"])
r.value = as_dict["value"]
except Exception as e:
hello_imgui.log(hello_imgui.LogLevel.error, f"Error while loading user settings: {e}")
return r
def load_my_app_settings(app_state: AppState):
"""
Note: load_my_app_settings() and save_my_app_settings() will be called in the callbacks `post_init` & `before_exit`
runner_params.callbacks.post_init = lambda: load_user_settings(app_state)
runner_params.callbacks.before_exit = lambda: save_user_settings(app_state)
"""
app_state.my_app_settings = string_to_my_app_settings(
hello_imgui.load_user_pref("MyAppSettings")
)
def save_my_app_settings(app_state: AppState):
hello_imgui.save_user_pref(
"MyAppSettings", my_app_settings_to_string(app_state.my_app_settings)
)
##########################################################################
# Gui functions used in this demo
##########################################################################
@immapp.static(last_hide_time=1)
def demo_hide_window(app_state: AppState):
# Display a button that will hide the application window
imgui.push_font(app_state.title_font)
imgui.text("Hide app window")
imgui.pop_font()
if imgui.button("Hide"):
demo_hide_window.last_hide_time = time.time()
hello_imgui.get_runner_params().app_window_params.hidden = True
if imgui.is_item_hovered():
imgui.set_tooltip("By clicking this button, you can hide the window for 3 seconds.")
if demo_hide_window.last_hide_time > 0.0:
now = time.time()
if now - demo_hide_window.last_hide_time > 3.0:
demo_hide_window.last_hide_time = -1.0
hello_imgui.get_runner_params().app_window_params.hidden = False
# Display a button that will add another dockable window during execution
def demo_show_additional_window(app_state: AppState):
# In order to add a dockable window during execution, you should use
# hello_imgui.add_dockable_window()
# Note: you should not modify manually the content of runnerParams.docking_params.dockable_windows
# (since HelloImGui is constantly looping on it)
imgui.push_font(app_state.title_font)
imgui.text("Dynamically add window")
imgui.pop_font()
window_name = "Additional Window"
if imgui.button("Show additional window"):
additional_window = hello_imgui.DockableWindow()
additional_window.label = window_name
additional_window.include_in_view_menu = False # this window is not shown in the view menu,
additional_window.remember_is_visible = False # its visibility is not saved in the settings file,
additional_window.dock_space_name = "MiscSpace" # when shown, it will appear in MiscSpace.
additional_window.gui_function = lambda: imgui.text("This is the additional window")
hello_imgui.add_dockable_window(
additional_window,
force_dockspace=False # means that the window will be docked to the last space it was docked to
# i.e. dock_space_name is ignored if the user previously moved the window to another space
)
imgui.set_item_tooltip("By clicking this button, you can show an additional window")
if imgui.button("Remove additional window"):
hello_imgui.remove_dockable_window(window_name)
imgui.set_item_tooltip("By clicking this button, you can remove the additional window")
def demo_basic_widgets(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Basic widgets demo")
imgui.pop_font()
imgui.begin_group()
# Edit a float using a slider from 0.0 to 1.0
changed, app_state.f = imgui.slider_float("float", app_state.f, 0.0, 1.0)
if changed:
hello_imgui.log(
hello_imgui.LogLevel.warning, f"state.f was changed to {app_state.f}"
)
# Buttons return true when clicked (most widgets return true when edited/activated)
if imgui.button("Button"):
app_state.counter += 1
hello_imgui.log(hello_imgui.LogLevel.info, "Button was pressed")
imgui.same_line()
imgui.text(f"counter = {app_state.counter}")
imgui.end_group()
if imgui.is_item_hovered():
imgui.set_tooltip("These widgets will interact with the log window")
def demo_user_settings(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("User settings")
imgui.pop_font()
imgui.begin_group()
imgui.set_next_item_width(hello_imgui.em_size(7.0))
_, app_state.my_app_settings.value = imgui.slider_int(
"Value", app_state.my_app_settings.value, 0, 100
)
_ = hello_imgui.input_text_resizable("Motto", app_state.my_app_settings.motto)
imgui.text("(this text widget is resizable)")
imgui.end_group()
if imgui.is_item_hovered():
imgui.set_tooltip("The values below are stored in the application settings ini file and restored at startup")
def demo_rocket(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Rocket demo")
imgui.pop_font()
imgui.begin_group()
if app_state.rocket_state == RocketState.Init:
if imgui.button(f"{icons_fontawesome_6.ICON_FA_ROCKET} Launch rocket"):
app_state.rocket_launch_time = time.time()
app_state.rocket_state = RocketState.Preparing
hello_imgui.log(hello_imgui.LogLevel.warning, "Rocket is being prepared")
elif app_state.rocket_state == RocketState.Preparing:
imgui.text("Please Wait")
app_state.rocket_progress = (time.time() - app_state.rocket_launch_time) / 3.0
if app_state.rocket_progress >= 1.0:
app_state.rocket_state = RocketState.Launched
hello_imgui.log(hello_imgui.LogLevel.warning, "Rocket was launched")
elif app_state.rocket_state == RocketState.Launched:
imgui.text(f"{icons_fontawesome_6.ICON_FA_ROCKET} Rocket launched")
if imgui.button("Reset Rocket"):
app_state.rocket_state = RocketState.Init
app_state.rocket_progress = 0.0
imgui.end_group()
if imgui.is_item_hovered():
imgui.set_tooltip("Look at the status bar after clicking")
def demo_docking_flags(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Main dock space node flags")
imgui.pop_font()
imgui.text_wrapped(
"""
This will edit the ImGuiDockNodeFlags for "MainDockSpace".
Most flags are inherited by children dock spaces.
"""
)
class DockFlagWithInfo:
def __init__(self, flag, label, tip):
self.flag = flag
self.label = label
self.tip = tip
all_flags = [
DockFlagWithInfo(
imgui.DockNodeFlags_.no_docking_split,
"NoSplit",
"prevent Dock Nodes from being split",
),
DockFlagWithInfo(
imgui.DockNodeFlags_.no_resize,
"NoResize",
"prevent Dock Nodes from being resized",
),
DockFlagWithInfo(
imgui.DockNodeFlags_.auto_hide_tab_bar,
"AutoHideTabBar",
"show tab bar only if multiple windows\n"
+ 'You will need to restore the layout after changing (Menu "View/Restore Layout")',
),
DockFlagWithInfo(
imgui.DockNodeFlags_.no_docking_over_central_node,
"NoDockingInCentralNode",
"prevent docking in central node\n(only works with the main dock space)",
),
# DockFlagWithInfo(imgui.DockNodeFlags_.passthru_central_node, "PassthruCentralNode", "advanced"),
]
main_dock_space_node_flags = (
hello_imgui.get_runner_params().docking_params.main_dock_space_node_flags
)
for flag_with_info in all_flags:
_, main_dock_space_node_flags = imgui.checkbox_flags(
flag_with_info.label, main_dock_space_node_flags, flag_with_info.flag
)
if imgui.is_item_hovered():
imgui.set_tooltip("%s" % flag_with_info.tip)
hello_imgui.get_runner_params().docking_params.main_dock_space_node_flags = (
main_dock_space_node_flags
)
def gui_window_layout_customization(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Switch between layouts")
imgui.pop_font()
imgui.text('with the menu "View/Layouts"')
if imgui.is_item_hovered():
imgui.set_tooltip(
"Each layout remembers separately the modifications applied by the user, \n"
+ "and the selected layout is restored at startup"
)
imgui.separator()
imgui.push_font(app_state.title_font)
imgui.text("Change the theme")
imgui.pop_font()
imgui.text('with the menu "View/Theme"')
if imgui.is_item_hovered():
imgui.set_tooltip("The selected theme is remembered and restored at startup")
imgui.separator()
demo_docking_flags(app_state)
imgui.separator()
def gui_window_alternative_theme(app_state: AppState):
# Since this window applies a theme, We need to call "imgui.begin" ourselves so
# that we can apply the theme before opening the window.
#
# In order to obtain this, we applied the following option to the window
# that displays this Gui:
# alternative_theme_window.call_begin_end = False
# emulate C/C++ static variable: we will store some static variables
# as attributes of the function
statics = gui_window_alternative_theme
# Apply the theme before opening the window
tweaked_theme = hello_imgui.ImGuiTweakedTheme()
tweaked_theme.theme = hello_imgui.ImGuiTheme_.white_is_white
tweaked_theme.tweaks.rounding = 0.0
hello_imgui.push_tweaked_theme(tweaked_theme)
# Open the window
window_opened = imgui.begin("Alternative Theme")
if window_opened:
# Display some widgets
imgui.push_font(app_state.title_font)
imgui.text("Alternative Theme")
imgui.pop_font()
imgui.text("This window uses a different theme")
imgui.set_item_tooltip("""
tweaked_theme = hello_imgui.ImGuiTheme.ImGuiTweakedTheme()
tweaked_theme.theme = hello_imgui.ImGuiTheme_.white_is_white.value
tweaked_theme.tweaks.rounding = 0.0
hello_imgui.apply_tweaked_theme(tweaked_theme)
"""
)
if imgui.collapsing_header("Basic Widgets", imgui.TreeNodeFlags_.default_open.value):
if not hasattr(statics, "checked"):
statics.checked = True
_, statics.checked = imgui.checkbox("Checkbox", statics.checked)
if imgui.button("Button"):
hello_imgui.log(hello_imgui.LogLevel.info, "Button was pressed")
imgui.set_item_tooltip("This is a button")
if not hasattr(statics, "radio"):
statics.radio = 0
if imgui.radio_button("Radio 1", statics.radio == 0):
statics.radio = 0
imgui.same_line()
if imgui.radio_button("Radio 2", statics.radio == 1):
statics.radio = 1
imgui.same_line()
if imgui.radio_button("Radio 3", statics.radio == 2):
statics.radio = 2
# Haiku
# Display a image of the haiku below with Japanese characters
# with an informative tooltip
haiku_image_height = hello_imgui.em_size(5.0)
hello_imgui.image_from_asset("images/haiku.png", (0.0, haiku_image_height))
imgui.set_item_tooltip("""
Extract from Wikipedia
-------------------------------------------------------------------------------
In early 1686, Bashō composed one of his best-remembered haiku:
furu ike ya / kawazu tobikomu / mizu no oto
an ancient pond / a frog jumps in / the splash of water
This poem became instantly famous.
-------------------------------------------------------------------------------
This haiku is here rendered as an image, mainly to preserve space,
because adding a Japanese font to the project would enlarge its size.
Handling Japanese font is of course possible within ImGui / Hello ImGui!
""")
# Display the haiku text as an InputTextMultiline
if not hasattr(statics, "poem"):
statics.poem = (
" Old Pond\n"
" Frog Leaps In\n"
" Water's Sound\n"
"\n"
" Matsuo Bashō - 1686"
)
_, statics.poem = imgui.input_text_multiline("##Poem", statics.poem, hello_imgui.em_to_vec2(15.0, 5.5))
# a popup with a modal window
if imgui.button("Open Modal"):
imgui.open_popup("MyModal")
popup_opened, _ = imgui.begin_popup_modal("MyModal", None, imgui.WindowFlags_.always_auto_resize.value)
if popup_opened:
imgui.text("This is a modal window")
if imgui.button("Close"):
imgui.close_current_popup()
imgui.end_popup()
if not hasattr(statics, "text"):
statics.text = "Hello, world!"
_, statics.text = imgui.input_text("Input text", statics.text)
if imgui.tree_node("Text Display"):
imgui.text("Hello, world!")
imgui.text_colored((1.0, 0.5, 0.5, 1.0), "Some text")
imgui.text_disabled("Disabled text")
imgui.text_wrapped("This is a long text that will be wrapped in the window")
imgui.tree_pop()
# Close the window
imgui.end()
# Restore the theme
hello_imgui.pop_tweaked_theme()
def demo_assets(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Image From Assets")
imgui.pop_font()
hello_imgui.begin_group_column()
imgui.dummy(hello_imgui.em_to_vec2(0.0, 0.45))
imgui.text("Hello")
hello_imgui.end_group_column()
hello_imgui.image_from_asset("images/world.png", hello_imgui.em_to_vec2(2.5, 2.5))
def demo_fonts(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Fonts - " + icons_fontawesome_6.ICON_FA_ROCKET)
imgui.pop_font()
imgui.text_wrapped("Mix icons " + icons_fontawesome_6.ICON_FA_FACE_SMILE + " and text " + icons_fontawesome_6.ICON_FA_ROCKET)
if imgui.is_item_hovered():
imgui.set_tooltip("Example with Font Awesome Icons")
imgui.text("Emojis")
with imgui_ctx.begin_group():
imgui.push_font(app_state.emoji_font)
imgui.text("✌❤🌴🚀")
imgui.pop_font()
if imgui.is_item_hovered():
imgui.set_tooltip("Example with NotoEmoji font")
imgui.text("Colored Fonts")
imgui.push_font(app_state.color_font)
imgui.text("COLOR!")
imgui.pop_font()
if imgui.is_item_hovered():
imgui.set_tooltip("Example with Playbox-FREE.otf font")
def demo_themes(app_state: AppState):
imgui.push_font(app_state.title_font)
imgui.text("Themes")
imgui.pop_font()
tweaked_theme = hello_imgui.get_runner_params().imgui_window_params.tweaked_theme
imgui.begin_group()
button_size = hello_imgui.em_to_vec2(7.0, 0.0)
if imgui.button("Cherry", button_size):
tweaked_theme.theme = hello_imgui.ImGuiTheme_.cherry
hello_imgui.apply_tweaked_theme(tweaked_theme)
if imgui.button("DarculaDarker", button_size):
tweaked_theme.theme = hello_imgui.ImGuiTheme_.darcula_darker
hello_imgui.apply_tweaked_theme(tweaked_theme)
imgui.end_group()
if imgui.is_item_hovered():
imgui.set_tooltip(
"There are lots of other themes: look at the menu View/Theme\n"
"The selected theme is remembered and restored at startup"
)
def gui_window_demo_features(app_state: AppState):
demo_fonts(app_state)
imgui.separator()
demo_assets(app_state)
imgui.separator()
demo_basic_widgets(app_state)
imgui.separator()
demo_rocket(app_state)
imgui.separator()
demo_user_settings(app_state)
imgui.separator()
demo_hide_window(app_state)
imgui.separator()
demo_show_additional_window(app_state)
imgui.separator()
demo_themes(app_state)
imgui.separator()
def status_bar_gui(app_state: AppState):
if app_state.rocket_state == RocketState.Preparing:
imgui.text("Rocket completion: ")
imgui.same_line()
imgui.progress_bar(app_state.rocket_progress, hello_imgui.em_to_vec2(7.0, 1.0)) # type: ignore
def show_menu_gui(runner_params: hello_imgui.RunnerParams):
hello_imgui.show_app_menu(runner_params)
hello_imgui.show_view_menu(runner_params)
if imgui.begin_menu("My Menu"):
clicked, _ = imgui.menu_item("Test me", "", False)
if clicked:
hello_imgui.log(hello_imgui.LogLevel.warning, "It works")
imgui.end_menu()
def show_app_menu_items():
clicked, _ = imgui.menu_item("A Custom app menu item", "", False)
if clicked:
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on A Custom app menu item")
def show_top_toolbar(app_state: AppState):
imgui.push_font(app_state.large_icon_font)
if imgui.button(icons_fontawesome_6.ICON_FA_POWER_OFF):
hello_imgui.get_runner_params().app_shall_exit = True
imgui.same_line(imgui.get_window_width() - hello_imgui.em_size(7.0))
if imgui.button(icons_fontawesome_6.ICON_FA_HOUSE):
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Home in the top toolbar")
imgui.same_line()
if imgui.button(icons_fontawesome_6.ICON_FA_FLOPPY_DISK):
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Save in the top toolbar")
imgui.same_line()
if imgui.button(icons_fontawesome_6.ICON_FA_ADDRESS_BOOK):
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Address Book in the top toolbar")
imgui.same_line(imgui.get_window_width() - hello_imgui.em_size(2.0))
imgui.text(icons_fontawesome_6.ICON_FA_BATTERY_THREE_QUARTERS)
imgui.pop_font()
def show_right_toolbar(app_state: AppState):
imgui.push_font(app_state.large_icon_font)
if imgui.button(icons_fontawesome_6.ICON_FA_CIRCLE_ARROW_LEFT):
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Circle left in the right toolbar")
if imgui.button(icons_fontawesome_6.ICON_FA_CIRCLE_ARROW_RIGHT):
hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Circle right in the right toolbar")
imgui.pop_font()
##########################################################################
# Docking Layouts and Docking windows
##########################################################################
#
# 1. Define the Docking splits (two versions are available)
#
def create_default_docking_splits() -> List[hello_imgui.DockingSplit]:
# Define the default docking splits,
# i.e. the way the screen space is split in different target zones for the dockable windows
# We want to split "MainDockSpace" (which is provided automatically) into three zones, like this:
#
# ___________________________________________
# | | |
# | Command| |
# | Space | MainDockSpace |
# |------- | |
# | |--------------------------------|
# | | CommandSpace2 |
# -------------------------------------------
# | MiscSpace |
# -------------------------------------------
#
# Uncomment the next line if you want to always start with this layout.
# Otherwise, modifications to the layout applied by the user layout will be remembered.
# runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.ApplicationStart
# Then, add a space named "MiscSpace" whose height is 25% of the app height.
# This will split the preexisting default dockspace "MainDockSpace" in two parts.
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.25
# 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.5
splits = [split_main_misc, split_main_command, split_main_command2]
return splits
def create_alternative_docking_splits() -> List[hello_imgui.DockingSplit]:
# Define alternative docking splits for the "Alternative Layout"
# ___________________________________________
# | | |
# | Misc | |
# | Space | MainDockSpace |
# | | |
# -------------------------------------------
# | | |
# | | Command |
# | CommandSpace | Space2 |
# -------------------------------------------
split_main_command = hello_imgui.DockingSplit()
split_main_command.initial_dock = "MainDockSpace"
split_main_command.new_dock = "CommandSpace"
split_main_command.direction = imgui.Dir.down
split_main_command.ratio = 0.5
split_main_command2 = hello_imgui.DockingSplit()
split_main_command2.initial_dock = "CommandSpace"
split_main_command2.new_dock = "CommandSpace2"
split_main_command2.direction = imgui.Dir.right
split_main_command2.ratio = 0.4
split_main_misc = hello_imgui.DockingSplit()
split_main_misc.initial_dock = "MainDockSpace"
split_main_misc.new_dock = "MiscSpace"
split_main_misc.direction = imgui.Dir.left
split_main_misc.ratio = 0.5
splits = [split_main_command, split_main_command2, split_main_misc]
return splits
#
# 2. Define the Dockable windows
#
def create_dockable_windows(app_state: AppState) -> List[hello_imgui.DockableWindow]:
# A features demo window named "FeaturesDemo" will be placed in "CommandSpace".
# Its Gui is provided by "gui_window_demo_features"
features_demo_window = hello_imgui.DockableWindow()
features_demo_window.label = "Features Demo"
features_demo_window.dock_space_name = "CommandSpace"
features_demo_window.gui_function = lambda: gui_window_demo_features(app_state)
# A layout customization window will be placed in "MainDockSpace".
# Its Gui is provided by "gui_window_layout_customization"
layout_customization_window = hello_imgui.DockableWindow()
layout_customization_window.label = "Layout customization"
layout_customization_window.dock_space_name = "MainDockSpace"
layout_customization_window.gui_function = lambda: gui_window_layout_customization(app_state)
# A Log window named "Logs" will be placed in "MiscSpace". It uses the HelloImGui logger gui
logs_window = hello_imgui.DockableWindow()
logs_window.label = "Logs"
logs_window.dock_space_name = "MiscSpace"
logs_window.gui_function = hello_imgui.log_gui
# A Window named "Dear ImGui Demo" will be placed in "MainDockSpace"
dear_imgui_demo_window = hello_imgui.DockableWindow()
dear_imgui_demo_window.label = "Dear ImGui Demo"
dear_imgui_demo_window.dock_space_name = "MainDockSpace"
dear_imgui_demo_window.imgui_window_flags = imgui.WindowFlags_.menu_bar.value
dear_imgui_demo_window.gui_function = imgui.show_demo_window # type: ignore
# alternativeThemeWindow
alternative_theme_window = hello_imgui.DockableWindow()
# Since this window applies a theme, We need to call "imgui.begin" ourselves so
# that we can apply the theme before opening the window.
alternative_theme_window.call_begin_end = False
alternative_theme_window.label = "Alternative Theme"
alternative_theme_window.dock_space_name = "CommandSpace2"
alternative_theme_window.gui_function = lambda: gui_window_alternative_theme(app_state)
dockable_windows = [
features_demo_window,
layout_customization_window,
logs_window,
dear_imgui_demo_window,
alternative_theme_window,
]
return dockable_windows
#
# 3. Define the layouts:
# A layout is stored inside DockingParams, and stores the splits + the dockable windows.
# Here, we provide the default layout, and two alternative layouts.
def create_default_layout(app_state: AppState) -> hello_imgui.DockingParams:
docking_params = hello_imgui.DockingParams()
# By default, the layout name is already "Default"
# docking_params.layout_name = "Default"
docking_params.docking_splits = create_default_docking_splits()
docking_params.dockable_windows = create_dockable_windows(app_state)
return docking_params
def create_alternative_layouts(app_state: AppState) -> List[hello_imgui.DockingParams]:
alternative_layout = hello_imgui.DockingParams()
alternative_layout.layout_name = "Alternative Layout"
alternative_layout.docking_splits = create_alternative_docking_splits()
alternative_layout.dockable_windows = create_dockable_windows(app_state)
tabs_layout = hello_imgui.DockingParams()
tabs_layout.layout_name = "Tabs Layout"
tabs_layout.dockable_windows = create_dockable_windows(app_state)
# Force all windows to be presented in the MainDockSpace
for window in tabs_layout.dockable_windows:
window.dock_space_name = "MainDockSpace"
# In "Tabs Layout", no split is created
tabs_layout.docking_splits = []
return [alternative_layout, tabs_layout]
##########################################################################
# Define the app initial theme
##########################################################################
def setup_my_theme():
"""Example of theme customization at App startup
This function is called in the callback `setup_imgui_style` in order to apply a custom theme:
runner_params.callbacks.setup_imgui_style = setup_my_theme()
"""
# Apply default style
hello_imgui.imgui_default_settings.setup_default_imgui_style()
# Create a tweaked theme
tweaked_theme = hello_imgui.ImGuiTweakedTheme()
tweaked_theme.theme = hello_imgui.ImGuiTheme_.material_flat
tweaked_theme.tweaks.rounding = 10.0
# Apply the tweaked theme
hello_imgui.apply_tweaked_theme(tweaked_theme) # Note: you can also push/pop the theme in order to apply it only to a specific part of the Gui: hello_imgui.push_tweaked_theme(tweaked_theme) / hello_imgui.pop_tweaked_theme()
# Then apply further modifications to ImGui style
imgui.get_style().item_spacing = ImVec2(6, 4) # Reduce spacing between items ((8, 4) by default)
imgui.get_style().set_color_(imgui.Col_.text.value, (0.8, 0.8, 0.85, 1.0)) # Change text color
##########################################################################
# main(): here, we simply fill RunnerParams, then run the application
##########################################################################
def main():
# By default, an assets folder is installed via pip inside site-packages/lg_imgui_bundle/assets
# and provides two fonts (fonts/DroidSans.ttf and fonts/fontawesome-webfont.ttf)
# If you need to add more assets, make a copy of this assets folder and add your own files,
# and call set_assets_folder
hello_imgui.set_assets_folder(demo_utils.demos_assets_folder())
#
# Part 1: Define the application state, fill the status and menu bars, and load additional font
#
# Our application state
app_state = AppState()
# Hello ImGui params (they hold the settings as well as the Gui callbacks)
runner_params = hello_imgui.RunnerParams()
runner_params.app_window_params.window_title = "Docking Demo"
runner_params.imgui_window_params.menu_app_title = "Docking Demo"
runner_params.app_window_params.window_geometry.size = (1000, 900)
runner_params.app_window_params.restore_previous_geometry = True
runner_params.app_window_params.borderless = True
runner_params.app_window_params.borderless_movable = True
runner_params.app_window_params.borderless_resizable = True
runner_params.app_window_params.borderless_closable = True
# Set LoadAdditionalFonts callback
runner_params.callbacks.load_additional_fonts = lambda: load_fonts(app_state)
#
# Status bar
#
# We use the default status bar of Hello ImGui
runner_params.imgui_window_params.show_status_bar = True
# Add custom widgets in the status bar
runner_params.callbacks.show_status = lambda: status_bar_gui(app_state)
# uncomment next line in order to hide the FPS in the status bar
# runner_params.im_gui_window_params.show_status_fps = False
#
# Menu bar
#
# Here, we fully customize the menu bar:
# by setting `show_menu_bar` to True, and `show_menu_app` and `show_menu_view` to False,
# HelloImGui will display an empty menu bar, which we can fill with our own menu items via the callback `show_menus`
runner_params.imgui_window_params.show_menu_bar = True
runner_params.imgui_window_params.show_menu_app = False
runner_params.imgui_window_params.show_menu_view = False
# Inside `show_menus`, we can call `hello_imgui.show_view_menu` and `hello_imgui.show_app_menu` if desired
runner_params.callbacks.show_menus = lambda: show_menu_gui(runner_params)
# Optional: add items to Hello ImGui default App menu
runner_params.callbacks.show_app_menu_items = show_app_menu_items
#
# Top and bottom toolbars
#
# toolbar options
edge_toolbar_options = hello_imgui.EdgeToolbarOptions()
edge_toolbar_options.size_em = 2.5
edge_toolbar_options.window_bg = ImVec4(0.8, 0.8, 0.8, 0.35)
# top toolbar
runner_params.callbacks.add_edge_toolbar(
hello_imgui.EdgeToolbarType.top,
lambda: show_top_toolbar(app_state),
edge_toolbar_options,
)
# right toolbar
edge_toolbar_options.window_bg.w = 0.4
runner_params.callbacks.add_edge_toolbar(
hello_imgui.EdgeToolbarType.right,
lambda: show_right_toolbar(app_state),
edge_toolbar_options,
)
#
# Load user settings at callbacks `post_init` and save them at `before_exit`
#
runner_params.callbacks.post_init = lambda: load_my_app_settings(app_state)
runner_params.callbacks.before_exit = lambda: save_my_app_settings(app_state)
# Change style
runner_params.callbacks.setup_imgui_style = setup_my_theme
#
# Part 2: Define the application layout and windows
#
# First, tell HelloImGui that we want full screen dock space (this will create "MainDockSpace")
runner_params.imgui_window_params.default_imgui_window_type = (
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
)
# In this demo, we also demonstrate multiple viewports: you can drag windows outside
# out the main window in order to put their content into new native windows
runner_params.imgui_window_params.enable_viewports = True
# Set the default layout (this contains the default DockingSplits and DockableWindows)
runner_params.docking_params = create_default_layout(app_state)
# Add alternative layouts
runner_params.alternative_docking_layouts = create_alternative_layouts(app_state)
#
# Part 3: Where to save the app settings
#
# tag::app_settings[]
# By default, HelloImGui will save the settings in the current folder.
# This is convenient when developing, but not so much when deploying the app.
# You can tell HelloImGui to save the settings in a specific folder: choose between
# current_folder
# app_user_config_folder
# app_executable_folder
# home_folder
# temp_folder
# documents_folder
#
# Note: app_user_config_folder is:
# AppData under Windows (Example: C:\Users\[Username]\AppData\Roaming)
# ~/.config under Linux
# "~/Library/Application Support" under macOS or iOS
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
# runnerParams.ini_filename: this will be the name of the ini file in which the settings
# will be stored.
# In this example, the subdirectory Docking_Demo will be created under the folder defined
# by runnerParams.ini_folder_type.
#
# Note: if ini_filename is left empty, the name of the ini file will be derived
# from app_window_params.window_title
runner_params.ini_filename = "Docking_Demo/Docking_demo.ini"
# end::app_settings[]
#
# Part 4: Run the app
#
hello_imgui.run(runner_params)
if __name__ == "__main__":
main()

127
gui.py
View File

@ -1,47 +1,96 @@
from imgui_bundle import imgui, immapp # Custom
import glfw from model import *
import OpenGL.GL as gl from appstate import AppState
from datatypes import *
from view import View
class GUI(object): # Layouts
def __init__(self): from analyzer import analyzer_layout
super().__init__() from database import database_editor_layout
# External
from imgui_bundle import imgui, immapp, hello_imgui, ImVec2
# Built In
import shelve
from typing import List
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
hello_imgui.show_app_menu(runner_params)
hello_imgui.show_view_menu(runner_params)
if imgui.begin_menu("File"):
clicked, _ = imgui.menu_item("Open", "", False)
if clicked:
pass
imgui.end_menu()
def status_bar(app_state: AppState) -> None:
imgui.text("Student Analyzer by @DerGrumpf")
def main() -> None:
app_state = AppState()
# self.io = imgui.get_io() # Load Database
with shelve.open("state") as state:
v = state.get("DB")
if v:
db.init(v)
db.connect()
db.create_tables([Class, Student, Lecture, Submission])
app_state.update()
# Global GUI Setting # Set Asset Folder
'''win_w, win_h = glfw.get_window_size(self.window) #hello_imgui.set_assets_folder()
fb_w, fb_h = glfw.get_framebuffer_size(self.window)
font_scaling_factor = max(float(fb_w) / win_w, float(fb_h) / win_h)
font_size_in_pixels = 30
self.io.fonts.add_font_from_file_ttf("assets/MPLUSRounded1c-Regular.ttf", font_size_in_pixels * font_scaling_factor)
self.io.font_global_scale /= font_scaling_factor'''
self.view = View()
def header(self): # Set Theme
imgui.set_next_window_size(io.display_size.x, io.display_size.y*0.03)
imgui.set_next_window_position(0, io.display_size.y*0.02)
with imgui.begin("HEADER", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE | imgui.WINDOW_NO_TITLE_BAR): # Set Window Params
imgui.set_window_font_scale(1.3) runner_params = hello_imgui.RunnerParams()
text = "Student Analyzer" runner_params.app_window_params.window_title = "Analyzer"
ww = imgui.get_window_size().x runner_params.imgui_window_params.menu_app_title = "Analyzer"
tw = imgui.calc_text_size(text).x runner_params.app_window_params.window_geometry.size = (1000, 900)
imgui.set_cursor_pos_x((ww - tw) * 0.5) runner_params.app_window_params.restore_previous_geometry = True
imgui.text("Student Analyzer") runner_params.app_window_params.borderless = True
runner_params.app_window_params.borderless_movable = True
def __call__(self): runner_params.app_window_params.borderless_resizable = True
self.view() runner_params.app_window_params.borderless_closable = True
# Load Fonts
#runner_params.callbacks.load_additional_fonts = lambda: f()
# Status Bar & Main Menu
if __name__ == "__main__": runner_params.imgui_window_params.show_menu_bar = True
immapp.run( runner_params.imgui_window_params.show_menu_app = False
gui_function=GUI(), runner_params.imgui_window_params.show_menu_view = False
window_title="Student Analyzer", runner_params.imgui_window_params.show_status_bar = True
window_size_auto=True, # Inside `show_menus`, we can call `hello_imgui.show_view_menu` and `hello_imgui.show_app_menu` if desired
with_implot=True, runner_params.callbacks.show_menus = lambda: menu_bar(runner_params)
with_markdown=True # Optional: add items to Hello ImGui default App menu
#runner_params.callbacks.show_app_menu_items = show_app_menu_items
runner_params.callbacks.show_status = lambda: status_bar(app_state)
# Application layout
runner_params.imgui_window_params.default_imgui_window_type = (
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
) )
runner_params.imgui_window_params.enable_viewports = True
runner_params.docking_params = analyzer_layout(app_state)
runner_params.alternative_docking_layouts = [
database_editor_layout(app_state)
]
# Save App Settings
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
runner_params.ini_filename = "Analyzer/Analyzer.ini"
# Uncomment if layout will stay the same at start
runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.application_start
# Run it
add_ons_params = immapp.AddOnsParams()
add_ons_params.with_markdown = True
add_ons_params.with_implot = True
add_ons_params.with_implot3d = True
immapp.run(runner_params, add_ons_params)
if __name__ == "__main__":
main()

View File

@ -1,44 +0,0 @@
from imgui_bundle import imgui
from datatypes import *
from model import *
class LectureEditor:
def __init__(self):
super().__init__()
self.select = 0
self.add_lecture_text = str()
self.add_lecture_points = 0
def __call__(self, clas: Class):
id = clas.id if clas else None
lectures = Lecture.select().where(Lecture.class_id == id) if id else None
with imgui.begin("Lecture Editor", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
imgui.text("Add Lecture")
_, self.add_lecture_text = imgui.input_text("Title", self.add_lecture_text)
if self.add_lecture_points < 0:
self.add_lecture_points = 0
_, self.add_lecture_points = imgui.input_int("Points", self.add_lecture_points)
if imgui.button("Add"):
Lecture.create(
title=self.add_lecture_text,
points=self.add_lecture_points,
class_id=id
)
imgui.separator()
if not lectures:
imgui.text("No Lectures could be queried")
return
for n, lecture in enumerate(lectures, start=1):
display = f"{n}. {lecture.title}"
opened, _ = imgui.selectable(display, self.select == n-1)
if opened:
self.select = n-1
return lectures[self.select]

View File

@ -1,37 +0,0 @@
from imgui_bundle import imgui, imgui_ctx
from datatypes import *
class MainMenu:
def __init__(self):
super().__init__()
self.new = False
self.new_text = str()
def __call__(self):
if self.new:
self.create_new_file()
with imgui_ctx.begin_main_menu_bar() as main_menu_bar:
if main_menu_bar:
with imgui_ctx.begin_menu("File", True) as file_menu:
if file_menu.visible:
new, _ = imgui.menu_item("New", " ", False, True)
if new:
self.new = True
imgui.menu_item("Open", " ", False, True)
imgui.menu_item("Save", " ", False, True)
imgui.menu_item("Save as", " ", False, True)
with imgui_ctx.begin_menu("View", True) as view_menu:
if view_menu.visible:
with imgui_ctx.begin_menu("Change Layout", True) as open_layout_menu:
if open_layout_menu.visible:
layout_options = list(LayoutOptions)
for n, l in enumerate(layout_options):
clicked = imgui.menu_item_simple(l.name.title())
if clicked:
return l
def create_new_file(self):
pass

View File

@ -123,10 +123,6 @@ def dump_to_json(fp: Path, indent=None) -> None:
with open(fp, "w") as file: with open(fp, "w") as file:
json.dump(d, file, indent=indent) json.dump(d, file, indent=indent)
db.init('test.db')
db.connect()
db.create_tables([Class, Student, Lecture, Submission])
def main(): def main():
import random import random
# Generate Test Data # Generate Test Data
@ -166,4 +162,6 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
# main() # main()
db.init('wise_24_25.db')
db.connect()
dump_to_json(Path().cwd()/"TEST.json") dump_to_json(Path().cwd()/"TEST.json")

BIN
state Normal file

Binary file not shown.

View File

@ -1,41 +0,0 @@
from model import *
from imgui_bundle import imgui
class StudentEditor:
def __init__(self):
super().__init__()
self.prename = str()
self.surname = str()
self.sex = True
self.current = 0
def __call__(self):
with imgui.begin("Student Editor", False, imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
imgui.text("Add Student")
_, self.prename = imgui.input_text("First Name", self.prename)
_, self.surname = imgui.input_text("Last Name", self.surname)
with imgui.begin_group():
if imgui.radio_button("Male", self.sex):
self.sex = True
imgui.same_line()
if imgui.radio_button("Female", not self.sex):
self.sex = False
classes = Class.select()
if classes:
_, self.current = imgui.combo("Classes", self.current, [c.name for c in classes])
if imgui.button("Confirm") and classes:
Student.create(
prename=self.prename,
surname=self.surname,
sex='Male' if self.sex else 'Female',
class_id = classes[self.current]
)
self.prename = str()
self.surname = str()

View File

@ -1,78 +0,0 @@
import numpy as np
from imgui_bundle import imgui
import random
from datatypes import *
from model import *
class StudentGraph:
def __init__(self):
super().__init__()
self.classes = None
self.students = None
self.select_class = 0
self.select_student = 0
def __call__(self):
self.classes = Class.select()
self.students = Student.select().where(Student.class_id == self.classes[self.select_class].id) if self.classes else None
# Setup Data
submissions = Submission.select().where(Submission.student_id == self.students[self.select_student].id) if self.students else None
data = np.array([submission.points/Lecture.get_by_id(submission.lecture_id).points*100 for submission in submissions], dtype=np.float32) if submissions else None
with imgui.begin("Student Graph", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
w, h = imgui.get_content_region_available()
if not isinstance(data, np.ndarray):
imgui.text("No Submission available for this Student")
else:
imgui.plot_histogram(
"##Data", data, overlay_text="Performance per Lecture (in %)",
scale_min=0.0, scale_max=100,
graph_size=(w, h*0.69)
)
with imgui.begin_child("Select Class", w/3, h*0.3, border=True):
if not self.classes:
imgui.text("No Class could be queried")
else:
for n, c in enumerate(self.classes, start = 1):
display = f"{n}. {c.name}"
opened, _ = imgui.selectable(display, self.select_class == n-1)
if opened:
self.select_class = n-1
self.select_student = 0
imgui.same_line()
with imgui.begin_child("Select Student", w/3, h*0.3, border=True):
if not self.students:
imgui.text("No Student in this class")
else:
for n, s in enumerate(self.students, start = 1):
display = f"{n}. {s.prename} {s.surname}"
opened, _ = imgui.selectable(display, self.select_student == n-1)
if opened:
self.select_student = n-1
imgui.same_line()
with imgui.begin_child("Student Info", w/3, h*0.3, border=True):
if not submissions:
imgui.text("No Submissions for this Student")
else:
for n, s in enumerate(submissions):
lecture = Lecture.get_by_id(s.lecture_id)
points = s.points
if points.is_integer():
points = int(points)
display = f"{n}. {lecture.title} {points}/{lecture.points}"
COLOR = COLOR_TEXT_PASSED if points > lecture.points*0.3 else COLOR_TEXT_FAILED
imgui.text_colored(display, *COLOR)

View File

@ -1,77 +0,0 @@
from datatypes import *
from imgui_bundle import imgui, imgui_ctx, imgui_md, implot, ImVec2
import numpy as np
from model import *
class StudentInfo:
def __init__(self):
super().__init__()
self.select_class = 0
self.select_student = 0
def student_graph(self, student_id: int) -> None:
student = Student.get_by_id(student_id)
clas = Class.get_by_id(student.class_id)
lectures = Lecture.select().where(Lecture.class_id == clas.id)
submissions = Submission.select().where(Submission.student_id == student.id)
overall_points = np.sum([l.points for l in lectures])
points = np.sum([sub.points for sub in submissions])
if points.is_integer():
points = int(points)
subs_data = np.array([sub.points/l.points for sub, l in zip(submissions, lectures)])*100
subs_labels = [str(l.title) for l in lectures]
with imgui_ctx.begin_group():
imgui_md.render(f"# {student.prename} {student.surname}")
imgui_md.render(f"### {clas.name}")
pb_content = f"{points}/{overall_points} {points/overall_points:.1%}"
imgui.progress_bar(points/overall_points, overlay=pb_content)
implot.push_colormap(implot.Colormap_.deep.value)
if implot.begin_plot("Performance"):
implot.setup_axes("Lectures", "Percentage")
implot.setup_axes_limits(-1, len(subs_data), 0, 110)
implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(subs_labels), len(subs_labels), subs_labels, False)
implot.plot_bars("Submissions", subs_data)
implot.end_plot()
def student_list(self) -> int:
classes = Class.select()
content = [f"{n}. {c.name}" for n, c in enumerate(classes, start=1)]
students = Student.select().where(Student.class_id == classes[self.select_class].id)
lectures = Lecture.select().where(Lecture.class_id == classes[self.select_class].id)
overall_points = np.sum([l.points for l in lectures])
points = list()
for student in students:
submissions = Submission.select().where(Submission.student_id == student.id)
cummultative = [sub.points for sub in submissions]
passed = np.sum([p > overall_points*0.3 for p in cummultative])
points.append((student, np.sum(cummultative)/overall_points, passed > 1))
students = sorted(points, key=lambda x: x[1], reverse=True)
with imgui_ctx.begin_group():
_, self.select_class = imgui.combo("##class_list", self.select_class, content, len(content))
for n, student in enumerate(students, start=1):
s = student[0]
display = f"{n}. {s.prename} {s.surname}"
_, clicked = imgui.selectable(display, self.select_student == n-1)
if clicked:
self.select_student = n-1
return students[self.select_student][0].id
def __call__(self):
with imgui_ctx.begin("Student Info"):
w, h = imgui.get_window_size()
with imgui_ctx.begin_child("Student Selector", ImVec2(w*0.25, h*0.9)):
id = self.student_list()
imgui.same_line()
with imgui_ctx.begin_child("Student Graph", ImVec2(w*0.7, h*0.9)):
self.student_graph(id)

View File

@ -1,27 +0,0 @@
from model import *
from imgui_bundle import imgui
class StudentList:
def __init__(self):
super().__init__()
self.select: int = 0
def __call__(self, clas: Class):
id = clas.id if clas else None
students = Student.select().where(Student.class_id == id) if id else None
with imgui.begin("Student Table", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
if not students:
imgui.text("No Dataset could be queried")
return
for n, student in enumerate(students, start=1):
display = f"{n}. {student.prename} {student.surname}"
opened, _ = imgui.selectable(display, self.select == n-1)
if opened:
self.select = n-1
return students[self.select]

View File

@ -1,37 +0,0 @@
import numpy as np
from imgui_bundle import imgui
from model import *
class StudentRanking:
def __init__(self):
super().__init__()
def __call__(self):
students = Student.select().where(Student.class_id == 1)
lectures = Lecture.select().where(Lecture.class_id == 1)
overall_points = sum([l.points for l in lectures])
ranking = list()
avg = list()
for s in students:
rank = sum([sub.points for sub in Submission.select().where(Submission.student_id == s.id)])/overall_points
ranking.append((f"{s.prename} {s.surname}", rank))
avg.append(rank)
ranking = sorted(ranking, key=lambda x: x[1], reverse=True)
avg = sum(avg)/len(avg)
flag = True
with imgui.begin("Student Ranking", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
for n, rank in enumerate(ranking, start=1):
if rank[1] < avg and flag:
imgui.separator()
flag = False
imgui.text(f"{n}. {rank[0]} {rank[1]:.1%}")
imgui.separator()
imgui.text(f"Average: {avg:.1%}")

View File

@ -1,66 +0,0 @@
from imgui_bundle import imgui
from model import *
class SubmissionEditor:
def __init__(self):
super().__init__()
self.current_lecture = 0
self.current_student = 0
self.points = 0
def __call__(self, clas: Class):
id = clas.id if clas else None
lectures = Lecture.select().where(Lecture.class_id == id) if id else None
students = Student.select().where(Student.class_id == id) if id else None
with imgui.begin("Submission Editor", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE):
imgui.text("Add Submission")
if not lectures:
imgui.text("No Lectures queried")
return
if not students:
imgui.text("No Students queried")
return
_, self.current_lecture = imgui.combo("Lecture", self.current_lecture, [f"{l.title} ({l.points})" for l in lectures])
_, self.current_student = imgui.combo("Student", self.current_student, [f"{s.prename} {s.surname}" for s in students])
if self.points < 0:
self.points = 0
max = lectures[self.current_lecture].points
if self.points > max:
self.points = max
_, self.points = imgui.input_float("Points", self.points, format='%.1f', step=0.5, step_fast=1.0)
if imgui.button("Add"):
if not Submission.select().where(
Submission.student_id == students[self.current_student].id and
Submission.lecture_id == lectures[self.current_lecture].id
):
Submission.create(
student_id=students[self.current_student].id,
lecture_id=lectures[self.current_lecture].id,
points=self.points
)
imgui.same_line()
if imgui.button("Update"):
submission = Submission.select().where(
Submission.student_id == students[self.current_student].id and
Submission.lecture_id == lectures[self.current_lecture].id
).get()
submission.points = self.points
submission.save()
imgui.same_line()
if imgui.button("Delete"):
Submission.delete().where(
Submission.student_id == students[self.current_student].id and
Submission.lecture_id == lectures[self.current_lecture].id
).execute()

65
view.py
View File

@ -1,65 +0,0 @@
from imgui_bundle import imgui, ImVec2
from datatypes import *
from main_menu import MainMenu
from database_editor import DatabaseEditor
from student_info import StudentInfo
def set_layout(size: tuple, pos: tuple) -> None:
io = imgui.get_io()
size = imgui.ImVec2(*size)
pos = imgui.ImVec2(*pos)
imgui.set_next_window_size(size)
imgui.set_next_window_pos(pos)
class GrapherLayout:
def __init__(self):
super().__init__()
self.student_info = StudentInfo()
def set_layout(self):
pass
def __call__(self):
self.student_info()
class EditorLayout:
def __init__(self):
super().__init__()
self.database_editor = DatabaseEditor()
def set_layout(self):
pass
def __call__(self):
self.database_editor()
class View:
def __init__(self):
super().__init__()
self.current = LayoutOptions.GRAPHER
self.main_menu = MainMenu()
self.editor = EditorLayout()
self.grapher = GrapherLayout()
def switch_context(self, ctx: LayoutOptions) -> None:
match ctx:
case LayoutOptions.EDITOR:
self.editor.set_layout()
case LayoutOptions.GRAPHER:
self.grapher.set_layout()
def __call__(self):
option = self.main_menu()
if option:
self.current = option
if self.current == LayoutOptions.EDITOR:
self.editor()
if self.current == LayoutOptions.GRAPHER:
self.grapher()