I am trying to create a Java Sorting Visualizer using SwingWorker to run the sorting algorithm step by step in a separate thread, and publish intermediate states to update the UI (I am going to say using the Event Dispatch Thread) but I am having trouble understanding how to use SwingWorker for this. Below is my code:
package visualizer;
import visualizer.ui.SortingLogic;
import visualizer.ui.SortingWindow;
import javax.swing.*;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new SortingWindow();
});
//System.out.println("Hello world!");
}
}
package visualizer.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
public class SortingWindow {
int [] arrayToSort;
SortingVisualizer visualizer;
SortingLogic sortingLogic;
JButton startButton, stopButton, resetButton;
public SortingWindow(){
//System.out.println("SortingWindow is being called");
initiliaze();
}
private void initiliaze() {
JFrame frame = new JFrame();
frame.setTitle("Sorting Visualizer");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(1000, 800);
frame.setLocationRelativeTo(null);
frame.setResizable(true);
JPanel panelBottom = new JPanel();
panelBottom.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
frame.add(panelBottom, BorderLayout.PAGE_END);
panelBottom.setBackground(Color.ORANGE);
//Creates Panel that holds Start and Reset Buttons
/*JPanel panelTop = new JPanel();
panelTop.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
frame.add(panelTop, BorderLayout.PAGE_START);
panelTop.setBackground(Color.blue);
//Creates top Panel that holds currently sorting*/
String [] sortOptions = {"Bubble Sort", "Selection Sort", "Insertion Sort",
"Merge Sort", "Quick Sort", "Heap Sort", "Counting Sort", "Radix Sort",
"Bucket Sort", "Shell Sort"};
JComboBox sortingOptionsDropdown = new JComboBox(sortOptions);
panelBottom.add(sortingOptionsDropdown);
//Adds the drop-down menu to the bottom panel
/*https://docs.oracle.com/javase/tutorial/uiswing/components/combobox.html*/
startButton = new JButton("Start");
stopButton = new JButton("Stop");
resetButton = new JButton("Reset");
panelBottom.add(startButton);
panelBottom.add(stopButton);
panelBottom.add(resetButton);
//Adds buttons for starting and stopping to JPanel
stopButton.setEnabled(false);
resetButton.setEnabled(false);
/*JLabel currentlySorting = new JLabel("Currently Sorting");
panelTop.add(currentlySorting);
currentlySorting.setText("Currently Sorting: " + sortingOptionsDropdown.getSelectedItem()
+ " \tTime Elapsed: ");
currentlySorting.setForeground(Color.gray);
currentlySorting.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5));
//Adds labels for Currently sorting to the top label*/
sortingLogic = new SortingLogic();
arrayToSort = sortingLogic.generateRandomIntArray(5, 1000, 30);
visualizer = new SortingVisualizer(arrayToSort);
sortingLogic.setArrayToSort(arrayToSort);
sortingLogic.setVisualizer(visualizer);
System.out.println(Arrays.toString(arrayToSort));
//initializes a random array
frame.getContentPane().add(visualizer);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String selectedAlgorithm = (String) sortingOptionsDropdown.getSelectedItem();
if (selectedAlgorithm != null){
if (!sortingLogic.isSorted(arrayToSort)) {
sortingLogic.setSortingAlgorithm(selectedAlgorithm);
sortingLogic.startSorting();
}else {
System.out.println("Array is sorted");
}
}
startButton.setEnabled(false);
stopButton.setEnabled(true);
resetButton.setEnabled(true);
//System.out.println("Start has been clicked");
}
});//Performs sort that is selected under dropdownOptions and checks that it is not null or already sorted
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
sortingLogic.stopSorting();
}
});
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//System.out.println("Reset has been clicked");
sortingLogic.stopSorting(); // Stop the sorting algorithm
sortingLogic.resetArray();
startButton.setEnabled(true);
stopButton.setEnabled(false);
resetButton.setEnabled(false);
}
});
frame.setVisible(true);
}
}
package visualizer.ui;
import visualizer.algorithms.BubbleSort;
import visualizer.algorithms.SelectionSort;
import visualizer.algorithms.SortingInterface;
import java.util.Arrays;
import java.util.Random;
public class SortingLogic {
private SortingVisualizer visualizer;
private SortingInterface currentAlgorithm;
private int[] arrayToSort;
public SortingLogic() {
//System.out.println("SortingLogic is being called");
}
public void setVisualizer(SortingVisualizer visualizer) {
this.visualizer = visualizer;
}
public int [] generateRandomIntArray(int min, int max, int length) {
arrayToSort = new int[length];
Random randNumGenerator = new Random();
for (int i = 0; i < length; i++) {
arrayToSort[i] = randNumGenerator.nextInt(max - min + 1) + min;
}
return arrayToSort;
}
public void setSortingAlgorithm(String algorithm) {
switch (algorithm) {
case "Bubble Sort":
currentAlgorithm = new BubbleSort(visualizer);
break;
case "Selection Sort":
currentAlgorithm = new SelectionSort(visualizer);
break;
/*case "Insertion Sort":
currentAlgorithm = new InsertionSort(visualizer);*/
default:
break;
}
}
public void startSorting() {
if (currentAlgorithm != null)
currentAlgorithm.toSort(visualizer);
}
public void stopSorting() {
if (currentAlgorithm != null) {
currentAlgorithm.stopSorting();
}
}
public int[] getArrayToSort() {
return arrayToSort;
}
public void setArrayToSort(int[] newArray) {
this.arrayToSort = newArray;
}
public void resetArray() {
// Generate a new random array and update the visualizer
arrayToSort = generateRandomIntArray(1, 1000, 30);
visualizer.setArrayToSort(arrayToSort);
visualizer.updateVisualizationPanel();
}
public boolean isSorted(int [] arrayToSort){
for (int i = 0; i < arrayToSort.length; i++){
if (arrayToSort[i] > arrayToSort[i + 1]){
return false;
}
}
return true;
}
}
package visualizer.ui;
import javax.swing.*;
import java.awt.*;
import java.util.Arrays;
public class SortingVisualizer extends JPanel{
private int[] arrayToSort;
public int[] getArrayToSort() {
return arrayToSort;
}
public void setArrayToSort(int[] arrayToSort) {
this.arrayToSort = arrayToSort;
}
public SortingVisualizer(int[] arrayToSort) {
this.arrayToSort = Arrays.copyOf(arrayToSort, arrayToSort.length);
//System.out.println("SortingVisualizer is being called");
}
protected void paintComponent(Graphics g){
super.paintComponent(g);
int barWidth = getWidth() / arrayToSort.length;
//System.out.println(Arrays.toString(arrayToSort) + "array within Visualizer's Paint Component");
for (int j = 0; j < arrayToSort.length; j++) {//Enhanced for loops look at the values not the indices, so we do not use it
int maxArrayValue = Arrays.stream(arrayToSort).max().getAsInt();
int barHeight = (int) ((double) arrayToSort[j] / maxArrayValue * getHeight()); int x = j * barWidth;
int y = getHeight() - barHeight;
g.setColor(Color.red);
g.fillRect(x, y, barWidth, barHeight);
//Fills in a rectangular column
g.setColor(Color.black);
g.drawRect(x, y, barWidth, barHeight);
//Creates a border for said columns
//System.out.println("SortingVisualizer: /nBar Height " + barHeight + "barWidth " + barWidth + "x " + x + "y" + y);
}
}
public void updateVisualizationPanel() {
//System.out.println("Repaint has been called");
repaint(); // Trigger a repaint of the panel
}
}
package visualizer.algorithms;
import visualizer.ui.SortingVisualizer;
public interface SortingInterface {
void toSort(SortingVisualizer visualizer);
void stopSorting();
}
I started to create a Seperate SwingWorker Class so it may not be complete to the experienced eye.
Before using SwingWorker I was trying to do it using a Timer instead but that was not doing it step-by-step so I switched to SwingWorker. Specifically because it would allow me to do Multi-threading.
What is currently happening: Once you hit start nothing happens, I believe that us just because I do not make any calls to the BubbleSort (fully expect to implement the rest) in the ActionListener. But that is because I need the Sorting classes to work with the SwingWorker first
What I am expecting: Once the user hits the Start button they see the bars being sorted step-by-step
Any tips, comments, or concerns or duly appreciated!
EDIT
I was able to remove the For Loops and do it step-by-step as Hovercraft Full Of Eels and David Conrad suggested and this shows in the visualization panel
public BubbleSort(SortingVisualizer visualizer) {
if (visualizer == null) {
throw new IllegalArgumentException("Visualizer cannot be null");
}
this.visualizer = visualizer;
this.arr = visualizer.getArrayToSort();
this.i = 0;
this.j = 0;
this.timer = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(i < arr.length - 1){
if(j < arr.length - i - 1){
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
visualizer.updateVisualizationPanel();
}
j++;
}else{
j = 0;
i++;
}
}else{
timer.stop();
}
}
});
}