Files
SemesterapparatsManager/src/ui/widgets/graph.py

177 lines
7.0 KiB
Python

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