简体   繁体   中英

Adding multiple real-time graphs from XChart onto a JPanel

I am in charge of creating a GUI in Java to monitor heart rates of multiple people over regions (modified for confidentiality reasons).

There are a total of 8 regions, and each region contains 8 people. These are indicated by a JTabbedPane, 1 tab for each region. I'm attempting to draw and create 8 real-time graphs using the XChart library. The data will be read from a text file and be passed as two double arrays. One of the arrays being time in milliseconds (x-axis) and the second array being the heart rate data for 1 person (y-axis). Each person will have their own individual graph within their corresponding regional tab.

This is similar to watching several graphs to monitor vitals in the hospital to get an overview of what this application is supposed to simulate.

I am currently using Model-View-Controller architecture.

TLDR; To break it down, I'm only attempting to add multiple real-time XYChart within a tab within a JPanel inside a JTabbedPane. But for the time being, just adding ONE real-time chart would be great.

View.java

private void initComponents() {
    jTabbedPane1 = new javax.swing.JTabbedPane();
    tabRegion1 = new javax.swing.JPanel();
    jTabbedPane1.setTabPlacement(javax.swing.JTabbedPane.BOTTOM);
    tabRegion1.setLayout(new java.awt.GridLayout(4, 2));

    // Populate the JPanel with empty static graphs
    for(int i = 0; i < Global.MAX_GRAPHS; i++)
        tabRegion1.add(Graph.getRandomStaticGraph());

    jTabbedPane1.addTab("Region 1", tabRegion1);
}

Graph.java (Models)

// Testing purposes
public static JPanel getRandomStaticGraph() {
    XYChart chart = getEmptyXYChart();

    XYSeries series = chart.addSeries("HeartRate", null, getRandomWalk(200));
    series.setMarker(SeriesMarkers.NONE);
    return new XChartPanel(chart);
}

// Testing purposes
public static JPanel getRandomRealTimeGraph() throws InterruptedException {
    XYChart chart = getEmptyXYChart();

    // Show it
    final SwingWrapper<XYChart> sw = new SwingWrapper<XYChart>(chart);
    sw.displayChart();

    double phase = 0;
    double[][] initdata = getSineData(phase);
    XYSeries series = chart.addSeries("HeartRate", null, getRandomWalk(200));
    while(true) {
        phase += 2 * Math.PI * 2 / 20.0;

        Thread.sleep(100);
        final double[][] data = getSineData(phase);

        chart.updateXYSeries("sine", data[0], data[1], null);
        sw.repaintChart();

    }

}
// To be used to generate random static graphs (testing purposes)
private static double[] getRandomWalk(int numPoints) {
    double[] y = new double[numPoints];
    y[0] = 0;
    for(int i = 1; i < y.length; i++)
        y[i] = y[i-1] + Math.random() - .5;
    return y;
}

// To be used to generate real time graphs (testing purposes)
private static double[][] getSineData(double phase) {
    double[] xData = new double[100];
    double[] yData = new double[100];
    for(int i = 0; i < xData.length; i++) {
        double radians = phase + (2 * Math.PI / xData.length * i);
        xData[i] = radians;
        yData[i] = Math.sin(radians);
    }
    return new double[][] {xData, yData};
}

Resources and Where I've Looked
http://knowm.org/how-to-make-real-time-charts-in-java/ : This site has helped me create a base class for a real-time graph, but no way to help integrate this within a JPanel or how to make it a "real-time" through repaint() or update(). Not that using repaint() or update() is necessary. I'm open to pretty much any suggestions.

Current Environment
- NetBeans IDE 8.2 (for the GUI generator)
- Java 1.8.0_111
- XChart JAR ( http://knowm.org/open-source/xchart/ )

Additional Notes
I'm just not able to call getRandomRealTimeGraph() to return a JPanel or even an XChartPanel so I can add that to the JTabbedPane. I would love any suggestions that would be helpful!

If this helps, the data already exists and is being parsed into 2 arrays of doubles. But for now, I don't really care what's being taken in as the x-data and the y-data. As long as I can substitute it out later, I'm happy with anything.

Thank you so much!

Okay, you need to take a big step back and think about what it is you want to achieve, you then need to figure out how you can make it work for one chart, if you can make it work for one chart, you should be able to scale it.

Start with a simple concept of a model. The model should abstract the data in such away that it doesn't matter if it's coming from a file, a web service, a database, serial port or some other means, the other parts of your solution shouldn't care, they should only care about the data been presented in a common manner.

Something like...

public interface HeartMonitorModel {
    public List<Double>[] getSineData();
    public List<Double> getWalkData();
}

And yes, you will need a separate model for each chart, otherwise you'll be presenting the same data for each.

Now for the sake of argument, I've simply taken you code and generate a simple default implementation.

public class DefaultHeartMonitorModel implements HeartMonitorModel {

    private double phase = 0;
    private int numPoints = 200;

    @Override
    public List[] getSineData() {
        phase += 2 * Math.PI * 2 / 20.0;
        List<Double> xData = new ArrayList<>(100);
        List<Double> yData = new ArrayList<>(100);
        for (int i = 0; i < 100; i++) {
            double radians = phase + (2 * Math.PI / 100 * i);
            xData.add(radians);
            yData.add(Math.sin(radians));
        }
        return new List[]{xData, yData};
    }

    @Override
    public List getWalkData() {
        List<Double> data = new ArrayList<>(numPoints);
        data.add(0.0);
        for (int i = 1; i < numPoints; i++) {
            double last = data.get(i - 1);
            data.add(last + + Math.random() - .5);
        }
        return data;
    }

}

In theory, you should be able to create as many instance of this and it should present slightly random data

Now, there are two more things we need, we need some way to display the charted data and some way to update it.

To update our model, we need someway to get the new data and some way to feed it back to the UI in way that doesn't block the Event Dispatching Thread

To this, I start with a simple interface which acts as bridge

public interface ChartMonitor {
    public HeartMonitorModel getModel();
    public void updateData(List<Double>[] data);
}

This is then used by a SwingWorker to ask for new data in the background and feed that information back to the EDT so it can safely be updated on the screen

public class UpdateWorker extends SwingWorker<Void, List<Double>[]> {

    private ChartMonitor monitor;
    private XYChart chart;

    public UpdateWorker(ChartMonitor monitor) {
        this.monitor = monitor;
    }

    @Override
    protected Void doInBackground() throws Exception {
        while (true) {
            Thread.sleep(100);
            publish(monitor.getModel().getSineData());
        }
    }

    @Override
    protected void process(List<List<Double>[]> chunks) {
        for (List<Double>[] data : chunks) {
            monitor.updateData(data);
        }
    }

}

Finally, we need away to display it all...

public class SeriesChartPane extends JPanel implements ChartMonitor {

    private HeartMonitorModel model;
    private XYChart chart;

    public SeriesChartPane(HeartMonitorModel model) {
        this.model = model;
        chart = new XYChartBuilder().width(400).height(200).title("Are you dead yet?").build();

        List<Double> walkData = model.getWalkData();
        chart.addSeries("Heart Rate", null, walkData);

        List<Double>[] sineData = model.getSineData();
        chart.addSeries("sine", sineData[0], sineData[1]);
        setLayout(new BorderLayout());

        XChartPanel<XYChart> chartPane = new XChartPanel<>(chart);
        add(chartPane);

        UpdateWorker worker = new UpdateWorker(this);
        worker.execute();
    }

    @Override
    public HeartMonitorModel getModel() {
        return model;
    }

    @Override
    public void updateData(List<Double>[] data) {
        chart.updateXYSeries("sine", data[0], data[1], null);
        repaint();
    }

}

And then we can display it in our own window...

public class HeartMonitor {

    public static void main(String[] args) {
        new HeartMonitor();
    }

    public HeartMonitor() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                HeartMonitorModel model = new DefaultHeartMonitorModel();

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new SeriesChartPane(model));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

Take a look at Concurrency in Swing and Worker Threads and SwingWorker for more details about how Swing and threading works

You might also want to do a refresher on the Model-View-Controller paradigm

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM