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

如何在Java中实时更新XYChart(使用JavaFX)?,java,algorithm,javafx,Java,Algorithm,Javafx,我正在使用JavaFX和JavaFXSDK中的散点图XYChart在Java中创建不同排序算法的可视化。我的想法是,在排序算法中进行交换后,每次都会重新绘制散点图。我从一个简单版本的insertionSort开始,因为它似乎是最容易实现的算法 我的问题是,当我开始排序时,程序会锁定,直到排序完全完成,并完全重新绘制图形。我希望它在每一步都重新绘制图表!我添加了一个Thread.sleepx,然后切换到TimeUnit.millizes.sleepx,尝试添加延迟,但遗憾的是,这并没有解决问题,它

我正在使用JavaFX和JavaFXSDK中的散点图XYChart在Java中创建不同排序算法的可视化。我的想法是,在排序算法中进行交换后,每次都会重新绘制散点图。我从一个简单版本的insertionSort开始,因为它似乎是最容易实现的算法

我的问题是,当我开始排序时,程序会锁定,直到排序完全完成,并完全重新绘制图形。我希望它在每一步都重新绘制图表!我添加了一个Thread.sleepx,然后切换到TimeUnit.millizes.sleepx,尝试添加延迟,但遗憾的是,这并没有解决问题,它只是增加了程序锁定的时间,直到排序完成后才重新绘制图形。有没有一种方法可以在不改变JavaFX框架的情况下实现我想要的功能

包装样品; 导入javafx.application.application; 导入javafx.collections.FXCollections; 导入javafx.event.ActionEvent; 导入javafx.event.EventHandler; 导入javafx.fxml.fxmloader; 导入javafx.scene.Group; 导入javafx.scene.Parent; 导入javafx.scene.scene; 导入javafx.scene.chart.*; 导入javafx.scene.control.Button; 导入javafx.scene.layout.GridPane; 导入javafx.stage.stage; 导入jdk.jfr.Event; 导入java.util.array; 导入java.util.Random; 导入java.util.concurrent.TimeUnit; 公共类主扩展应用程序{ 按钮sortBtn=新按钮; @凌驾 public void startStage primaryStage引发异常{ //创建条形图 int size=100; int maxValue=100; int windowX=1920; int windowY=1020; int[]unsortedArray=createunsortedarray大小,最大值; NumberAxis xAxis=新的NumberAxis; xAxis.setLabelPosition; NumberAxis yAxis=新的NumberAxis; yAxis.setLabelValue; XYChart.Seriesgraph=新的XYChart.Series<>; 对于int i=0;ii,unsortedArray[i]; } 散点图<数字,数字>散点图=新散点图<>X轴,Y轴; 散点图。设置感测阵列; scatterChart.getData.addAllgraph; scatterChart.setPrefSizewindowX-100,windowY; //结束创建条形图 primaryStage.setTitleSort可视化工具; GridPane布局=新的GridPane; layout.getChildren.add0,散点图; sortBtn=新按钮传感器; layout.getChildren.add1,sortBtn; 场景=新场景LAOUT、windowX、windowY; 初生阶段;初生阶段; 初级舞台表演; sortBtn.setOnActionactionEvent->{ 试一试{ insertionSortgraph,unsortedArray; }捕捉中断异常e{ e、 打印跟踪; } }; } public void insertionSortXYChart.Seriesgraph,int[]unsortedArray抛出InterruptedException{ int-lowPos=0; int swappedValue=0; 对于int i=0;igraph,int[]updateArray{ graph.getData.clear; 对于int i=0;ii,updateArray[i]; } } int[]createUnsortedArrayint大小,int最大值{ int[]unsortedArray=新的int[大小]; 随机随机数=新随机数; 对于int i=0;i对于我的代码如此草率,我也提前表示歉意,我只是希望这将是一个很容易在工作状态下获得一些东西的方法,然后我希望以后偿还技术债务。我知道,这个坏习惯是,任何非琐碎的、与UI无关的进程都应该在单独的线程上运行。您可以创建一个任务并根据需要设置其不同的属性,然后将其放置在线程中并运行该线程。您只应使用task.setOnRunning、task.setOnSucceeded和task.setOnFailed方法更新UI:


查看更多详细信息,并阅读。

任何非琐碎的、与UI无关的进程都应该在单独的计算机上运行 线您可以创建一个任务并根据需要设置其不同的属性,然后将其放置在线程中并运行该线程。您只应使用task.setOnRunning、task.setOnSucceeded和task.setOnFailed方法更新UI:


查看更多详细信息,并阅读。

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

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

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 ;
    }
    
}
以下是以这种方式重构的完整示例注意:我还关闭了图表的默认动画,这将无法与自定义动画配合使用:

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应用程序线程负责呈现UI和处理事件。如果事件处理程序运行时间较长,则在处理程序完成之前无法呈现UI。因此,永远不要执行长时间运行的任务,包括在事件处理程序中睡眠或在FX应用程序线程上执行的代码中的任何位置

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

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 ;
    }
    
}
以下是以这种方式重构的完整示例注意:我还关闭了图表的默认动画,这将无法与自定义动画配合使用:

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线程以更新UI。。。不要使用睡眠!!!!阅读有关Runnable、Task、Platform.runLater的信息。关于这一点,这里有很多答案。但是请注意,对于这类功能,动画API是一个使用后台线程的更合适的解决方案。我有点困惑为什么这是插入排序而不是选择排序。它不是找到最低的值并将其放在前面,而不是在每个值到达时进行排序吗?您需要将排序结果发送到JavaFX线程以更新UI。。。不要使用睡眠!!!!阅读有关Runnable、Task、Platform.runLater的信息。关于这一点,这里有很多答案。但是请注意,对于这类功能,动画API是一个使用后台线程的更合适的解决方案。我有点困惑为什么这是插入排序而不是选择排序。它不是找到最低的值并将其放在前面,而不是在每个值到达时对其进行排序吗?