JFreeChart --> TimeSeries auslesen

Nisbo

Bekanntes Mitglied
Servus,

ich erstelle mehrere JInternalFrames auf einer JDesktopPane welche ich dann mit Charts fülle. Jetzt möchte ich das Ganze so umsetzen das sich die Charts auch von alleine aktualisieren (also ohne komplett alles neu erstellen mit removeAll und add, das funktioniert nämlich), dazu nutze ich einen SwingWorker mit
Java:
protected Void doInBackground() throws Exception {
            while (!this.isCancelled()) {
                startChartMonitoring();
                Thread.sleep(1000);
            }
            return null;
        }


Meine Idee ist alle Komponenten auf der DesktopPane zu durchlaufen und erst einmal die Frames raus suchen, das ist auch kein Problem. Dann suche ich auf den jeweiligen Frames nach den JFreeChart Komponenten (ist pro Frame jeweils nur einmal vorhanden)

Jetzt benötige ich die TimeSeries um denen dann die Werte hinzu zu fügen und hier hapert es. Es werden die Serien zwar gefunden aber der SwingWorker steigt kommentarlos aus und ruft gleich done() auf.

Jemand eine Idee ?

Java:
for(Component chartPanel : ((JInternalFrame) internalFrameToUse).getContentPane().getComponents()){
    // get the ChartPanel
    if(chartPanel instanceof ChartPanel){
        // get the Chart
        JFreeChart jFreeChart = ((ChartPanel) chartPanel).getChart();
       
        int i = 0;
        while(i < jFreeChart.getXYPlot().getDataset().getSeriesCount()){
            // get the TimeSeries(Collection)
            String timeSeriesName = jFreeChart.getXYPlot().getDataset().getSeriesKey(i).toString();
           
            // check if the READING is equal the name of the TimeSerie --> this work
            System.out.println("READING: " + result.getString("READING") + " - Name of the TimeSerie: " + timeSeriesName);
           
            if(result.getString("READING").equals(timeSeriesName)){
                System.out.println("Add or Update the value");
                // hier steigt der SwingWorker kommentarlos aus und ruft done() auf
                ((TimeSeries) jFreeChart.getXYPlot().getDataset().getSeriesKey(i)).addOrUpdate(new Second(myDate), result.getInt(6));
               
                System.out.println("Das wird nicht mehr angezeigt");
               
               
                if (result.getString("DEVICE").equals("Arbeitszimmer.Steckdose.PC")){
                    System.out.println(result.getString("TIMESTAMP") + " - READING: " + result.getString("READING") + " Value: " + result.getInt(6) +
                            " - Count: " + ((TimeSeries) jFreeChart.getXYPlot().getDataset().getSeriesKey(i)).getItemCount());
                }
            }
           
            i++;
        }
    }
}
 
So nach stundenlangen Suchen und probieren habe ich leider keine Lösung in der Art gefunden und habe mich halt selbst um eine Lösung gekümmert.

Also für alle welche auch das Problem haben

neue Klasse AvailableTimeSeries erstellen:
Java:
import java.util.ArrayList;
import org.jfree.data.time.TimeSeries;

public class AvailableTimeSeries {
    private String nameOfTheSet;
    private ArrayList<TimeSeries> timeSerie = new ArrayList<TimeSeries>();
   
    public AvailableTimeSeries(String nameOfTheSet){
        this.nameOfTheSet = nameOfTheSet;
    }

    public void addTimeSerie(TimeSeries timeSerie) {
        this.timeSerie.add(timeSerie);
    }

    public ArrayList<TimeSeries> getTimeSerie() {
        return timeSerie;
    }

    public String getNameOfTheSet() {
        return nameOfTheSet;
    }

    public void setNameOfTheSet(String nameOfTheSet) {
        this.nameOfTheSet = nameOfTheSet;
    }
}

Für eure Main Class oder wo auch immer ihr die TimeSeries braucht

Java:
private ArrayList<AvailableTimeSeries> timeSeriesMegaArray = new ArrayList<AvailableTimeSeries>();

Beim Aufruf eurer Methode welche das Monitoring der LiveDaten startet nicht vergessen die ArrayList zu clearen
Java:
this.timeSeriesMegaArray.clear();

Füttern mit den Daten (qName ist ein String mit dem Namen des Sets)

Java:
availableTimeSeries = new AvailableTimeSeries(qName);
TimeSeries ts1 = new TimeSeries("Serie 1");
TimeSeries ts1 = new TimeSeries("Serie 2");
TimeSeries ts1 = new TimeSeries("Serie 3");
availableTimeSeries.addTimeSerie(ts1);
availableTimeSeries.addTimeSerie(ts2);
availableTimeSeries.addTimeSerie(ts3);

this.timeSeriesMegaArray.add(availableTimeSeries);


Und so nutzen wir das Ganze dann, der String qName enthält wieder den Namen des Sets, result.getString("READING") in meinem Fall den Namen der TimeSerie
Java:
for(AvailableTimeSeries setOfTimeSeries : this.timeSeriesMegaArray){
    if(setOfTimeSeries.getNameOfTheSet().equals(qName)){
        for(TimeSeries timeSerie : setOfTimeSeries.getTimeSerie()){
            if(result.getString("READING").equals(timeSerie.getKey())){
                timeSerie.addOrUpdate(new Second(myDate), result.getInt(6));
            }
        }
    }
}

Hoffe irgendwer kann damit mal was anfangen, war ja doch eine etwas spezielle Frage gewesen. Evtl könnte man das Ganze auch mittels eines Streams lösen, k.A. ob das dann besser oder schneller wäre
 
Das automatische Aktualisieren von Daten ist bei JFreeChart immer mal wieder ein Thema. Ich habe mir deshalb mal den Thread durchgelesen, muss aber gestehen, dass ich deinen Anwendungsfall nicht nachvollziehen könnte. Dazu fehlt zu viel Kontext, z.B. wo qName herkommt und von welchem Objekt die getString-Methode aufgerufen wird.
Der Ansatz, die zu aktualisierenden TimeSeries in einem separaten Objekt abzuspeichern, anstatt sich immer durch die Swing-Komponenten und JFreeChart Objekthierarchie durchzukämpfen, ist schon mal richtig.
Der Fehler in deinem ersten Codeschnupsel dürfte daher kommen, dass eine ClassCastException fliegt. Keines von den Objekten, die bei deinem verketteten Methodenaufruf jemals zurückgeliefert werden, ist eine TimeSeries.
 
qName ist ein String welcher den Titel des InternalFrames enthält, ist gleichzeitig auch der Name des "Chart-Sets". Ich habe die ChartSets in einer CSV gespeichert welche dann beim Start des Programms eingelesen wird. Diese Charsets sind zusätzlich noch in einem Layout (auch csv) speicherbar.

Aufbau
Name vom ChartSet;Type;Devices;Readings
Wetter.Sensor;CUL_HM;Wetter.Sensor;dewpoint, humidity, temperature;

Sprich daraus wird dann ein Query für MySQL gefertigt
SELECT * FROM current WHERE TYPE=CUL_HM AND DEVICE=Wetter.Sensor and READING in (dewpoint, humidity, temperature)

getSting() geht somit auf das MySQL result und vergleicht ob das READING gleich der TimeSerie ist welche den selben Namen trägt.

Aber ich denke mal auch das es besser ist das Ganze über ein Objekt zu regeln wie ich es jetzt ja habe.

Hier mal ein lieblos 😀 erstelltes Video, ab ca Sekunde 35 siehst Du das mit dem Monitoring (automatischen Aktualisieren)

Das ganze Programm ist noch sehr ausbaufähig das ist mir bekannt 😉
 
Mit ist immer noch nicht klar, die die einzelnen Codeschnipsel zusammenarbeiten sollen. Dennoch habe ich ein paar Fragen/Anmerkungen:
1. Mir deucht, dass es statt deiner AvailableTimeSeries-Klasse eine HashMap <String,HashMap<String, TimeSeries>> tun müsste. An die gewünschte TimeSeries müsste man über map.get(qName).get(result.getString("READING")) kommen.
2. Wie oft fragst du die Datenbank ab?
3. Hast du Kontrolle darüber, dann die Daten in die db geschrieben werden?
 
2. momentan 1 mal pro Sekunde, werde ich aber noch einstellbar machen
3. nein nicht wirklich denn das wird durch die Homeautomatisierung FHEM übernommen
http://fhem.de/fhem_DE.html
Welche einmal auf Änderungen im angeschlossenen System reagiert und einmal in festlegbaren Intervallen, sprich Tür geht auf, Eintrag wird sofort geloggt. Lampe hat von 8-16 Uhr immer 60 Watt dann wird dafür nur alle 10 Minuten ein Eintrag erstellt, hat sie einmal 60,1 Watt dann wird sofort geloggt (wenn man das so einstellt)


Wegen der Eingangsfrage Deines Beitrages, evtl hilft Dir der Code ja besser beim Verständnis. Das if / else um die DB Abfrage bitte nicht beachten, hierfür brauchst DU nur den else Zweig welcher aus der current DB ausliest
Java:
// in qReading sind die Namen der Werte drin welche aus der READING Spalte der MySQL DB ausgelesen werden sollen
ArrayList<TimeSeries> series = new ArrayList<>();
     if(qReading.contains(",")){
       String[] splitti = qReading.split(",");
       for(String sss : splitti){
         if (updateFrame == false){
           TimeSeries ts = new TimeSeries(sss.trim());
           series.add(ts);
          
           availableTimeSeries.addTimeSerie(ts);
         }
        
         if(readings.length() > 0) readings = readings + ", ";
          readings = readings + "'" + sss.trim() + "'";
        
         if(value.length() > 0) value = value + " / ";
                  value = value + sss.trim();
       }
     }else{
       if (updateFrame == false){
         TimeSeries ts = new TimeSeries(qReading.trim());
         series.add(ts);
        
         availableTimeSeries.addTimeSerie(ts);
       }
       readings = "'"  + qReading.trim() + "'";
       value  = value + qReading.trim();
     }

value = qName;// + ": " + value;
    
     if (updateFrame == false){
       result = dbConnection.connect2db().executeQuery("select * from history WHERE "
           + "TYPE  = '" + qType + "' AND "
           + "DEVICE  = '" + qDevice + "' AND "
         + "READING IN(" + readings + ") AND "
         + "TIMESTAMP < '" + this.datePickerTo2.getDateTimeStrict().toString().replace("T", " ") + "' AND "
         + "TIMESTAMP > '" + this.datePickerFrom2.getDateTimeStrict().toString().replace("T", " ") + "' "
         + "ORDER BY TIMESTAMP ASC"
         );
     }else{
       result = dbConnection.connect2db().executeQuery("select * from current WHERE "
         + "TYPE  = '" + qType + "' AND "
         + "DEVICE  = '" + qDevice + "' AND "
       + "READING IN(" + readings + ") "
       );
     }
    
     while(result.next()){
       try {
         java.util.Date now = new Date();
        
         String strDate = standardDateFormat.format(now);
         myDate = standardDateFormat.parse(strDate);
       } catch (ParseException e) {
         e.printStackTrace();
       }

       if (updateFrame == true){
         for(AvailableTimeSeries setOfTimeSeries : this.timeSeriesMegaArray){
           if(setOfTimeSeries.getNameOfTheSet().equals(qName)){
             for(TimeSeries timeSerie : setOfTimeSeries.getTimeSerie()){
               if(result.getString("READING").equals(timeSerie.getKey())){
                 timeSerie.addOrUpdate(new Second(myDate), result.getInt(6));
               }
             }
           }
         }
       }else{
         for(TimeSeries s : series){
         if(result.getString("READING").equals(s.getKey())){
           s.addOrUpdate(new Second(myDate), result.getInt(6));
         }
      }
       }
     }
      
     dbConnection.disconnectFromDb();
 
Danke. Allmählich wirds klarer. Ich habe den Eindruck als wäre manches noch etwas unschön gelöst.
Der Sinn seiner AvailableTimeSeries Klasse erschliesst sich mit nach wie vor nicht. Sind die Namen der AvailableTimeSeries und die Keys der TimeSeries unique?
Statt if (aBoolean ==true) bzw. if (aBoolean==false) schreibt man einfach if (aBoolean) bzw. if (!aBoolean)
Verwende PreparedStatements und vermeide "Select *"
Mache qReading zu einem String []. Dann ist Fallunterscheidung im ersten if-else-Bock überflüssig. Aktuell wird nur durch das Vorhandensein von "," angezeigt, das qReading eigentlich aus mehreren Fragmenten besteht.
Das wiederholte Aufrufen von trim () auf denselben Strings ist ebenfalls unschön. Besser: einmal trimmen, dann mit dem getrimmten String weiterarbeiten.
Wenn ich keinen massiven Verständnisfehler habe, kannst du einfach myDate = new Date() schreiben und das temporäre Umwandeln in ein String inkl. exception handling sparen.
 
Sind die Namen der AvailableTimeSeries und die Keys der TimeSeries unique?
Ja

verwende PreparedStatements und vermeide "Select *"
OK hast recht, 4 Spalten benötige ich nicht wirklich

Statt if (aBoolean ==true) bzw. if (aBoolean==false) schreibt man einfach if (aBoolean) bzw. if (!aBoolean)
Die Frage ist jetzt ernst gemeint, gehört das in die Kategorie "Weil man das so macht" oder "Weil es schöner ausschaut" oder "weil das alle so machen" oder gibt es da einen technischen Grund dahinter ? Bitte die Frage nicht falsch verstehen, würde mich wirklich interessieren.

Das wiederholte Aufrufen von trim () auf denselben Strings ist ebenfalls unschön.
Die Trims können eigentlich raus, wird bei der Erstellung/Einlesen schon getrimmt

Wenn ich keinen massiven Verständnisfehler habe, kannst du einfach myDate = new Date() schreiben und das temporäre Umwandeln in ein String inkl. exception handling sparen.

Jetzt wo Du es sagst weiß ich auch nicht mehr warum ich das da drin habe, ich glaube da war zuerst Date nur von MySQL importiert gewesen, deswegen hatte ich dann auch
java.util.Date now = new Date();
geschrieben

Danke für die Hinweise 🙂
 
Nachteile von if (aBoolean == true)
1. Es ist unnötig.
2. Beim verketten von mehreren boolschen Ausdrücken wird das ganze sehr schnell unübersichtlich.
3. Es kann sehr leicht mit der Zuweisung if (aBoolean = true) verwechselt werden. Die Zuweisung ändert nicht nur möglicherweise den Wert von aBoolean, sondern ergibt auch immer true.
 
@Nisbo: Ausserdem ist es bei Verwendung vernünftiger Namen auch viel besser lesbar. Du hast es teilweise ja bereits so gemacht. Folgendes Beispiel aus deinem Code:
Java:
if (qReading.contains(",")) {
ist doch viel klarer als folgender Code:
Java:
if (qReading.contains(",")==true) {
 

Zurück
Oben