如何在 Java 中实时更新 XYChart(使用 JavaFX)?

Sta*_*kii 1 java algorithm javafx

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

我的问题是,当我开始排序时,程序会锁定,直到排序完全完成并重新绘制图形完全完成。我希望它在每一步都重新绘制图表!我添加了 Thread.sleep(x) 然后切换到 TimeUnit.milliseconds.sleep(x) 来尝试添加延迟,但可惜这并没有解决问题,它只是增加了程序被锁定的时间并且没有直到排序完成后才重新绘制图表。有没有办法在不将框架从 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,提前为我的代码有多草率道歉,我只是希望这将是一个简单的跟踪子弹,让某些东西处于工作状态,然后我希望稍后偿还技术债务(我知道的坏习惯)

Jam*_*s_D 5

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

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

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 ;
    }
    
}
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,既然您说您想要创建多种排序算法的可视化,这应该非常适合您的应用程序设计。使用和方法创建一个Sort接口,然后为不同的算法创建实现该接口的单独类。您可以将他们在一个左右,用户可以选择一个,等等)nextStep()isDone()List<Sort>ComboBox<Sort>

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

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();
}
Run Code Online (Sandbox Code Playgroud)

这是以这种方式重构的完整示例(请注意,我还关闭了图表的默认动画,这不会与自定义动画很好地配合):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)