【问题标题】:Line Chart Live update折线图实时更新
【发布时间】:2014-04-01 02:13:40
【问题描述】:

实时更新折线图

我想修改这个简单的折线图示例并添加实时更新。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;


public class LineChartSample extends Application {

    @Override public void start(Stage stage) {
        stage.setTitle("Line Chart Sample");
        //defining the axes
        final NumberAxis xAxis = new NumberAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Number of Month");
        //creating the chart
        final LineChart<Number,Number> lineChart = 
                new LineChart<Number,Number>(xAxis,yAxis);

        lineChart.setTitle("Stock Monitoring, 2010");
        //defining a series
        XYChart.Series series = new XYChart.Series();
        series.setName("My portfolio");
        //populating the series with data
        series.getData().add(new XYChart.Data(1, 23));
        series.getData().add(new XYChart.Data(2, 14));
        series.getData().add(new XYChart.Data(3, 15));
        series.getData().add(new XYChart.Data(4, 24));
        series.getData().add(new XYChart.Data(5, 34));
        series.getData().add(new XYChart.Data(6, 36));
        series.getData().add(new XYChart.Data(7, 22));
        series.getData().add(new XYChart.Data(8, 45));
        series.getData().add(new XYChart.Data(9, 43));
        series.getData().add(new XYChart.Data(10, 17));
        series.getData().add(new XYChart.Data(11, 29));
        series.getData().add(new XYChart.Data(12, 25));

        Scene scene  = new Scene(lineChart,800,600);
        lineChart.getData().add(series);

        stage.setScene(scene);
        stage.show();
    }

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

例如我想得到这个结果:

http://dev.sencha.com/deploy/ChartsDemo/examples/chart/Live%20Animated.html

如何修改折线图以获得相同的结果?有没有例子?

P.S 我测试了这个例子来动画折线图

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;


public class MainApp extends Application {

    private static final int MAX_DATA_POINTS = 50;
    private int xSeriesData = 0;
    private XYChart.Series series1;
    private XYChart.Series series2;
    private XYChart.Series series3;
    private ExecutorService executor;
    private AddToQueue addToQueue;
    private ConcurrentLinkedQueue<Number> dataQ1 = new ConcurrentLinkedQueue<Number>();
    private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<Number>();
    private ConcurrentLinkedQueue<Number> dataQ3 = new ConcurrentLinkedQueue<Number>();

    private NumberAxis xAxis = new NumberAxis();
    final NumberAxis yAxis = new NumberAxis();
    final LineChart<Number,Number> lineChart =
                new LineChart<Number,Number>(xAxis,yAxis);

    private void init(Stage primaryStage) {
        xAxis = new NumberAxis(0,MAX_DATA_POINTS,MAX_DATA_POINTS/10);
        xAxis.setForceZeroInRange(false);
        xAxis.setAutoRanging(false);

        xAxis.setTickLabelsVisible(false);
        xAxis.setTickMarkVisible(false);
        xAxis.setMinorTickVisible(false);


        NumberAxis yAxis = new NumberAxis();
        yAxis.setAutoRanging(true);

        //-- Chart
        final LineChart<Number, Number> sc = new LineChart<Number, Number>(xAxis, yAxis) {
            // Override to remove symbols on each data point
            @Override protected void dataItemAdded(Series<Number, Number> series, int itemIndex, Data<Number, Number> item) {}
        };
        sc.setAnimated(false);
        sc.setId("liveLineeChart");
        sc.setTitle("Animated Line Chart");

        //-- Chart Series
        series1 = new XYChart.Series<Number, Number>();
        series2 = new XYChart.Series<Number, Number>();
        series3 = new XYChart.Series<Number, Number>();
        lineChart.getData().addAll(series1, series2, series3);

        series1.setName("Test 1");
        series2.setName("Test 2");
        series3.setName("Test 3");



        primaryStage.setScene(new Scene(lineChart));
    }





    @Override public void start(Stage stage) {
        stage.setTitle("Animated Line Chart Sample");
        init(stage);
        stage.show();


        executor = Executors.newCachedThreadPool(new ThreadFactory() {
            @Override public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                return thread;
            }
        });
        addToQueue = new AddToQueue();
        executor.execute(addToQueue);
        //-- Prepare Timeline
        prepareTimeline();


    }

    private class AddToQueue implements Runnable {
        @Override
        public void run() {
            try {
                // add a item of random data to queue
                dataQ1.add(Math.random());
                dataQ2.add(Math.random());
                dataQ3.add(Math.random());

                Thread.sleep(1000);
                executor.execute(this);
            } catch (InterruptedException ex) {
                Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

  //-- Timeline gets called in the JavaFX Main thread
    private void prepareTimeline() {
        // Every frame to take any data from queue and add to chart
        new AnimationTimer() {
            @Override public void handle(long now) {
                addDataToSeries();
            }
        }.start();
    }

    private void addDataToSeries() {
        for (int i = 0; i < 20; i++) { //-- add 20 numbers to the plot+
            if (dataQ1.isEmpty()) break;
            series1.getData().add(new AreaChart.Data(xSeriesData++, dataQ1.remove()));
            series2.getData().add(new AreaChart.Data(xSeriesData++, dataQ2.remove()));
            series3.getData().add(new AreaChart.Data(xSeriesData++, dataQ3.remove()));
        }
        // remove points to keep us at no more than MAX_DATA_POINTS
        if (series1.getData().size() > MAX_DATA_POINTS) {
            series1.getData().remove(0, series1.getData().size() - MAX_DATA_POINTS);
        }
        if (series2.getData().size() > MAX_DATA_POINTS) {
            series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS);
        }
        if (series3.getData().size() > MAX_DATA_POINTS) {
            series3.getData().remove(0, series3.getData().size() - MAX_DATA_POINTS);
        }
        // update
        xAxis.setLowerBound(xSeriesData-MAX_DATA_POINTS);
        xAxis.setUpperBound(xSeriesData-1);
    }


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

代码不能正常工作,因为只添加了新值。旧值不会被破坏。

【问题讨论】:

    标签: javafx javafx-2 javafx-8


    【解决方案1】:

    基于AnimatedAreaChart上的JewelSea's示例,我对其进行了修改,以基于LineGraph为您制作类似的示例。请看示例,希望能满足您的需求!

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.chart.LineChart;
    import javafx.scene.chart.NumberAxis;
    import javafx.scene.chart.XYChart;
    import javafx.stage.Stage;
    
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    
    public class AnimatedLineChart extends Application {
    
        private static final int MAX_DATA_POINTS = 50;
        private int xSeriesData = 0;
        private XYChart.Series<Number, Number> series1 = new XYChart.Series<>();
        private XYChart.Series<Number, Number> series2 = new XYChart.Series<>();
        private XYChart.Series<Number, Number> series3 = new XYChart.Series<>();
        private ExecutorService executor;
        private ConcurrentLinkedQueue<Number> dataQ1 = new ConcurrentLinkedQueue<>();
        private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<>();
        private ConcurrentLinkedQueue<Number> dataQ3 = new ConcurrentLinkedQueue<>();
    
        private NumberAxis xAxis;
    
        private void init(Stage primaryStage) {
    
            xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 10);
            xAxis.setForceZeroInRange(false);
            xAxis.setAutoRanging(false);
            xAxis.setTickLabelsVisible(false);
            xAxis.setTickMarkVisible(false);
            xAxis.setMinorTickVisible(false);
    
            NumberAxis yAxis = new NumberAxis();
    
            // Create a LineChart
            final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis) {
                // Override to remove symbols on each data point
                @Override
                protected void dataItemAdded(Series<Number, Number> series, int itemIndex, Data<Number, Number> item) {
                }
            };
    
            lineChart.setAnimated(false);
            lineChart.setTitle("Animated Line Chart");
            lineChart.setHorizontalGridLinesVisible(true);
    
            // Set Name for Series
            series1.setName("Series 1");
            series2.setName("Series 2");
            series3.setName("Series 3");
    
            // Add Chart Series
            lineChart.getData().addAll(series1, series2, series3);
    
            primaryStage.setScene(new Scene(lineChart));
        }
    
    
        @Override
        public void start(Stage stage) {
            stage.setTitle("Animated Line Chart Sample");
            init(stage);
            stage.show();
    
    
            executor = Executors.newCachedThreadPool(new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    return thread;
                }
            });
    
            AddToQueue addToQueue = new AddToQueue();
            executor.execute(addToQueue);
            //-- Prepare Timeline
            prepareTimeline();
        }
    
        private class AddToQueue implements Runnable {
            public void run() {
                try {
                    // add a item of random data to queue
                    dataQ1.add(Math.random());
                    dataQ2.add(Math.random());
                    dataQ3.add(Math.random());
    
                    Thread.sleep(500);
                    executor.execute(this);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
    
        //-- Timeline gets called in the JavaFX Main thread
        private void prepareTimeline() {
            // Every frame to take any data from queue and add to chart
            new AnimationTimer() {
                @Override
                public void handle(long now) {
                    addDataToSeries();
                }
            }.start();
        }
    
        private void addDataToSeries() {
            for (int i = 0; i < 20; i++) { //-- add 20 numbers to the plot+
                if (dataQ1.isEmpty()) break;
                series1.getData().add(new XYChart.Data<>(xSeriesData++, dataQ1.remove()));
                series2.getData().add(new XYChart.Data<>(xSeriesData++, dataQ2.remove()));
                series3.getData().add(new XYChart.Data<>(xSeriesData++, dataQ3.remove()));
            }
            // remove points to keep us at no more than MAX_DATA_POINTS
            if (series1.getData().size() > MAX_DATA_POINTS) {
                series1.getData().remove(0, series1.getData().size() - MAX_DATA_POINTS);
            }
            if (series2.getData().size() > MAX_DATA_POINTS) {
                series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS);
            }
            if (series3.getData().size() > MAX_DATA_POINTS) {
                series3.getData().remove(0, series3.getData().size() - MAX_DATA_POINTS);
            }
            // update
            xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
            xAxis.setUpperBound(xSeriesData - 1);
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    注意我没有考虑更新 X 轴上的值,你可以这样做!

    【讨论】:

    • 是的!谢谢你。只剩下一个小细节我需要问:我怎样才能让动画更流畅?例如,当添加新值时,我想更平滑地移动图表位置。
    • 我测试了通过将图表更新为 LineChart 来为图表设置动画。不幸的是,旧值没有更新。只添加一个新的。
    • @PeterPenzov 在我的示例中,LineChartLineChart&lt;Number, Number&gt;。你想改变什么?
    • 您希望 y 轴随时间变化?
    • 我想让它看起来像这样:dev.sencha.com/deploy/ChartsDemo/examples/chart/… 看看旧值是如何被破坏的。
    【解决方案2】:

    这是一个例子:

    public class AnimatedLineChart extends Application {
    
    private LineChart<Number, Number> chart;
    
    private XYChart.Series<Number, Number> dataSeries;
    
    private NumberAxis xAxis;
    
    private Timeline animation;
    
    private double sequence = 0;
    
    private double y = 10;
    
    private final int MAX_DATA_POINTS = 25, MAX = 10, MIN = 5;;
    
    public AnimatedLineChart() {
    
        // create timeline to add new data every 60th of second
        animation = new Timeline();
        animation.getKeyFrames()
                .add(new KeyFrame(Duration.millis(1000), 
                        (ActionEvent actionEvent) -> plotTime()));
        animation.setCycleCount(Animation.INDEFINITE);
    }
    
    public Parent createContent() {
    
        xAxis = new NumberAxis(0, MAX_DATA_POINTS + 1, 2);
        final NumberAxis yAxis = new NumberAxis(MIN - 1, MAX + 1, 1);
        chart = new LineChart<>(xAxis, yAxis);
    
        // setup chart
        chart.setAnimated(false);
        chart.setLegendVisible(false);
        chart.setTitle("Animated Line Chart");
        xAxis.setLabel("X Axis");
        xAxis.setForceZeroInRange(false);
    
        yAxis.setLabel("Y Axis");
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "$", null));
    
        // add starting data
        dataSeries = new XYChart.Series<>();
        dataSeries.setName("Data");
    
        // create some starting data
        dataSeries.getData()
                .add(new XYChart.Data<Number, Number>(++sequence, y));
    
        chart.getData().add(dataSeries);
    
        return chart;
    }
    
    private void plotTime() {
        dataSeries.getData().add(new XYChart.Data<Number, Number>(++sequence, getNextValue()));
    
        // after 25hours delete old data
        if (sequence > MAX_DATA_POINTS) {
            dataSeries.getData().remove(0);
        }
    
        // every hour after 24 move range 1 hour
        if (sequence > MAX_DATA_POINTS - 1) {
            xAxis.setLowerBound(xAxis.getLowerBound() + 1);
            xAxis.setUpperBound(xAxis.getUpperBound() + 1);
        }
    }
    
    private int getNextValue(){     
        Random rand = new Random();
        return rand.nextInt((MAX - MIN) + 1) + MIN;     
    }
    
    public void play() {
        animation.play();
    }
    
    @Override
    public void stop() {
        animation.pause();
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(createContent()));
        primaryStage.setTitle("Animated Line Chart");
        primaryStage.show();
        play();
    }
    
    /**
     * 
     * Java main for when running without JavaFX launcher
     * 
     */
    public static void main(String[] args) {
        launch(args);
    }
    
    }
    

    您可以在here 中找到更多示例。

    希望这会有所帮助! :)

    【讨论】: