Daten grafisch darstellen

Hallo Java-Experten!

Was wäre ein sinnvoller Weg, einen kontinuierlichen Datenstrom grafisch darzustellen?
Zur Zeit habe ich das so gelöst, dass ich ein Vector-Objekt erzeugt habe, das bei jedem SerialPortEvent ein Byte angehängt bekommt, das eine Zahl zwischen 0 und 255 repräsentiert.
Mittels einer selbst geschriebenen drawData-Methode zeichne ich in regelmäßigen Abständen die letzten paar hundert Daten mittels drawLine(…) in einen Grafikkontext. Diese drawData-Methode wird durch einen Thread alle 10ms aufgerufen.

Mit dem Profiler habe ich mir zeigen lassen, wieviele Objekte allokiert werden, und welche tatsächlich leben. Gerade bei der drawData-Methode ist die Diskrepanz zwischen allokierten Objekten/Speicher und wirklich existenten Objekten/belegten Speicher auffallend groß.

Bei jedem Aufruf führe ich ein paar hundert mal hintereinander eine drawLine-Methode auf, um einen Graph der ankommenden Daten auf den Bildschirm zu zeichnen.
Diese schreibe ich zunächst in einen Grafikkontext im Hintergrund, um anschließend mit drawImage() das Ganze darzustellen (Doublebuffering).

Ist dieses Vorgehen sinnvoll, und wenn nicht, wie würdet ihr das lösen, wenn ihr Daten über die serielle Schnittstelle bekommt, und diese während der Laufzeit wie bei einem Oszilloskop auf dem Bildschirm darstellen wollt.

Gruß

Michael

Moien

Mittels einer selbst geschriebenen drawData-Methode zeichne
ich in regelmäßigen Abständen die letzten paar hundert Daten
mittels drawLine(…) in einen Grafikkontext. Diese
drawData-Methode wird durch einen Thread alle 10ms aufgerufen.

10ms = 100x pro Sekunde. Ein normaler TFT schafft mit Mühe 50Hz. Geh mal auf 50 - 100ms, das past auch noch.

Mit dem Profiler habe ich mir zeigen lassen, wieviele Objekte
allokiert werden, und welche tatsächlich leben. Gerade bei der
drawData-Methode ist die Diskrepanz zwischen allokierten
Objekten/Speicher und wirklich existenten Objekten/belegten
Speicher auffallend groß.

Das hängt von deiner Implementierung ab. Zeig mal.

Ist dieses Vorgehen sinnvoll

Wenn man’s sauber programmiert: ja.

cu

Hallo Pumpkin!

Ich hätte fast darauf gewettet, dass du mir antwortest :smile:
Vielen Dank schon mal im Vorraus für deine Mühe!

Mittels einer selbst geschriebenen drawData-Methode zeichne
ich in regelmäßigen Abständen die letzten paar hundert Daten
mittels drawLine(…) in einen Grafikkontext. Diese
drawData-Methode wird durch einen Thread alle 10ms aufgerufen.

10ms = 100x pro Sekunde. Ein normaler TFT schafft mit Mühe
50Hz. Geh mal auf 50 - 100ms, das past auch noch.

Hast Recht, sieht bei 50 immer noch gut aus, bei 100 flackert es aber langsam.

Mit dem Profiler habe ich mir zeigen lassen, wieviele Objekte
allokiert werden, und welche tatsächlich leben. Gerade bei der
drawData-Methode ist die Diskrepanz zwischen allokierten
Objekten/Speicher und wirklich existenten Objekten/belegten
Speicher auffallend groß.

Das hängt von deiner Implementierung ab. Zeig mal.

Hier ein Ausschnitt Code:
storedata erhält seine Daten mittels append() bei jedem SerialPortEvent von der seriellen Schnittstelle. printvector nimmt bei jedem Aufruf der Methode das letzte Datum auf (idealerweise möchte ich später die letzten soundsoviel hundert Daten zeichnen, aber ich wollte es zunächst möglicht einfach probieren). Geschrieben wird das Ganze in TraceBuffer und wird am Ende in einem Panel gezeichnet.
Die for-Schleife soll die letzten x Daten als Linien-Plot zeichnen. Datacounter lasse ich mitzählen, damit nicht noch zusätzlich jedesmal die Größe des Vector-Objekts ermittelt werden muss (so zumindest die Idee). Vom letzten Element werden dann die vorausgegangenen x Werte gezeichnet.

Vector printvector = new Vector();

Graphics TraceBuffer, TracePanel;
Image TraceWnd;

TraceWnd = createImage(jTraceWindow.getWidth(), jTraceWindow.getHeight());
TraceBuffer = TraceWnd.getGraphics();
TracePanel = jTraceWindow.getGraphics();

...
...

public drawTraceWindow() {
 TraceBuffer.setColor(...)
 TraceBuffer.drawLine(...)
 i=0;
 try {
 printvector.add((Integer)aquisition.storedata.lastElement());
 datacounter++;
 int pointer = 0;
 for(pointer = datacounter-200; pointer 250) i=0;
 old = (Integer)printvector.get(pointer);
 }
 } catch (NoSuchElementException e) {}
 ....
 ...
 TracePanel.drawImage(TraceWnd, 0, 0, this);
}

Aufgerufen wird diese Funktion durch einen Thread alle 10ms. Grundsätzlich funktioniert das so, allerdings bemerke ich, wie sich die Darstellung mit wachsender Zahl von Elementen verzögert (der durchlaufende Graph erreicht mit der Zeit immer später den Wert, den ich der seriellen Schnittstelle zuführe (ich habe ein Poti, dass ich verstellen kann. Ein Mikrocontroller schiebt den A/D-gewandelten Wert dann rüber).
Am Anfang reagiert die Darstellung sofort, mit der Zeit aber verzögert.

Was mich auch stutzig macht ist folgendes:
Ich möchte den übermittelten Wert in einem Label darstellen. Dazu habe ich in die drawTraceWindow()-Methode folgendes eingefügt:

labelName.setText(Integer.toString(letztesElement));

Die Werte werden korrekt angezeigt, allerdings läuft der Graph im Fenster langsamer, wenn ich den Poti-Wert verändere, und wieder schneller, wenn ich den Wert konstant lasse.
Wie kann ich es erreichen, dass der Wert regelmäßig aktualisiert wird, ohne dass die Graphikdarstellung darunter leidet?

Ist dieses Vorgehen sinnvoll

Wenn man’s sauber programmiert: ja.

Dann vermute ich, es ist nicht sauber programmiert.
Kannst du (und andere natürlich auch) mir auf die Sprünge helfen?

Gruß

Michael

Moien

Grundsätzlich: wenn du mit int, float, double, byte & Co arbeitest dann lass Vector aus dem Spiel. Oder fass die Daten zuerst in Arrays zusammen und schieb dann erst in einen Vector. Die Umwandelung von int in Integer kostet jedesmal eine Objekterzeugung. In int[4096] verpackt erzeugen 4KB Daten nur noch ein Objekt. Und Vector ist nun wirklich nicht das schnellste. Arraylist wäre besser.

Dann solltest du auf java 1.5/5.0 umsteigen. Vector ist besser als Vector + ständiges casten.

storedata erhält seine Daten mittels append() bei jedem
SerialPortEvent von der seriellen Schnittstelle. printvector
nimmt bei jedem Aufruf der Methode das letzte Datum auf
(idealerweise möchte ich später die letzten soundsoviel
hundert Daten zeichnen, aber ich wollte es zunächst möglicht
einfach probieren).

Deine aktuelle Taktik zeigt wie lange ein Datum aktuell war. Könnte auch sinnvoll sein, hängt von der Anwendung ab.

Die for-Schleife soll die letzten x Daten als Linien-Plot
zeichnen. Datacounter lasse ich mitzählen, damit nicht noch
zusätzlich jedesmal die Größe des Vector-Objekts ermittelt
werden muss (so zumindest die Idee).

Diese Microoptimierung bringt dir garnix wenn du jedes Objekt 2x casten musst…

Was den Tracer wahrscheinlich ausflippen läst ist das try-catch. Die ersten 200 Durchläufe werden mit einer Exception (mindestens eine sinnfreie Objekterzeugung) beendet.

printvector.add((Integer)aquisition.storedata.lastElement());

Der Cast ist unnötig. Und der Zugriff könnte tödlich enden. Sowohl der Serial-Thread als auch dein redraw-thread greifen auf ein Objekt zu. In der Theorie muss da ein synchronize eingesetzt werden (Es wird funktionieren solange der Datenspeicher nur wächst. Aber wehe das Teil wird irgendwann kleiner). Allerdings verzögert das die Sache weiter.

for(pointer = datacounter-200; pointer 250) i=0;

Grundsätzlich funktioniert das so, allerdings bemerke ich, wie
sich die Darstellung mit wachsender Zahl von Elementen
verzögert (der durchlaufende Graph erreicht mit der Zeit immer
später den Wert, den ich der seriellen Schnittstelle zuführe

Da müsste man mal genauer nachkucken wie lange das Malen dauert und wann genau die Werte in aquisition.storedata ankommen.

Was mich auch stutzig macht ist folgendes:
Ich möchte den übermittelten Wert in einem Label darstellen.
Dazu habe ich in die drawTraceWindow()-Methode folgendes
eingefügt:

labelName.setText(Integer.toString(letztesElement));

Das erzeugt ein Stringobjekt und einiges als Aktionen/redraw im JLabel (u.a. String.intern() und String.compare()). Für sowas würde ich zu Graphics.drawString greifen.

Die Werte werden korrekt angezeigt, allerdings läuft der Graph
im Fenster langsamer, wenn ich den Poti-Wert verändere, und
wieder schneller, wenn ich den Wert konstant lasse.

Die JLabel-Updates verbraten unheimlich viel Zeit. Die Updates checken allerdings ob sich der Text geändert hat oder nicht. Bei konstanten Werten läuft es also flüssiger.

Dann vermute ich, es ist nicht sauber programmiert.

Der Code hier past schonmal ganz gut. Das mit den JLabels würde ich ändern. TimerTask mit Timer.scheduleAtFixedRate könnte man auch verbauen.

cu

1 Like

Hallo pumpkin!

Vielen Dank für deine Hilfe. Ich werde erst am Wochenende dazu kommen, deine Vorschläge umzusetzen, werde mich dann aber sicherlich nochmal melden :smile:

Gruß

Michael

Ok, vielleicht etwas zu spät, aber hast Du schonmal http://www.jfree.org/jfreechart/ ausprobiert? Die haben da auch ein Dataset für schnelle Daten, das hört sich genau richtig für Dich an.