[Code-Review]GUI um Messwerte aus CSV-Datei in Diagramm darzustellen

Fragen zu Tkinter.
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich benötige mal wieder eure Hilfe bzw. eure Meinung/Feedback/Kritik an meinem Code.
Ich habe hier mal vor längerem angefangen, heute noch etwas geändert und ehrlich gesagt fällt es mir total schwer meinen eigenen Code so kritisch zu hinterfragen, weil es ja aus meinem Kopf kam und ich es wohl für logisch hielt.

Es geht darum dass man eine oder mehrere CSV-Dateien auswählen kann, dann kann man auswählen welche Datei benutzt werden soll. Dabei werden die Messwerte, die sich in der Datei befinden angezeigt und mann kann die gewünschten Messwerte wieder auswählen und ein Diagramm erstellen lassen. Über eine weitere Checkbox kann man bestimmen ob bei einer Auswahl von mehreren Messwerten, diese in einem Diagramm oder in getrennten dargestellt werden.
Man kann auch mehrere Dateien auswählen um die ausgewählten Messwerte aus mehreren Dateien "gleichzeitig" in Diagrammen darzustellen.

Diesen Code hätte ich gerne erst mal glatt gezogen bzw. auf einem sauberen Zustand.
Was mir nicht gefällt, wenn ich zwei Messwerte mit unterschiedlichen Einheiten in einem Diagramm darstelle, dann hätte ich gerne zwei y-Achsen mit den entsprechenden Einheiten, denn sonst macht das ja keinen Sinn.

Ich befürchte dass der Einsatz von 'zip' vielleicht nicht unbedingt zum Verständnis beiträgt und das es auch verwirrend ist, das 'choosen_file' ein Pfad als Key hat und als Wert eine Liste aus den Dateieninhalt und einem True oder False. Jeder Versuch gleitete leider immer wieder in mehr oder weniger das gleiche Schema ab. Bei fremden Code fällt mir das Hinterfragen wesentlich leichter 😯

Naja ich glaube(hoffe) der Code zeigt was ich erreichen wollte, den abgesehen von den angesprochenen y-Achsen funktioniert er wie er soll. (Bis auf das was ich noch übersehen habe, davon gibts ja auch immer mal wieder was)

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from pathlib import Path
from tkinter import ttk
from tkinter.filedialog import askopenfilenames

import matplotlib.pyplot as plt
import pandas as pd

HEADINGS = {"Drehzahl": "s", "Druck": "p", "Temperatur": "t"}


def all_false(iterable):
    for element in iterable:
        if element:
            return False
    return True


def select_files():
    return [Path(file) for file in askopenfilenames(filetypes=[("CSV-File", ".csv")])]


class SensorData(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.chosen_file = {}
        self.selected_sensor = {}
        self.sensor_checkboxes = {}
        self.file_checkboxes = {}
        self.sensor_data = None
        ttk.Label(self, text="Datei(en) mit Messwerte auswählen").grid(row=0, column=0)
        self.open_button = ttk.Button(self, text="Öffnen", command=self.show_files)
        self.open_button.grid(row=1, column=0, sticky=tk.NW)
        self.create_diagram_button = ttk.Button(
            self, text="Diagramm erstellen", command=self.create_diagram
        )
        self.create_diagram_button.grid(row=3, column=0, sticky=tk.NW)
        self.create_diagram_button["state"] = tk.DISABLED
        self.subplots = tk.BooleanVar()
        self.subplots_button = ttk.Checkbutton(
            self,
            text="Diagramm vereinen",
            onvalue=False,
            offvalue=True,
            variable=self.subplots,
        )
        self.subplots.set(True)
        self.subplots_button.grid(row=3, column=1, sticky=tk.NW)
        self.reset_button = ttk.Button(self, text="Reset", command=self.reset_files)
        self.reset_button.grid(row=3, column=2, sticky=tk.NW)
        self.files_frame = ttk.Frame(self)
        self.files_frame.grid(row=2, column=0, sticky=tk.NW)
        self.measuring_ranges = ttk.Frame(self)
        self.measuring_ranges.grid(row=1, column=1, sticky=tk.NW)
        for index, heading in enumerate(HEADINGS):
            ttk.Label(
                self.measuring_ranges, text=heading, font="Helvetica 9 bold"
            ).grid(row=0, column=index, sticky=tk.NW)

    def create_checkboxes(self):
        for sensor, _ in self.sensor_data[0].columns.to_list():
            self.selected_sensor[sensor] = tk.BooleanVar()
            self.sensor_checkboxes[sensor] = ttk.Checkbutton(
                self.measuring_ranges,
                text=sensor,
                onvalue=True,
                offvalue=False,
                variable=self.selected_sensor[sensor],
            )

    def create_diagram(self):
        data_to_diagram = [
            sensor
            for sensor, selected_sensor in self.selected_sensor.items()
            if selected_sensor.get()
        ]
        for file, (selected, data) in self.chosen_file.items():
            if selected.get():
                data.plot.line(
                    y=data_to_diagram,
                    subplots=self.subplots.get(),
                    title=file.stem,
                    grid=True,
                )
        plt.show()

    def read_files(self):
        files = select_files()
        self.sensor_data = [
            pd.read_csv(
                file, header=[0, 1], index_col=0, parse_dates=True, low_memory=False
            )
            for file in files
        ]
        return zip(files, self.sensor_data)

    def reset_files(self):
        for sensor_checkbox in self.sensor_checkboxes.values():
            sensor_checkbox.grid_forget()
        for checkbox in self.file_checkboxes.values():
            checkbox.grid_forget()
        self.chosen_file = {}
        self.selected_sensor = {}
        self.sensor_checkboxes = {}
        self.sensor_data = None
        self.open_button["state"] = tk.NORMAL
        self.create_diagram_button["state"] = tk.DISABLED

    def show_files(self):
        for index, (file_path, data) in enumerate(self.read_files()):
            self.chosen_file[file_path] = [tk.BooleanVar(), data]
            self.file_checkboxes[file_path] = ttk.Checkbutton(
                self.files_frame,
                text=str(file_path.stem),
                onvalue=True,
                offvalue=False,
                variable=self.chosen_file[file_path][0],
                command=self.update_sensor_selection,
            )
            self.file_checkboxes[file_path].grid(row=index, column=0, sticky=tk.NW)
        self.open_button["state"] = tk.DISABLED

    def show_sensor_selection(self):
        if not self.sensor_checkboxes.values():
            self.create_checkboxes()
        index_speed = 1
        index_pressure = 1
        index_temperature = 1
        for sensor, _ in self.sensor_data[0].columns.to_list():
            if sensor.lower().startswith(HEADINGS["Drehzahl"]):
                self.sensor_checkboxes[sensor].grid(
                    row=index_speed, column=0, sticky=tk.NW
                )
                index_speed += 1
            elif sensor.lower().startswith(HEADINGS["Druck"]):
                self.sensor_checkboxes[sensor].grid(
                    row=index_pressure, column=1, sticky=tk.NW
                )
                index_pressure += 1
            elif sensor.lower().startswith(HEADINGS["Temperatur"]):
                self.sensor_checkboxes[sensor].grid(
                    row=index_temperature, column=2, sticky=tk.NW
                )
                index_temperature += 1
        self.create_diagram_button["state"] = tk.NORMAL

    def update_sensor_selection(self):
        selected_file = [
            file_checked.get() for file_checked, _ in self.chosen_file.values()
        ]
        if all_false(selected_file):
            for selected in self.selected_sensor.values():
                selected.set(False)
            for sensor_checkbox in self.sensor_checkboxes.values():
                sensor_checkbox.grid_forget()
        elif any(selected_file):
            self.show_sensor_selection()


def main():
    root = tk.Tk()
    root.title("Sensordaten auslesen")
    app = SensorData(root)
    app.pack()
    app.mainloop()


if __name__ == "__main__":
    main()
Eine CSV-Datei sieht zum Beispiel so aus:

Code: Alles auswählen

"Time","Speed_CNT 4/1 (AVG)","Speed_CNT 4/1 (RMS)","Speed_CNT 4/1 (MIN)","Speed_CNT 4/1 (MAX)","Speed_CNT 4/1","tX1 (AVG)","tX1 (RMS)","tX1 (MIN)","tX1 (MAX)","tX1","tX2 (AVG)","tX2 (RMS)","tX2 (MIN)","tX2 (MAX)","tX2","pD3 (AVG)","pD3 (RMS)","pD3 (MIN)","pD3 (MAX)","pD3","tD3  (AVG)","tD3  (RMS)","tD3  (MIN)","tD3  (MAX)","tD3 ","pD4  (AVG)","pD4  (RMS)","pD4  (MIN)","pD4  (MAX)","pD4 ","tD4 (AVG)","tD4 (RMS)","tD4 (MIN)","tD4 (MAX)","tD4","pX1 (AVG)","pX1 (RMS)","pX1 (MIN)","pX1 (MAX)","pX1","tS4 (AVG)","tS4 (RMS)","tS4 (MIN)","tS4 (MAX)","tS4","pX2 (AVG)","pX2 (RMS)","pX2 (MIN)","pX2 (MAX)","pX2"
"s","rpm","rpm","rpm","rpm","rpm","deg C","deg C","deg C","deg C","deg C","deg C","deg C","deg C","deg C","deg C","bar","bar","bar","bar","bar","Cdeg","Cdeg","Cdeg","Cdeg","Cdeg","bar","bar","bar","bar","bar","Cdeg","Cdeg","Cdeg","Cdeg","Cdeg","bar","bar","bar","bar","bar","Cdeg","Cdeg","Cdeg","Cdeg","Cdeg","bar","bar","bar","bar","bar"
0.000000,700,,,,704.425125122070,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000514090092,,,,,,,,,,0.000514090092
0.001000,701,,,,704.394774754842,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.2000,700.2,,,,704.364424387614,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.3000,700,,,,704.334074020386,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.4000,700,,,,704.303723653158,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.5000,703,,,,704.273373285929,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.6000,703,,,,704.243022918701,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.7000,703,,,,704.212672551473,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.8000,702,,,,704.182322184245,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
0.9000,702,,,,704.151971817017,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.0000,702.2,,,,704.121621449788,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000514090092,,,,,,,,,,0.000514090092
1.1000,701,,,,704.091271082560,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.2000,701,,,,704.060920715332,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.3000,701,,,,704.030570348104,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.4000,702,,,,704.000219980876,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.5000,701,,,,703.969869613648,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.6000,701,,,,703.939519246419,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.7000,703,,,,703.909168879191,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.8000,703,,,,703.878818511963,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.9000,703,,,,703.848468144735,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.0000,702,,,,703.818117777506,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000514090092,,,,,,,,,,0.000514090092
2.1000,701,,,,703.787767410278,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.2000,701,,,,703.757417043050,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.3000,703,,,,703.727066675822,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.4000,701,,,,703.696716308594,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.5000,701,,,,703.666365941366,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.6000,701,,,,703.636015574137,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.7000,703,,,,703.605665206909,,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.8000,703,,,,703.575314839681,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2.9000,703,,,,703.544964472453,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.0000,700,,,,703.514614105225,,200,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000514090092,,,,,,,,,,0.000514090092
3.1000,700,,,,703.525987386704,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.2000,700,,,,703.537360668182,,201,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.3000,700,,,,703.548733949661,,203,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.4000,700,,,,703.560107231140,,203,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.5000,700,,,,703.571480512619,,203,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.6000,701,,,,703.582853794098,,203,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.7000,701,,,,703.594227075577,,203,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.8000,701,,,,703.605600357056,,204,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3.9000,701,,,,703.616973638534,,204,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
4.0000,700,,,,703.628346920013,,204,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000514090092,,,,,,,,,,0.000514090092
Irgendwann sollte ich dann mal Dateien bekommen, in denen alle Messwerte drin sind. Hier habe ich ein paar auch von Hand eingetragen, das ich den Code etwas testen kann.

Vielen Dank vorab!

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten