0

I have made a project that involves reading data from a serial line and representing it on a graph. So far, I've had my serial reader class do a .validate() on the dataset contained in my grapher class. The SerialReader class just got passed the whole grapher object and made changes directly to it. It also had to update a set of labels. That method worked.

The problem with this was that the logic was updating the model, which was stored in the GUI object. Since I'm making many new functions to the program, reading more serial lines for different types of data, and even controlling some hydraulics. So I needed more of a separated, modular approach.

I now have Main start an instance of Grapher, the GUI class, which when started with a button, makes a StrainTestObject and starts a SerialReader thread. The former is passed to the latter, and the StrainTestObject class holds my series and dataset, along with other metadata. When these are all created, I load the dataset from the StrainTestObject into the GUI with a getter.

Class diagram

When I do it this way, the data is correctly added to the dataset, but the graph on the GUI doesn't update! I think the source of my problem then is that Java is pass-by-value, and so it won't get updated. What's an elegant solution to this? Is there some way to keep the serial reader process separate from the model, and from the GUI? Also, bonus question, what is the most resource-effective way of redrawing the graph with JFreeGraph, it starts to go real slow after a while on a Raspberry Pi.

Here's the relevant code from the Grapher class:

private void startSerialReader()  {
    if (s != null) {
        if (s.comPort.isOpen()) { //Ensure port is ready
            s.closePort();
        }
    }
    s = new SerialReader(strainTestObject); //Make a new reader connection, give it access to data storage object
    new Thread(s).start();
    strainTestObject.removeAllSeries(); //reset the data if any, on start

    dataset = strainTestObject.getDataset();
    try {
        dataset.validateObject();
    } catch (InvalidObjectException e) {
        e.printStackTrace();
    }
    chart.getXYPlot().setDataset(strainTestObject.getDataset());

    labelCurrentValue.setText(String.valueOf(strainTestObject.getCurrentValue()));;
    labelOffsetValue.setText(String.valueOf(strainTestObject.getOffsetValue()));
    labelMaxValue.setText(String.valueOf(strainTestObject.getMaxValue()));
}

Here's the relevant code from the StrainTestObject class:

void addDataToGraph(double val) throws InvalidObjectException {
    invokeLater(() -> {
        series.add(new Millisecond(), val);
    });
}

From constructor

    series = new TimeSeries("Strekk");
    dataset = new TimeSeriesCollection();
    dataset.addSeries(series);

Now, if I alter the code to make series and dataset directly in the grapher class, and make SerialReader access it directly, it works fine. But it doesn't if it's kept in the StrainTestObject class.

Vincent Vega
  • 119
  • 10
  • Java passes objects by reference. However, some objects, like String objects are immutable. – NomadMaker Feb 10 '20 at 09:59
  • Java is pass by value, but the value is a reference to the data. What is the code you're using to update your graph. – matt Feb 10 '20 at 10:05
  • @NomadMaker right, I see now that just primitive types are passed by value. Well - that would make my approach workable, wouldn't it? – Vincent Vega Feb 10 '20 at 10:06
  • It's really pass-by-value for everything, but the value of an object is a reference. – NomadMaker Feb 10 '20 at 10:09
  • I would have to know more about your code to judge your approach. Sorry. – NomadMaker Feb 10 '20 at 10:10
  • 1
    Use [this approach](https://stackoverflow.com/a/13205322/230513) you can access the serial port in your implementation of `doInBackground()` without fear of blocking the UI thread. – trashgod Feb 10 '20 at 18:53
  • Thanks, @trashgod. I've read many of your answers lately, and I think that approach is ideal for me! Should I make a method PropertyChangeListener in Grapher for the StrainTestObject class, and check for changes in the series that way, or would the GUI actually listen for and update if I implement doInBackground()? My SerialReader class is doing the heavy lifting by regularly communicating and would be the one that needs to be a SwingWorker. – Vincent Vega Feb 11 '20 at 08:41
  • @SirVegardG: I'd recommend working through the [example](https://stackoverflow.com/a/13205322/230513), perhaps breaking on `ChartPanel::repaint` to see the event chain; if you have trouble subclassing `SwingWorker`, please [edit] your question to include a [mcve] that shows your revised approach. – trashgod Feb 13 '20 at 21:00

1 Answers1

1

As shown in this SwingWorker example, you can access the serial port in your implementation of doInBackground() without fear of blocking the UI thread. When your SerialReader gets new data, either by polling or responding to a call-back, invoke publish() with the intermediate result. Your implementation of process() will then see a List containing any results that have accumulated in the interim. Because it runs on the UI thread, process() can then safely update the chart's dataset, a TimeSeriesCollection. The listening XYPlot will update itself in response.

Should I make a method PropertyChangeListener in Grapher for the StrainTestObject class, and check for changes in the series…?

There should be no need; mutating the TimeSeriesCollection will fire a DatasetChangeEvent, to which the plot will respond; mutating any of the component TimeSeries will fire a SeriesChangeEvent, to which the enclosing collection will respond. In either case, the enclosing ChartPanel will schedule a repaint() when it sees the ensuing ChartChangeEvent.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045