import random import sys from typing import Any, Union import loguru from PySide6 import QtWidgets, QtCore, QtGui from PySide6.QtCore import Qt from PySide6.QtGui import QPainter, QPen, QColor from PySide6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis, QCategoryAxis from src import LOG_DIR from src.backend.semester import Semester log = loguru.logger log.remove() log.add(sys.stdout, level="INFO") log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") def mergedicts(d1: dict[str, Any], d2: dict[str, Any]): res: dict[str, Any] = {} d1_data = list(d1.items()) d2_data = list(d2.items()) for i in range(len(d1)): # _data is a list of tuples d1_data_slice = d1_data[i] d2_data_slice = d2_data[i] # convert the tuples to dicts d1_dict = dict([d1_data_slice]) d2_dict = dict([d2_data_slice]) # merge the dicts res.update(d1_dict) # type: ignore res.update(d2_dict) # type: ignore return res class DataQtGraph(QtWidgets.QWidget): def __init__( self, title: str, data: dict[str, Union[list[str], dict[str, list[int]]]], generateMissing: bool, y_label: str, x_rotation: int = 90, ): super().__init__() self.series = QLineSeries() self.chart = QChart() # scale the chart to fit the data self.chart.setTitle(title) self.chart.legend().setVisible(True) layout = QtWidgets.QVBoxLayout() lst = [] if generateMissing: s_start = Semester.from_string(data["x"][0]) s_end = Semester.from_string(data["x"][-1]) # generate all semesters from start to end missing_semesters = Semester.generate_missing(s_start, s_end) x_data = data["x"] y_data = data["y"] if not isinstance(y_data, list): for key in y_data: data = {"x": [], "y": []} data["y-label"] = key for semester in missing_semesters: if semester not in x_data: data["x"].append(semester) data["y"].append(0) data["x"].append(semester) # get the index of the semester in x_data if semester in x_data: index = x_data.index(semester) print("index:", index) print(key, y_data[key]) data["y"].append(y_data[key][index]) lst.append(data) # for key in y_data: # data = {"x": x_data, "y": y_data[key]} # data = self.generateMissingSemesters(data) # data["y-label"] = key else: # data = self.generateMissingSemesters(data) data = {"x": [], "y": []} for semester in missing_semesters: # if semester not in x_data, set y to 0 if semester not in x_data: data["x"].append(semester) data["y"].append(0) data["x"].append(semester) # get the index of the semester in x_data if semester in x_data: index = x_data.index(semester) data["y"].append(y_data[index]) data["y-label"] = y_label lst.append(data) else: x_data = data["x"] y_data = data["y"] if not isinstance(y_data, list): for key in y_data: data = {"x": x_data, "y": y_data[key]} data["y-label"] = key lst.append(data) else: lst.append(data) x_data = lst[0]["x"] # self.chart.createDefaultAxes() for entry in lst: print("entry:", entry) entryseries = QLineSeries() for x_val, y_val in zip(entry["x"], entry["y"]): # entryseries.append(entry["x"].index(x_val), y_val) entryseries.setName(entry["y-label"] if "y-label" in entry else y_label) entryseries.setPen( QPen( QColor( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), ), 2, ) ) self.chart.addSeries(entryseries) x_axis = QCategoryAxis() for index, semester in enumerate(lst[0]["x"]): x_axis.append(semester, index) x_axis.setLabelsPosition( QCategoryAxis.AxisLabelsPosition.AxisLabelsPositionOnValue ) # rotare the label by 45 degrees x_axis.setLabelsAngle(x_rotation) x_axis.setLabelsFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Normal, False)) self.chart.setAxisX(x_axis, entryseries) # entryseries.append( # str() # ) self.chart.legend().setVisible(True) self.chart.legend().setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) # set legend labels self.chart.setAxisY(QValueAxis(self.chart), entryseries) # the chart's x axis labels are not being displayed, as the chart only has limited space. scale down the x axis font chartview = QChartView(self.chart) chartview.setRenderHint(QPainter.RenderHint.Antialiasing) layout.addWidget(chartview) self.setLayout(layout) def generateMissingSemesters(self, data: dict[list]): # join the data into a single dict with x values as key and y values as value tmp_data = dict(zip(data["x"], data["y"], strict=False)) # split into dicts based on SoSe and WiSe SoSe_data = {k: v for k, v in tmp_data.items() if "SoSe" in k} WiSe_data = {k: v for k, v in tmp_data.items() if "WiSe" in k} SoSe_years = [int(sose.split("SoSe")[1]) for sose in SoSe_data] WiSe_years = [int(wise.split("WiSe")[1].split("/")[0]) for wise in WiSe_data] years = SoSe_years + WiSe_years years = [ year for year in range(min(list(set(years))), max(list(set(years))) + 1) ] years.sort() for year in years: SoSe_year = f"SoSe{year}" WiSe_year = f"WiSe{year}/{year + 1}" if SoSe_year not in SoSe_data.keys(): SoSe_data[SoSe_year] = 0 if WiSe_year not in WiSe_data.keys(): WiSe_data[WiSe_year] = 0 # sort WiSe_data to have same order as SoSe_data WiSe_data = dict(sorted(WiSe_data.items(), key=lambda x: x[0])) SoSe_data = dict(sorted(SoSe_data.items(), key=lambda x: x[0])) data = mergedicts(SoSe_data, WiSe_data) # split the data back into x and y data = {"x": list(data.keys()), "y": list(data.values())} return data if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) graph_data = {"x": ["WiSe 25/26", "WiSe 24/25", "SoSe 25"], "y": [1, 2, 1]} widget = DataGraph( "ELSA Apparate pro Semester", graph_data, True, "Anzahl der Apparate" ) widget.show() sys.exit(app.exec())