import random from typing import Any, Union from PySide6 import QtCore, QtGui, QtWidgets from PySide6.QtCharts import QCategoryAxis, QChart, QChartView, QLineSeries, QValueAxis from PySide6.QtGui import QColor, QPainter, QPen from src.core.models import Semester 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