java - 将多个实时图表从 XChart 添加到 JPanel

标签 java swing user-interface graph jtabbedpane

我负责用 Java 创建一个 GUI 来监测多个地区多人的心率(出于保密原因进行了修改)。

一共有8个区域,每个区域包含8个人。这些由 JTabbedPane 指示,每个区域 1 个选项卡。我正在尝试使用 XChart 库绘制并创建 8 个实时图表。数据将从文本文件中读取并作为两个 double 组传递。其中一个数组是以毫秒为单位的时间(x 轴),第二个数组是 1 人的心率数据(y 轴)。每个人都会在相应的区域选项卡中拥有自己的个人图表。

这类似于观看几张图表来监测医院的生命体征,以大致了解该应用程序应该模拟的内容。

我目前正在使用模型- View - Controller 架构。

TLDR; 为了分解它,我只是尝试在 JTabbedPane 内的 JPanel 内的选项卡内添加多个实时 XYChart。但目前来说,只需添加一个实时图表就可以了。

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(模型)

// 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};
}

资源和我看过的地方
http://knowm.org/how-to-make-real-time-charts-in-java/ :这个站点帮助我创建了实时图形的基类,但没有办法帮助将其集成到 JPanel 中,也没有办法通过 repaint() 或 update() 使其成为“实时”。并不是必须使用 repaint() 或 update() 。我几乎愿意接受任何建议。

当前环境
- NetBeans IDE 8.2(用于 GUI 生成器)
- Java 1.8.0_111
- XChart JAR (http://knowm.org/open-source/xchart/)

附加说明
我只是无法调用 getRandomRealTimeGraph() 来返回 JPanel 甚至 XChartPanel,因此我可以将其添加到 JTabbedPane。我希望有任何有用的建议!

如果这有帮助,则数据已经存在并且正在被解析为 2 个 double 组。但目前,我并不真正关心 x 数据和 y 数据所采用的内容。只要以后能替换掉,我就很高兴了。

非常感谢!

最佳答案

好吧,您需要退后一大步,思考您想要实现的目标,然后您需要弄清楚如何使其适用于一张图表,如果您可以使其适用于一张图表,你应该能够扩展它。

从模型的简单概念开始。模型应该以这样的方式抽象数据:无论数据来自文件、Web 服务、数据库、串行端口还是其他方式,解决方案的其他部分不应该关心,它们应该只关心关心以通用方式呈现的数据。

类似...

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

是的,每个图表都需要一个单独的模型,否则您将为每个图表呈现相同的数据。

现在,为了便于讨论,我只是简单地为您提供了代码并生成了一个简单的默认实现。

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;
    }

}

理论上,您应该能够创建尽可能多的实例,并且它应该呈现稍微随机的数据

现在,我们还需要两件事,我们需要某种方式来显示图表数据和某种方式来更新它。

为了更新我们的模型,我们需要某种方式来获取新数据,并以某种方式将其反馈给 UI,而不会阻塞事件调度线程

为此,我从一个充当桥梁的简单界面开始

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

然后,SwingWorker 使用它在后台请求新数据,并将该信息反馈给 EDT,以便可以安全地在屏幕上更新

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);
        }
    }

}

最后,我们需要离开来显示所有内容......

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();
    }

}

然后我们可以将其显示在我们自己的窗口中...

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);
            }
        });
    }
}

看看Concurrency in SwingWorker Threads and SwingWorker有关 Swing 和线程如何工作的更多详细信息

您可能还想回顾一下 Model-View-Controller范例

关于java - 将多个实时图表从 XChart 添加到 JPanel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42662611/

相关文章:

java - 如何将servlet发送的结果放入html输入框中?

java - 带有下拉菜单和文本输入的多部分 Java 表单

java - Swing:函数的执行顺序

winapi - 在对话框中,如何在编辑控件中按 Enter 键时阻止默认按钮获取焦点?

java - 当我尝试使用 Spring 和 file.properties 时,我看到错误

java - JVMTI GetLocalVariableTable() 总是给出 JVMTI_ERROR_ABSENT_INFORMATION

java - 如何让火柴人具有互动性?

java - 如何仅更改一个组件的工具提示颜色?

java - 使用 JToggleButton 实现内存游戏时计时器不启动

python - Qt 平台插件 'windows' - py2exe