如何使用JavaFX(使用JavaFX)实时更新XYChart? [英] How do I update a XYChart in realtime in Java (using JavaFX)?

查看:72
本文介绍了如何使用JavaFX(使用JavaFX)实时更新XYChart?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用JavaFX,更具体地说是JavaFX SDK中的散点图(XYChart),创建Java中不同排序算法的可视化.我的想法是,每次在进行排序算法交换后,都会重新绘制散点图,我从简单版本的insertingSort开始,因为它似乎是最容易实现的算法.

I am creating a visualization of different sorting algos in Java using JavaFX and more specifically a scatterplot (XYChart) from the JavaFX SDK. My idea is that the scatterplot will be redrawn each time after a swap in the sorting algorithm has been conducted, I started with a simple version of insertionSort because it seemed the easiest algorithm to implement.

我的问题是,当我开始排序时,程序将锁定直到排序已完全完成,然后重新绘制图形才完全完成.我希望它在每一步都重新绘制图形!我添加了一个Thread.sleep(x),然后切换到TimeUnit.milliseconds.sleep(x)尝试添加延迟,但可惜的是,这并不能解决问题,它只是增加了程序锁定的时间,并且没有重新绘制图形,直到排序完成.有没有一种方法可以做我想要的事情而无需从JavaFX切换框架?

My problem is that when I start the sort the program locks up until the sort has been completely finished and redraws the graphic completely finished. I would like it to redraw the graph at every step! I added a Thread.sleep(x) then switched to TimeUnit.milliseconds.sleep(x) to try and add in a delay but alas this has not fixed the issue, it has just increased the time the program is locked up and doesn't redraw the graph until after the sort is completed. Is there a way to do what I want without switching frameworks away from JavaFX?

package sample;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import jdk.jfr.Event;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class Main extends Application {

    Button sortBtn = new Button();

    @Override
    public void start(Stage primaryStage) throws Exception {
        //Creating Bar Graph
        int size = 100;
        int maxValue = 100;
        int windowX = 1920;
        int windowY = 1020;

        int[] unsortedArray = createUnsortedArray(size, maxValue);

        NumberAxis xAxis = new NumberAxis();
        xAxis.setLabel("Position");

        NumberAxis yAxis = new NumberAxis();
        yAxis.setLabel("Value");

        XYChart.Series < Number, Number > graph = new XYChart.Series < > ();
        for (int i = 0; i < size; i++) {
            graph.getData().add(new XYChart.Data < > (i, unsortedArray[i]));
        }

        ScatterChart < Number, Number > scatterChart = new ScatterChart < > (xAxis, yAxis);
        scatterChart.setTitle("Unsorted Array");

        scatterChart.getData().addAll(graph);
        scatterChart.setPrefSize(windowX - 100, windowY);
        //End Creating Bar Graph
        primaryStage.setTitle("Sort Visualizer");
        GridPane layout = new GridPane();
        layout.getChildren().add(0, scatterChart);
        sortBtn = new Button("Sort");
        layout.getChildren().add(1, sortBtn);
        Scene scene = new Scene(layout, windowX, windowY);
        primaryStage.setScene(scene);
        primaryStage.show();

        sortBtn.setOnAction(actionEvent - > {
            try {
                insertionSort(graph, unsortedArray);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public void insertionSort(XYChart.Series < Number, Number > graph, int[] unsortedArray) throws InterruptedException {
        int lowPos = 0;
        int swappedValue = 0;
        for (int i = 0; i < unsortedArray.length; i++) {
            lowPos = i;
            for (int j = i; j < unsortedArray.length; j++) {
                if (unsortedArray[j] < unsortedArray[lowPos]) {
                    lowPos = j;
                }
            }
            //Swap lowPos value with i
            swappedValue = unsortedArray[i];
            unsortedArray[i] = unsortedArray[lowPos];
            unsortedArray[lowPos] = swappedValue;
            updateGraph(graph, unsortedArray);
            TimeUnit.MILLISECONDS.sleep(100);
        }
    }

    public void updateGraph(XYChart.Series < Number, Number > graph, int[] updatedArray) {
        graph.getData().clear();
        for (int i = 0; i < updatedArray.length; i++) {
            graph.getData().add(new XYChart.Data < > (i, updatedArray[i]));
        }
    }

    int[] createUnsortedArray(int size, int maxValue) {
        int[] unsortedArray = new int[size];
        Random randy = new Random();
        for (int i = 0; i < size; i++) {
            unsortedArray[i] = Math.abs(randy.nextInt() % maxValue);
        }
        return unsortedArray;
    }


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

还为我的代码草率而事先表示歉意,我只是希望这会是使tracerbullet容易工作的好方法,然后我希望以后再偿还技术债务(我知道是坏习惯)

Also apologies in advance for how sloppy my code is, I was just hoping this would be an easy tracerbullet to get something in working condition and then I was hoping to repay the technical debt later (bad habit I know)

推荐答案

JavaFX应用程序线程负责呈现UI和处理事件.如果事件处理程序需要很长时间才能运行,则在处理程序完成之前无法呈现UI.因此,永远不要在事件处理程序(或在FX Application Thread上执行的代码的任何位置)中执行长期运行的任务(包括睡眠).

The JavaFX application thread is responsible for rendering the UI and handling events. If your event handler takes a long time to run, then the UI can't be rendered until the handler completes. Consequently, you should never execute long-running tasks (including sleeping) in an event handler (or anywhere in code that is executed on the FX Application Thread).

在您的示例中,您想要在特定时间点更新UI,并且为每个步骤执行的代码不需要花费很长时间即可运行.在这种情况下,最好的方法是动画,例如Timeline.重构代码需要花费一些工作,以便可以单独运行迭代的每个步骤,但是最干净的方法是创建一个表示排序的类,并使用一次执行一个步骤的方法:

In your example, you want to update the UI at specific time points, and the code to execute for each individual step does not take a long time to run. In this case, the best approach is an animation, such as a Timeline. It takes a little work to refactor your code so that each step of the iteration can be run individually, but the cleanest approach is to create a class that represents the sort, with a method to perform one step at a time:

private static class InsertionSort {
    private int lowPos = 0;
    private int[] data ;
    
    private int i ; // current iteration
    
    InsertionSort(int[] unsortedArray) {
        this.data = unsortedArray ;
    }
    
    private boolean isDone() {
        return i >= data.length ;
    }
    
    private int[] nextStep() {

        if (isDone()) throw new IllegalStateException("Sorting is complete");

        lowPos = i;
        for (int j = i; j < data.length; j++) {
            if(data[j] < data[lowPos]){
                lowPos = j;
            }
        }
        //Swap lowPos value with i
        int swappedValue = data[i];
        data[i] = data[lowPos];
        data[lowPos] = swappedValue;
        i++ ;
        return data ;
    }
    
}

(顺便说一句,由于您说过要创建多种排序算法的可视化效果,因此这很适合您的应用程序设计.使用nextStep()isDone()方法创建一个Sort接口,然后创建一个单独的接口类,用于针对不同算法实现该接口的类.您可以将它们放在List<Sort>ComboBox<Sort>中,以便用户可以选择一个,等等.

(As an aside, since you said you want to create visualizations of multiple sorting algorithms, this should lend itself nicely to your application design. Create a Sort interface with nextStep() and isDone() methods, then create individual classes implementing that interface for the different algorithms. You can put them in a List<Sort> or ComboBox<Sort> so the user can choose one, etc.)

现在,您的事件处理程序可以创建时间轴,该时间轴执行排序的单个步骤并在指定的时间点更新图形:

Now your event handler can create a timeline which performs a single step of the sort and updates the graph at specified timepoints:

public void insertionSort(XYChart.Series<Number, Number> graph, int[] unsortedArray) throws InterruptedException {

    InsertionSort sort = new InsertionSort(unsortedArray);
    Timeline timeline = new Timeline();
    timeline.getKeyFrames().add(
            new KeyFrame(Duration.millis(100), event ->  {
                updateGraph(graph, sort.nextStep());
                if (sort.isDone()) {
                    timeline.stop();
                }
            })
    );
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();
}

这是重构后的完整示例(请注意,我还关闭了图表的默认动画,该动画在自定义动画中无法很好地播放):

Here's the complete example refactored this way (note I also turned off the chart's default animation, which will not play nicely with the custom animation):

import java.util.Random;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

    Button sortBtn = new Button();

    @Override
    public void start(Stage primaryStage) throws Exception{
        //Creating Bar Graph
        int size = 100;
        int maxValue = 100;
        int windowX = 1920;
        int windowY = 1020;

        int[] unsortedArray = createUnsortedArray(size, maxValue);

        NumberAxis xAxis = new NumberAxis();
        xAxis.setLabel("Position");

        NumberAxis yAxis = new NumberAxis();
        yAxis.setLabel("Value");

        XYChart.Series<Number, Number> graph = new XYChart.Series<>();
        for(int i = 0; i < size; i++){
            graph.getData().add(new XYChart.Data<>(i, unsortedArray[i]));
        }

        ScatterChart<Number, Number> scatterChart = new ScatterChart<>(xAxis, yAxis);
        scatterChart.setTitle("Unsorted Array");
        
        scatterChart.setAnimated(false);

        scatterChart.getData().addAll(graph);
        scatterChart.setPrefSize(windowX-100,windowY);
        //End Creating Bar Graph
        primaryStage.setTitle("Sort Visualizer");
        GridPane layout = new GridPane();
        layout.getChildren().add(0,scatterChart);
        sortBtn = new Button("Sort");
        layout.getChildren().add(1,sortBtn);
        Scene scene = new Scene(layout, windowX,windowY);
        primaryStage.setScene(scene);
        primaryStage.show();

            sortBtn.setOnAction(actionEvent -> {
                try {
                    insertionSort(graph,unsortedArray);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

        }

    public void insertionSort(XYChart.Series<Number, Number> graph, int[] unsortedArray) throws InterruptedException {

        InsertionSort sort = new InsertionSort(unsortedArray);
        Timeline timeline = new Timeline();
        timeline.getKeyFrames().add(
                new KeyFrame(Duration.millis(100), event ->  {
                    updateGraph(graph, sort.nextStep());
                    if (sort.isDone()) {
                        timeline.stop();
                    }
                })
        );
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }

    public void updateGraph( XYChart.Series<Number, Number> graph, int[] updatedArray){
            graph.getData().clear();
            for(int i = 0; i < updatedArray.length; i++){
                graph.getData().add(new XYChart.Data<>(i, updatedArray[i]));
            }
        }

    int[] createUnsortedArray(int size, int maxValue){
        int[] unsortedArray = new int[size];
        Random randy = new Random();
        for(int i = 0; i < size; i++){
            unsortedArray[i] = Math.abs(randy.nextInt() % maxValue);
        }
        return unsortedArray;
    }
    
    private static class InsertionSort {
        private int lowPos = 0;
        private int[] data ;
        
        private int i ; // current iteration
        
        InsertionSort(int[] unsortedArray) {
            this.data = unsortedArray ;
        }
        
        private boolean isDone() {
            return i >= data.length ;
        }
        
        private int[] nextStep() {

            if (isDone()) throw new IllegalStateException("Sorting is complete");

            lowPos = i;
            for (int j = i; j < data.length; j++) {
                if(data[j] < data[lowPos]){
                    lowPos = j;
                }
            }
            //Swap lowPos value with i
            int swappedValue = data[i];
            data[i] = data[lowPos];
            data[lowPos] = swappedValue;
            i++ ;
            return data ;
        }
        
    }


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

这篇关于如何使用JavaFX(使用JavaFX)实时更新XYChart?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆