diff --git a/analyzer.py b/analyzer.py new file mode 100644 index 0000000..4632316 --- /dev/null +++ b/analyzer.py @@ -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 diff --git a/appstate.py b/appstate.py new file mode 100644 index 0000000..31979ca --- /dev/null +++ b/appstate.py @@ -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) + diff --git a/assets/Student_list.csv b/assets/Student_list.csv index 064b71e..6765eaf 100644 --- a/assets/Student_list.csv +++ b/assets/Student_list.csv @@ -1,34 +1,35 @@ -First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium -Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22 -Marleen,Adolphi,Female,29.5,15,18,32,19,0,17,24 -Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26 -Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16 -Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22 -Nova,Eib,Female,31,15,15,34,20,20,21,27 -Nele,Grundke,Female,23.5,13,16,28,20,17,21,18 -Anna,Grünewald,Female,12,14,16,29,16,15,19,9 -Yannik,Haupt,Male,18,6,14,21,13,2,9,0 -Janna,Heiny,Female,30,15,18,33,18,20,22,25 -Milena,Krieger,Female,30,15,18,33,20,20,21.5,26 -Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26 -Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21 -Manthey,Leonie,Female,28.5,14,18,29,20,10,18,23 -Izabel,Mike,Female,29.5,15,15,35,11,4,19,21 -Lea,Noglik,Female,22.5,15,17,34,13,10,20,0 -Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18 -Julia,Renner,Female,27.5,10,14,0,20,17,11,20 -Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22 -Natascha,Rott,Female,29.5,12,18,32,19,20,21,26 -Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21 -Melina,Sablotny,Female,31,15,18,33,20,20,21,19 -Alea,Schleier,Female,27,14,18,34,16,18,21.5,22 -Flemming,Schur,Male,29.5,15,17,34,19,20,19,22 -Marie,Seeger,Female,27.5,15,18,32,14,9,17,22 -Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18 -Lara,Troschke,Female,28.5,14,17,28,13,19,21,25 -Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22 -Alea,Unger,Female,30,12,18,31,20,20,21,22 -Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24 -Katharina,Walz,Female,31,15,18,31,19,19,17,24 -Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0 -Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14 +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,0,0 +Marleen,Adolphi,Female,29.5,15,18,32,19,20,17,24,23,0 +Sarina,Apel,Female,28.5,15,18,32,20,20,21,24,20,0 +Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26,16,0 +Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16,0,0 +Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22,12,0 +Nova,Eib,Female,31,15,15,34,20,20,21,27,19,0 +Nele,Grundke,Female,23.5,13,16,28,20,17,21,18,22,0 +Anna,Grünewald,Female,12,14,16,29,16,15,19,9,0,0 +Yannik,Haupt,Male,18,6,14,21,13,2,9,0,0,0 +Janna,Heiny,Female,30,15,18,33,18,20,22,25,24,30 +Milena,Krieger,Female,30,15,18,33,20,20,21.5,26,20,0 +Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26,24,0 +Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21,21,0 +Leonie,Manthey,Female,28.5,14,18,29,20,10,18,23,16,28 +Izabel,Mike,Female,29.5,15,15,35,11,15,19,21,21,27 +Lea,Noglik,Female,22.5,15,17,34,13,10,20,21,19,0 +Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18,19,6 +Julia,Renner,Female,27.5,10,14,32,20,17,11,20,24,0 +Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22,18,0 +Natascha,Rott,Female,29.5,12,18,32,19,20,21,26,23,0 +Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21,16,0 +Melina,Sablotny,Female,31,15,18,33,20,20,21,19,11,0 +Alea,Schleier,Female,27,14,18,34,16,18,21.5,22,15,22 +Flemming,Schur,Male,29.5,15,17,34,19,20,19,22,18,0 +Marie,Seeger,Female,27.5,15,18,32,14,9,17,22,9,0 +Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18,22,0 +Lara,Troschke,Female,28.5,14,17,28,13,19,21,25,12,0 +Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22,17,0 +Alea,Unger,Female,30,12,18,31,20,20,21,22,15,21.5 +Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24,12,0 +Katharina,Walz,Female,31,15,18,31,19,19,17,24,17,14.5 +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 diff --git a/test.db b/assets/WiSe_24_25.db similarity index 57% rename from test.db rename to assets/WiSe_24_25.db index 846da44..cd99f60 100644 Binary files a/test.db and b/assets/WiSe_24_25.db differ diff --git a/assets/convert.py b/assets/convert.py index 4ce3dd4..ac04ab4 100644 --- a/assets/convert.py +++ b/assets/convert.py @@ -5,6 +5,7 @@ sys.path.append('..') from model import * df = pd.read_csv("Student_list.csv") +df = df.dropna() courses = { 'Tutorial 1': 31, 'Tutorial 2': 15, @@ -13,9 +14,17 @@ courses = { 'SciPy': 20, 'Monte Carlo': 20, '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 clas = Class.create(name='WiSe 24/25') #print(clas.id) @@ -39,4 +48,4 @@ for index, row in df.iterrows(): lecture_id=Lecture.select().where(Lecture.title == title), points=points ) - + diff --git a/assets/icons/db_icon.png b/assets/icons/db_icon.png new file mode 100644 index 0000000..708890a Binary files /dev/null and b/assets/icons/db_icon.png differ diff --git a/class_editor.py b/class_editor.py deleted file mode 100644 index 1751ea3..0000000 --- a/class_editor.py +++ /dev/null @@ -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] diff --git a/database.py b/database.py new file mode 100644 index 0000000..335c0a9 --- /dev/null +++ b/database.py @@ -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 diff --git a/datatypes.py b/datatypes.py deleted file mode 100644 index 74eb1be..0000000 --- a/datatypes.py +++ /dev/null @@ -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 diff --git a/demo_docking.py b/demo_docking.py deleted file mode 100644 index 1be190e..0000000 --- a/demo_docking.py +++ /dev/null @@ -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() diff --git a/gui.py b/gui.py index 0128e8e..1565dd5 100644 --- a/gui.py +++ b/gui.py @@ -1,47 +1,96 @@ -from imgui_bundle import imgui, immapp -import glfw -import OpenGL.GL as gl -from datatypes import * -from view import View +# Custom +from model import * +from appstate import AppState -class GUI(object): - def __init__(self): - super().__init__() +# Layouts +from analyzer import analyzer_layout +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 - '''win_w, win_h = glfw.get_window_size(self.window) - 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() + # Set Asset Folder + #hello_imgui.set_assets_folder() - def header(self): - 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) + # Set Theme - with imgui.begin("HEADER", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE | imgui.WINDOW_NO_TITLE_BAR): - imgui.set_window_font_scale(1.3) - text = "Student Analyzer" - ww = imgui.get_window_size().x - tw = imgui.calc_text_size(text).x - imgui.set_cursor_pos_x((ww - tw) * 0.5) - imgui.text("Student Analyzer") - - def __call__(self): - self.view() + # Set Window Params + runner_params = hello_imgui.RunnerParams() + runner_params.app_window_params.window_title = "Analyzer" + runner_params.imgui_window_params.menu_app_title = "Analyzer" + 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 + # Load Fonts + #runner_params.callbacks.load_additional_fonts = lambda: f() - -if __name__ == "__main__": - immapp.run( - gui_function=GUI(), - window_title="Student Analyzer", - window_size_auto=True, - with_implot=True, - with_markdown=True + # Status Bar & Main Menu + 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 + runner_params.imgui_window_params.show_status_bar = True + # 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: menu_bar(runner_params) + # 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() diff --git a/lecture_editor.py b/lecture_editor.py deleted file mode 100644 index 235f082..0000000 --- a/lecture_editor.py +++ /dev/null @@ -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] diff --git a/main_menu.py b/main_menu.py deleted file mode 100644 index 94c8a96..0000000 --- a/main_menu.py +++ /dev/null @@ -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 diff --git a/model.py b/model.py index 56aa96d..6036c92 100644 --- a/model.py +++ b/model.py @@ -123,10 +123,6 @@ def dump_to_json(fp: Path, indent=None) -> None: with open(fp, "w") as file: json.dump(d, file, indent=indent) -db.init('test.db') -db.connect() -db.create_tables([Class, Student, Lecture, Submission]) - def main(): import random # Generate Test Data @@ -166,4 +162,6 @@ def main(): if __name__ == "__main__": # main() + db.init('wise_24_25.db') + db.connect() dump_to_json(Path().cwd()/"TEST.json") diff --git a/state b/state new file mode 100644 index 0000000..7135d88 Binary files /dev/null and b/state differ diff --git a/student_editor.py b/student_editor.py deleted file mode 100644 index e4fa3b3..0000000 --- a/student_editor.py +++ /dev/null @@ -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() diff --git a/student_graph.py b/student_graph.py deleted file mode 100644 index 81bd978..0000000 --- a/student_graph.py +++ /dev/null @@ -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) diff --git a/student_info.py b/student_info.py deleted file mode 100644 index 325d695..0000000 --- a/student_info.py +++ /dev/null @@ -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) - diff --git a/student_list.py b/student_list.py deleted file mode 100644 index 7ec48e3..0000000 --- a/student_list.py +++ /dev/null @@ -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] - - diff --git a/student_ranking.py b/student_ranking.py deleted file mode 100644 index 6add0d0..0000000 --- a/student_ranking.py +++ /dev/null @@ -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%}") - diff --git a/submission_editor.py b/submission_editor.py deleted file mode 100644 index a231fe5..0000000 --- a/submission_editor.py +++ /dev/null @@ -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() diff --git a/view.py b/view.py deleted file mode 100644 index 9e3e55a..0000000 --- a/view.py +++ /dev/null @@ -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()