Gerade im IoT-Umfeld nehmen die Datenmengen gerne schwindelerregende Umfänge an. Die einzige Chance, den Überblick zu behalten, ist eine geeignete Datenvisualisierung. Der erste Schritt bei Zeitreihendaten ist die Erstellung einer Infografik in Form eines xy-Liniendiagramms. Dazu sind grundsätzlich keine Grafiktools notwendig, eine einfaches in Python oder C# geschriebenes Programm genügt. Um abschätzen zu können, welche Programmierumgebung angenehmer ist, sind hier der Python- und der C#-Quellcode für das gleiche Ergebnis dargestellt.
09.01.2024
Das Anwendungsszenario
Das Beispiel verwendet Daten, die von einer WLAN-Steckdose gesammelt und im Internet gespeichert werden. Daten für bis zu drei Tagen sind über einen Webservice im CSV-Format ladbar. Die Daten enthalten einen Unix-Zeitstempel, einen menschenlesbaren Zeitstring, die jeweils aktuelle durch die WLAN-Steckdose gemessene Leistung in Watt und die über die Zeit kumulierte (integrierte) Leitung sprich Energie in Kilo-Wattstunden.
Aus den Daten wird eine x-y-Liniengrafik erzeugt und auf der eigenen Festplatte abgespeichert. Das Programm wurde zunächst mit Python entwickelt und dann – möglichst eins zu eins – in Dot Net C# umgesetzt. Das führt zu einem wenig schönen C#-Code, lässt aber den Vergleich zwischen Python und C# als Programmiersprachen zu.
Persönliche Wertung (Achtung subjektiv): Nach einigen Experimenten mit solchen ETL-Prozessen bin ich für mich zum Schluss gekommen, dass ich mit Python schneller zu einem brauchbaren Ergebnis gelange. Dabei bin ich weder Python- noch C# / Dot Net Fan.
Die erzeugte Grafik
Abb. 1: Die von den Programmen erzeugte SVG-Grafik, hier als Screenshot im Browse Chrome. Dargestellt sind in der x-Achse die Zeit beginnend mit vorgestern Null Uhr bis jetzt, in schwarz die von der WLAN-Steckdose gemessene Leistung, in rot die kumulierte Leistung sprich Energie. Sowohl Leistung als auch Energie sind in der y-Achse so skaliert, dass sie die Zeichenfläche ausfüllen.
Vorgehen und Programmierung
Python mit Thonny
Für die Programmierung des Python-Programms diente die einfache IDE Thonny. Thonny bringt schon die aktuelle Python-Version mit und ist mit einem Mausklick zu installieren.
C# mit Visual Studio Code
Für die Programmierung in C# wurde Dot Net in der aktuellen Version (heißt: ist in einem Jahr hoffnungslos veraltet) und Visual Studio Code verwendet. Zunächst wurde im Terminal ein Hello-World-Programm erzeugt mit „dotnet new console“, dann der Python Quelltext im Edotor einfügt und manuell in C# übersetzt. Da Python nicht typisiert ist, führt das zu einigen umständlichen Konstrukten. Um die Vergleichbarkeit zu erhalten, wurden auch in C# Listen und keine starren Arrays verwendet.
In der aktuellen Visual Studio Code Version genügt es, lediglich für den WebClient „using System.Net“ einzubinden.
Quelltexte:
Python
#!/usr/bin/env python3 # ------------------------------- # SVG-Liniengrafik # Datenvisualisierung mit Python # Claus Brell # 06.01.2024 # ------------------------------- # Ablauf: # Stage 1: Daten laden von Internetquelle # Stage 2: Daten umformen / anpassen # Stage 3: SVG-Grafik erzeugen # ------------------------------- # Vorbereitungen # ------------------------------- # Import für Internet-Zugriff import requests import sys # initialisieren, sonst kann man nichts anhängen # für die x-Achse listeZeit=[] listeZeitstempel=[] # für die y-Achse listeLeistung=[] listeEnergie=[] # -------------------------------- # SVG-Rohling festlegen # -------------------------------- # polyLineErsatz wird die transformierten Daten aufnehmen polyLineErsatz1='' polyLineErsatz2='' # mehrzeiliger String mit """ svgRahmen="""<svg width="800" height="400" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg"> <desc>Liniengrafik mit Daten aus 2024</desc> <!-- x und y Achse --> <line x1="50" y1="350" x2="750" y2="350" stroke="#0a0a0a" stroke-width="2" /> <line x1="50" y1="350" x2="50" y2="50" stroke="#0a0a0a" stroke-width="2" /> <!-- Beschriftung --> <!-- maximale Leistung --> <text x="50" y="40" fill="#000000" font-size="16" text-anchor="middle">#*3*#W</text> <!-- maximale Energie --> <text x="#*4*#" y="40" fill="#ff0000" font-size="16" text-anchor="right">#*5*#kWh</text> <!-- Startzeitpunkt --> <text x="50" y="370" fill="#000000" font-size="16" text-anchor="left">#*6*#</text> <!-- Endezeitpunkt --> <text x="#*4*#" y="370" fill="#000000" font-size="16" text-anchor="right">#*7*#</text> <!-- Polygonzug zeichnen --> <!-- für Leistung --> <polyline points="#*1*#" fill="none" stroke="black"/> <!-- für Energie --> <polyline points="#*2*#" fill="none" stroke="red"/> </svg> """ # --------------------------------------- # Stage 1 Daten laden von Internetquelle # (Daten Speichern wird hier aus didaktischen Gründen unerschlagen) # --------------------------------------- print('Stage 1 Daten von Internet-Quelle laden') # Nutzerbeglückung print() url='https://clabremo.de/shelly/WS/getshellycsvWS.php?shellyNr=192.168.1.81' print("Aufruf :", url) # Nutzerbeglückung response = requests.get(url) print("--> ",response) # Nutzerbeglückung # ----------------------------------- # Daten in Listen # ----------------------------------- # Rückgabeobjekt enthält den Inhalt der Internet-Ressource # Daraus eine Liste von Zeilen erzeugen datenstring=response.text zeilen=datenstring.split("\r\n") anzahlZeilen=len(zeilen) print("Anzahl Zeilen: ",anzahlZeilen) # Nutzerbeglückung # Jede Zeile in Datenfelder zerlegen und in separate Listen packen # Start mit 1, da in Zeile 0 die Spaltenbezeichnungen stehen zeileNr=1 # Leider hat Python kein schönes for-Konzept, also while-Schleife verwenden while zeileNr<(anzahlZeilen-1): werte=zeilen[zeileNr].split(';') # WARTUNG print(werte) # ACHTUNG: werte sind Strings, damit kann man nicht rechnen ... # also: in Ganzzahlen konvertieren mit int() listeZeitstempel.append(int(werte[2])) listeZeit.append(werte[3]) # Zeit lesbar, als String belassen listeLeistung.append(float(werte[4])) listeEnergie.append(float(werte[5])) zeileNr=zeileNr+1 # Nutzerbeglückung: Einen Teil der Listen ausgeben print('Ergebnis-Listen (Auszug)') print('..............................') print('Nr \t Zeit \t Leistung \t Energie') zeileNr=0 while zeileNr<anzahlZeilen and zeileNr<10: print(zeileNr, '\t', listeZeit[zeileNr], '\t', listeLeistung[zeileNr], '\t', listeEnergie[zeileNr]) zeileNr=zeileNr+1 zeileNr=anzahlZeilen-10 print('..............................') while zeileNr<(anzahlZeilen-2): print(zeileNr, '\t', listeZeit[zeileNr], '\t', listeLeistung[zeileNr], '\t', listeEnergie[zeileNr]) zeileNr=zeileNr+1 print ("Daten in Listen umkopiert.") # ------------------------ # Stage 2 Daten umformen / anpassen # ------------------------ # Es folgt ein mehrfach verketteter Dreisatz, um die Daten an den viewBox des SVG anzupassen # viewBox="0 0 800 400" # maximaler y-Wert: 50 # minimaler y-Wert: 400-50 # minimaler x-Wert: 50 # maximaler x-Wert: 800-50 yMin=50 yMax=350 yDelta=yMax-yMin xMin=50 xMax=750 xDelta=xMax-xMin # Aufbereiten der Daten, x-Werte müssen nur einmal umgerechnet werden xZeitstempel=[] yLeistung=[] yEnergie=[] # ein paar Hilfsdaten maxZeitstempel=max(listeZeitstempel) minZeitstempel=min(listeZeitstempel) deltaZeitstempel=3*24*60*60 # drei Tage maxLeistung=max(listeLeistung) maxEnergie=max(listeEnergie) print('maximale Leistung: ',maxLeistung,' Engergie geerntet: ',maxEnergie) # Nutzerbeglückung # ------------------------------------------ # Die folgenden Zeilen enthalten die eigentliche # "Magie" des Programms ... # ------------------------------------------ zeileNr=1 while zeileNr<(anzahlZeilen-2): xZeitstempel.append(xMin+int(round((listeZeitstempel[zeileNr]-minZeitstempel)/deltaZeitstempel*xDelta))) yLeistung.append(yMax-int(round(listeLeistung[zeileNr]/maxLeistung*yDelta))) yEnergie.append(yMax-int(round(listeEnergie[zeileNr]/maxEnergie*yDelta))) zeileNr=zeileNr+1 # Nutzerbeglückung: Einen Teil der Listen ausgeben print('Daten-Umformung (Auszug)') print('..............................') print('Nr \t Zeit \t Leistung \t Energie') zeileNr=0 while zeileNr<anzahlZeilen and zeileNr<10: print(zeileNr, '\t', xZeitstempel[zeileNr], '\t', yLeistung[zeileNr], '\t', yEnergie[zeileNr]) zeileNr=zeileNr+1 zeileNr=anzahlZeilen-10 print('..............................') while zeileNr<(anzahlZeilen-3): print(zeileNr, '\t', xZeitstempel[zeileNr], '\t', yLeistung[zeileNr], '\t', yEnergie[zeileNr]) zeileNr=zeileNr+1 print ("Daten an SVG angepasst.") # ------------------------ # Stage 3 svg modifizieren # ------------------------ # für jeden Wert einen Punkt erzeugen for i in range(1,anzahlZeilen-3): x_str=str(xZeitstempel[i]) yL_str=str(yLeistung[i]) yE_str=str(yEnergie[i]) polyLineErsatz1+=x_str+","+yL_str+" \r\n" polyLineErsatz2+=x_str+","+yE_str+" \r\n" # Nutzerbeglückung sys.stdout.write('.') print () # Kontrolle # print ("polyline-Ersatz") # print ("---------------") # print (polyLineErsatz1) # print (polyLineErsatz2) # letzter Schritt: Platzhalter in svg ersetzen svgOut=svgRahmen.replace("#*1*#",polyLineErsatz1) svgOut=svgOut.replace("#*2*#",polyLineErsatz2) svgOut=svgOut.replace("#*3*#",str(maxLeistung)) svgOut=svgOut.replace("#*4*#",str(max(xZeitstempel))) svgOut=svgOut.replace("#*5*#",str(listeEnergie[anzahlZeilen-3])) svgOut=svgOut.replace("#*6*#",listeZeit[1]) svgOut=svgOut.replace("#*7*#",listeZeit[anzahlZeilen-3]) # speichern # Datei öffnen, String hineinschreiben, schließen datei = open('liniengrafik.svg','w') datei.write(svgOut) datei.close() print ("Daten in linengrafik.svg gespeichert") print ("----- fertig -----")
Listing 1: Python Quelltext
C#
// Noch aus der Python-Datei: #!/usr/bin/env python3 // ------------------------------- // SVG-Liniengrafik // Datenvisualisierung mit Python >>> Umsetzung in C# / Dot Net // Claus Brell // 06.01.2024 >>> 08.01.2024 // ------------------------------- // Ablauf: // Stage 1: Daten laden von Internetquelle // Stage 2: Daten umformen / anpassen // Stage 3: SVG-Grafik erzeugen // ------------------------------- // Vorbereitungen // ------------------------------- // using System; //funktioniert auch so // using System.IO; //funktioniert auch so using System.Net; // entspricht Import für Internet-Zugriff // Das Folgende braucht man in Python nicht ... // ACHTUNG: Keinen expliziten namespace verwenden class Program { static void Main(string[] args) { // initialisieren, sonst kann man nichts anhängen // für die x-Achse // so gehts: List<double> wertedummy =new List<double>() {1,2,3,4,5,6}; // vorbelegen List<string> listeZeit=new List<string>(); List<int> listeZeitstempel=new List<int>(); // für die y-Achse List<double> listeLeistung=new List<double>(); List<double> listeEnergie=new List<double>(); // -------------------------------- // SVG-Rohling festlegen // -------------------------------- // polyLineErsatz wird die transformierten Daten aufnehmen string polyLineErsatz1=""; string polyLineErsatz2=""; // mehrzeiliger String mit (""" in Python), in Dot Net kein Problem string svgRahmen=@"<svg width=""800"" height=""400"" viewBox=""0 0 800 400"" xmlns=""http://www.w3.org/2000/svg""> <desc>Liniengrafik mit Daten aus 2024</desc> <!-- x und y Achse --> <line x1=""50"" y1=""350"" x2=""750"" y2=""350"" stroke=""#0a0a0a"" stroke-width=""2"" /> <line x1=""50"" y1=""350"" x2=""50"" y2=""50"" stroke=""#0a0a0a"" stroke-width=""2"" /> <!-- Beschriftung --> <!-- maximale Leistung --> <text x=""50"" y=""40"" fill=""#000000"" font-size=""16"" text-anchor=""middle"">#*3*#W</text> <!-- maximale Energie --> <text x=""#*4*#"" y=""40"" fill=""#ff0000"" font-size=""16"" text-anchor=""right"">#*5*#kWh</text> <!-- Startzeitpunkt --> <text x=""50"" y=""370"" fill=""#000000"" font-size=""16"" text-anchor=""left"">#*6*#</text> <!-- Endezeitpunkt --> <text x=""#*4*#"" y=""370"" fill=""#000000"" font-size=""16"" text-anchor=""right"">#*7*#</text> <!-- Polygonzug zeichnen --> <!-- für Leistung --> <polyline points=""#*1*#"" fill=""none"" stroke=""black""/> <!-- für Energie --> <polyline points=""#*2*#"" fill=""none"" stroke=""red""/> </svg> "; // --------------------------------------- // Stage 1 Daten laden von Internetquelle // (Daten Speichern wird hier aus didaktischen Gründen unterschlagen) // --------------------------------------- Console.WriteLine("Stage 1 Daten von Internet-Quelle laden");// Nutzerbeglückung string url="https://clabremo.de/shelly/WS/getshellycsvWS.php?shellyNr=192.168.1.81"; Console.WriteLine("Aufruf: "+url); // Nutzerbeglückung string response = ""; //Anlegen eines Webclients. Dieser übernimmt das Herunterladen der Daten. // Lassen Sie sich durch die "Veraltet-Warnung" nicht verblüffen WebClient myWebClient = new WebClient(); // Datenstrom zur URL aufmachen in zwei Schritten // WARTUNG Console.WriteLine("Url: "+url); Stream data=myWebClient.OpenRead(url); StreamReader reader=new StreamReader(data); // Kompletten Inhalt der Rückgabe in einen String einlesen Console.Write("Beginne Daten Einlesen ..."); response=reader.ReadToEnd(); Console.Write("Einlesen beendet, Enter drücken (1) ..."); // WARTUNG Console.ReadLine(); // Verbindung wieder schließen reader.Close(); data.Close(); Console.WriteLine("\r\nresponse: "); Console.WriteLine(response.Substring(0,512)); Console.WriteLine(response.Substring(response.Length-512)); // Nutzerbeglückung // ----------------------------------- // Daten in Listen // ----------------------------------- // Rückgabeobjekt enthält den Inhalt der Internet-Ressource // Daraus eine Liste von Zeilen erzeugen string[] zeilen=response.Split("\r\n"); // https://learn.microsoft.com/de-de/dotnet/api/system.string.split?view=net-8.0 int anzahlZeilen=zeilen.Length; Console.Write("Anzahl Zeilen: "); // Nutzerbeglückung Console.WriteLine(anzahlZeilen); // Nutzerbeglückung // Jede Zeile in Datenfelder zerlegen und in separate Listen packen // Start mit 1, da in Zeile 0 die Spaltenbezeichnungen stehen int zeileNr=1; // Leider hat Python kein schönes for-Konzept, also while-Schleife verwenden // Zum Vergleich hier vergleichbare Programmierung Console.WriteLine("Umformung... Enter drücken (2) ..."); // WARTUNG Console.ReadLine(); while (zeileNr<(anzahlZeilen-1)){ // WARTUNG Console.Write('.'); string[] werte=zeilen[zeileNr].Split(';'); // WARTUNG print(werte) // ACHTUNG: werte sind Strings, damit kann man nicht rechnen ... // also: in Ganzzahlen konvertieren mit int() listeZeitstempel.Add(Convert.ToInt32(werte[2])); //https://learn.microsoft.com/de-de/dotnet/api/system.convert.toint32?view=net-8.0 listeZeit.Add(werte[3]); // Zeit lesbar, als String belassen // ACHTUNG: eingedeutschtes DOT NET verwirft Punkt als Dezmaltrenner und rechnet falsch!!!!! listeLeistung.Add(Convert.ToDouble(werte[4].Replace(".",","))); // Korrigiert DOT NET Unsinn listeEnergie.Add(Convert.ToDouble(werte[5].Replace(".",","))); // Korrigiert DOT NET Unsinn zeileNr++; } // Ende while // Nutzerbeglückung: Einen Teil der Listen ausgeben Console.WriteLine("Ergebnis-Listen (Auszug) Enter drücken (3)"); // WARTUNG Console.ReadLine(); Console.WriteLine(".............................."); Console.WriteLine("Nr \t Zeit \t\t Leistung \t Energie"); zeileNr=0; while(zeileNr<anzahlZeilen & zeileNr<10){ Console.Write(zeileNr); Console.Write("\t"); Console.Write(listeZeit[zeileNr]); Console.Write("\t"); Console.Write(listeLeistung[zeileNr]); Console.Write("\t"); Console.WriteLine(listeEnergie[zeileNr]); zeileNr++; } zeileNr=anzahlZeilen-10; Console.WriteLine(".............................."); while (zeileNr<(anzahlZeilen-2)){ Console.Write(zeileNr); Console.Write("\t"); Console.Write(listeZeit[zeileNr]); Console.Write("\t"); Console.Write(listeLeistung[zeileNr]); Console.Write("\t"); Console.WriteLine(listeEnergie[zeileNr]); zeileNr++; } Console.WriteLine("Daten in Listen umkopiert."); // ------------------------ // Stage 2 Daten umformen / anpassen // ------------------------ // Es folgt ein mehrfach verketteter Dreisatz, um die Daten an den viewBox des SVG anzupassen // viewBox="0 0 800 400" // maximaler y-Wert: 50 // minimaler y-Wert: 400-50 // minimaler x-Wert: 50 // maximaler x-Wert: 800-50 int yMin=50; int yMax=350; int yDelta=yMax-yMin; int xMin=50; int xMax=750; int xDelta=xMax-xMin; // Aufbereiten der Daten, x-Werte müssen nur einmal umgerechnet werden List<int> xZeitstempel=new List<int>(); List<int> yLeistung=new List<int>(); List<int> yEnergie=new List<int>(); // ein paar Hilfsdaten int maxZeitstempel=listeZeitstempel.Max(); int minZeitstempel=listeZeitstempel.Min(); int deltaZeitstempel=3*24*60*60; // drei Tage double maxLeistung=listeLeistung.Max(); double maxEnergie=listeEnergie.Max(); Console.Write("kleinster Zeitstempel: "); Console.Write(minZeitstempel); // Nutzerbeglückung Console.Write(" größter Zeitstempel: "); Console.Write(maxZeitstempel); // Nutzerbeglückung Console.Write(" Delta: "); Console.WriteLine(deltaZeitstempel); // Nutzerbeglückung Console.Write("erster Zeitstempel: "); Console.Write(listeZeitstempel[0]); // Nutzerbeglückung Console.Write(" letzter Zeitstempel: "); Console.WriteLine(listeZeitstempel[anzahlZeilen-3]); // Nutzerbeglückung Console.Write("maximale Leistung: "); Console.Write(maxLeistung); // Nutzerbeglückung Console.Write(" Energie geerntet: "); // Nutzerbeglückung Console.WriteLine(maxEnergie); // Nutzerbeglückung // ------------------------------------------ // Die folgenden Zeilen enthalten die eigentliche // "Magie" des Programms ... // ------------------------------------------ zeileNr=0; while (zeileNr<(anzahlZeilen-2)){ xZeitstempel.Add(xMin+(int)Math.Round((double)(listeZeitstempel[zeileNr]-minZeitstempel)/(double)deltaZeitstempel*(double)xDelta)); yLeistung.Add(yMax-(int)Math.Round((double)(listeLeistung[zeileNr]/maxLeistung*yDelta))); yEnergie.Add(yMax-(int)Math.Round((double)(listeEnergie[zeileNr]/maxEnergie*yDelta))); // WARTUNG Console.WriteLine(zeileNr); // WARTUNG Console.WriteLine(xZeitstempel[zeileNr]); zeileNr++; } // Nutzerbeglückung: Einen Teil der Listen ausgeben Console.WriteLine("\r\nDaten-Umformung (Auszug)"); Console.WriteLine(".............................."); Console.WriteLine("Nr \t Zeit \t Leistung \t Energie"); zeileNr=0; while (zeileNr<anzahlZeilen & zeileNr<10){ Console.Write(zeileNr); Console.Write('\t'); Console.Write(xZeitstempel[zeileNr]); Console.Write('\t'); Console.Write(yLeistung[zeileNr]); Console.Write('\t'); Console.WriteLine(yEnergie[zeileNr]); zeileNr++; } zeileNr=anzahlZeilen-10; Console.WriteLine(".............................."); while (zeileNr<(anzahlZeilen-3)){ Console.Write(zeileNr); Console.Write('\t'); Console.Write(xZeitstempel[zeileNr]); Console.Write('\t'); Console.Write(yLeistung[zeileNr]); Console.Write('\t'); Console.WriteLine(yEnergie[zeileNr]); zeileNr++; } Console.WriteLine("Daten an SVG angepasst."); // ------------------------ // Stage 3 svg modifizieren // ------------------------ // für jeden Wert einen Punkt erzeugen string x_str, yL_str, yE_str; for (int i=1;i<(anzahlZeilen-3);i++){ x_str=xZeitstempel[i].ToString(); yL_str=yLeistung[i].ToString(); yE_str=yEnergie[i].ToString(); polyLineErsatz1+=x_str+","+yL_str+" \r\n"; polyLineErsatz2+=x_str+","+yE_str+" \r\n"; // Nutzerbeglückung sys.stdout.write('.') } // Kontrolle (noch Zeilen aus Python Programm) // print ("polyline-Ersatz") // print ("---------------") // print (polyLineErsatz1) // print (polyLineErsatz2) // letzter Schritt: Platzhalter in svg ersetzen string svgOut=svgRahmen.Replace("#*1*#",polyLineErsatz1); svgOut=svgOut.Replace("#*2*#",polyLineErsatz2); svgOut=svgOut.Replace("#*3*#",maxLeistung.ToString()); svgOut=svgOut.Replace("#*4*#",Convert.ToString(xZeitstempel.Max()-75)); // etwas nach links verschieben svgOut=svgOut.Replace("#*5*#",Convert.ToString(listeEnergie[anzahlZeilen-3])); svgOut=svgOut.Replace("#*6*#",listeZeit[1]); svgOut=svgOut.Replace("#*7*#",listeZeit[anzahlZeilen-3]); // speichern // Datei öffnen, String hineinschreiben, schließen // Das geht in C# einfacher als in Python File.WriteAllText("liniengrafikDotNet.svg", svgOut); Console.WriteLine("Daten in liniengrafikDotNet.svg gespeichert"); Console.WriteLine("----- fertig -----"); } // Ende main }// Ende class // Achtung: kein Ende namespace
Listing 3: C# Quelltext
Anhang
Quellen
SVG Grafiken erzeugen mit chatGPT
SVG Tutorials
Übersicht von Tutorials auf selfhtml: https://wiki.selfhtml.org/wiki/SVG/Tutorials
Ein HTML-Seminar, das SVG beleuchtet: https://www.html-seminar.de/svg-in-websites-nutzen.htm
Glossar
ETL-Prozess
Extract, Transform, Load (ETL) ist ein Prozess, bei dem Daten aus mehreren, oft unterschiedlich oder auch gar nicht strukturierten Datenquellen geladen, umgeformt und dann in einer Zieldatenbank – das kann auch eine einfache Datei sein – zusammengeführt werden. Eingesetzt werden ETL-Prozesse z.B. beim data-warehousing. Mittels ETL zusammengeführte Daten werde oft visualisiert, um sie einer schnellen Beurteilung zugänglich zu machen.
Autor und Lizenz
Autor: Prof. Dr. rer. nat. Claus Brell, aktuelle Projekte: Biene40, AI4Bee
Lizenz: CC BY
Inhalte des Beitrages können Sie entsprechen der Lizenz verwenden. Unter dieser Lizenz veröffentlichte Werke darf jedermann für private, gewerbliche und sonstige Zwecke nutzen verändern und auch neu ohne CC-Lizenz vermarkten. Als Urheber mache ich keine Rechte geltend.