Created
July 19, 2022 08:12
-
-
Save RustyKnight/40a4581830b9899c6e18f5ddee0707a9 to your computer and use it in GitHub Desktop.
Animate sorting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.Color; | |
import java.awt.Dimension; | |
import java.awt.EventQueue; | |
import java.awt.Graphics; | |
import java.awt.Graphics2D; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.beans.PropertyChangeEvent; | |
import java.beans.PropertyChangeListener; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Random; | |
import javax.swing.JButton; | |
import javax.swing.JFrame; | |
import javax.swing.JOptionPane; | |
import javax.swing.JPanel; | |
import javax.swing.SwingWorker; | |
import javax.swing.Timer; | |
public class Main { | |
public static void main(String[] args) { | |
new Main(); | |
} | |
public Main() { | |
EventQueue.invokeLater(new Runnable() { | |
@Override | |
public void run() { | |
JFrame frame = new JFrame(); | |
frame.add(new SortPane()); | |
frame.pack(); | |
frame.setLocationRelativeTo(null); | |
frame.setVisible(true); | |
} | |
}); | |
} | |
public class SortPane extends JPanel { | |
final int SCREEN_WIDTH = 1280 / 3; | |
final int SCREEN_HEIGHT = 720 / 3; | |
final int BAR_HEIGHT = SCREEN_HEIGHT * 4 / 5; | |
final int BAR_WIDTH = 5; | |
final int NUM_BARS = SCREEN_WIDTH / BAR_WIDTH; | |
private JButton bubbleSort = new JButton(); | |
private JButton insertSort = new JButton(); | |
private int[] values = new int[NUM_BARS]; | |
private int current = 0; | |
private int traverse = 0; | |
private Timer timer; | |
private int eventIndex; | |
private int min = 0; | |
private int max = 0; | |
private ColorBand colorBand = new ColorBand( | |
new float[]{0f, 1f}, | |
new Color[]{Color.BLUE, Color.MAGENTA} | |
); | |
public SortPane() { | |
for (int i = 0; i < NUM_BARS; i++) { | |
values[i] = i * BAR_HEIGHT / NUM_BARS; | |
} | |
initSpan(); | |
//InsertionSort Button | |
insertSort.setText("Insertion Sort"); | |
this.add(insertSort); | |
insertSort.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
insertSort.setEnabled(false); | |
bubbleSort.setEnabled(false); | |
SortWorker worker = new SortWorker(values, new InsertionSort(), new SortWorker.Observer() { | |
@Override | |
public void stateDidChange(SortWorker worker, SwingWorker.StateValue state) { | |
System.out.println("State did change " + state); | |
} | |
@Override | |
public void didComplete(SortWorker worker, SortModel model) { | |
animate(model); | |
} | |
@Override | |
public void didFail(SortWorker worker, Exception exp) { | |
exp.printStackTrace(); | |
JOptionPane.showMessageDialog(SortPane.this, "Pants", "Pants", JOptionPane.ERROR_MESSAGE); | |
animationDidComplete(); | |
} | |
}); | |
worker.execute(); | |
} | |
}); | |
//BubbleSort Button | |
bubbleSort.setText("Bubble Sort"); | |
this.add(bubbleSort); | |
bubbleSort.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
} | |
}); | |
} | |
protected void initSpan() { | |
min = Integer.MAX_VALUE; | |
max = Integer.MIN_VALUE; | |
for (int value : values) { | |
min = Math.min(value, min); | |
max = Math.max(value, max); | |
} | |
} | |
protected void animationDidComplete() { | |
insertSort.setEnabled(true); | |
bubbleSort.setEnabled(true); | |
} | |
protected void animate(SortModel model) { | |
if (timer != null) { | |
timer.stop(); | |
} | |
eventIndex = -1; | |
values = model.getUnsortedValues(); | |
initSpan(); | |
timer = new Timer(5, new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
eventIndex++; | |
List<SortEvent> events = model.getEvents(); | |
if (eventIndex >= events.size()) { | |
timer.stop(); | |
animationDidComplete(); | |
return; | |
} | |
SortEvent event = events.get(eventIndex); | |
if (event instanceof SortCurrentEvent) { | |
SortCurrentEvent evt = (SortCurrentEvent) event; | |
current = evt.getCurrent(); | |
} else if (event instanceof SortTraverseEvent) { | |
SortTraverseEvent evt = (SortTraverseEvent) event; | |
traverse = evt.getTraverse(); | |
} else if (event instanceof SortSwapEvent) { | |
SortSwapEvent evt = (SortSwapEvent) event; | |
int temp = values[evt.from()]; | |
values[evt.from()] = values[evt.to()]; | |
values[evt.to()] = temp; | |
} | |
repaint(); | |
} | |
}); | |
timer.start(); | |
repaint(); | |
} | |
@Override | |
public Dimension getPreferredSize() { | |
return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT); | |
} | |
@Override | |
protected void paintComponent(Graphics g) { | |
super.paintComponent(g); | |
Graphics2D g2d = (Graphics2D) g.create(); | |
for (int i = 0; i < values.length; i++) { | |
int value = values[i]; | |
float fraction = (float) (value - min) / (float) (max - min); | |
g2d.setColor(colorBand.colorAt(fraction)); | |
g2d.fillRect(i * BAR_WIDTH, SCREEN_HEIGHT - values[i], BAR_WIDTH, values[i]); | |
} | |
g2d.setColor(Color.green); | |
g2d.fillRect(current * BAR_WIDTH, SCREEN_HEIGHT - values[current], BAR_WIDTH, values[current]); | |
g2d.setColor(Color.red); | |
g2d.fillRect(traverse * BAR_WIDTH, SCREEN_HEIGHT - values[traverse], BAR_WIDTH, values[traverse]); | |
g2d.dispose(); | |
} | |
} | |
public interface SortEvent { | |
} | |
public interface SortSwapEvent extends SortEvent { | |
public int from(); | |
public int to(); | |
} | |
public interface SortCurrentEvent extends SortEvent { | |
public int getCurrent(); | |
} | |
public interface SortTraverseEvent extends SortEvent { | |
public int getTraverse(); | |
} | |
public interface SortModel { | |
public int[] getUnsortedValues(); | |
public int[] getSortedValues(); | |
public List<SortEvent> getEvents(); | |
} | |
public class DefaultSortCurrentEvent implements SortCurrentEvent { | |
private int current; | |
public DefaultSortCurrentEvent(int current) { | |
this.current = current; | |
} | |
@Override | |
public int getCurrent() { | |
return current; | |
} | |
} | |
public class DefaultSortTraverseEvent implements SortTraverseEvent { | |
private int traverse; | |
public DefaultSortTraverseEvent(int traverse) { | |
this.traverse = traverse; | |
} | |
@Override | |
public int getTraverse() { | |
return traverse; | |
} | |
} | |
public class DefaultSortSwapEvent implements SortSwapEvent { | |
private int from; | |
private int to; | |
public DefaultSortSwapEvent(int from, int to) { | |
this.from = from; | |
this.to = to; | |
} | |
@Override | |
public int from() { | |
return from; | |
} | |
@Override | |
public int to() { | |
return to; | |
} | |
} | |
public class DefaultSorterModel implements SortModel { | |
private int[] unsortedValues; | |
private int[] sortedValues; | |
private List<SortEvent> events; | |
public DefaultSorterModel(int[] unsortedValues, int[] sortedValues, List<SortEvent> events) { | |
this.unsortedValues = unsortedValues; | |
this.sortedValues = sortedValues; | |
this.events = events; | |
} | |
@Override | |
public int[] getUnsortedValues() { | |
return unsortedValues; | |
} | |
@Override | |
public int[] getSortedValues() { | |
return sortedValues; | |
} | |
@Override | |
public List<SortEvent> getEvents() { | |
return events; | |
} | |
} | |
public interface Sorter { | |
public SortModel sort(int[] values); | |
} | |
public abstract class AbstractSorter implements Sorter { | |
protected void swap(int[] values, int indexOne, int indexTwo) { | |
int temp = values[indexOne]; | |
values[indexOne] = values[indexTwo]; | |
values[indexTwo] = temp; | |
} | |
} | |
public class SortWorker extends SwingWorker<SortModel, Void> { | |
public interface Observer { | |
public void stateDidChange(SortWorker worker, SwingWorker.StateValue state); | |
public void didComplete(SortWorker worker, SortModel model); | |
public void didFail(SortWorker worker, Exception exp); | |
} | |
private int[] values; | |
private Sorter sorter; | |
private Observer observer; | |
public SortWorker(int[] values, Sorter sorter, Observer observer) { | |
this.values = values; | |
this.sorter = sorter; | |
this.observer = observer; | |
addPropertyChangeListener(new PropertyChangeListener() { | |
@Override | |
public void propertyChange(PropertyChangeEvent evt) { | |
observer.stateDidChange(SortWorker.this, getState()); | |
} | |
}); | |
} | |
protected int[] getValues() { | |
return values; | |
} | |
@Override | |
protected SortModel doInBackground() throws Exception { | |
int[] values = getValues(); | |
int[] initalValues = Arrays.copyOf(values, values.length); | |
// Shuffle the values first... | |
ShuffleSorter shuffleSorter = new ShuffleSorter(); | |
SortModel shuffled = shuffleSorter.sort(getValues()); | |
// Get the events from the shuffle, we'll need those later | |
List<SortEvent> events = shuffled.getEvents(); | |
// Get the "shuffled" values as a new starting point | |
int[] shuffledValues = shuffled.getSortedValues(); | |
// Sorted the shuffled values... | |
SortModel sorted = sorter.sort(shuffledValues); | |
// Append all the sort events... | |
events.addAll(sorted.getEvents()); | |
// Build a new model... | |
DefaultSorterModel model = new DefaultSorterModel(initalValues, sorted.getSortedValues(), events); | |
return model; | |
} | |
@Override | |
protected void done() { | |
super.done(); | |
try { | |
SortModel model = get(); | |
observer.didComplete(this, model); | |
} catch (Exception ex) { | |
observer.didFail(this, ex); | |
} | |
} | |
} | |
public class ShuffleSorter extends AbstractSorter { | |
@Override | |
public SortModel sort(int[] values) { | |
Random random = new Random(); | |
int[] sortedValues = Arrays.copyOf(values, values.length); | |
int[] unsortedValues = Arrays.copyOf(values, values.length); | |
List<SortEvent> events = new ArrayList<>(values.length); | |
for (int i = 0; i < sortedValues.length; i++) { | |
int swap = random.nextInt(sortedValues.length - 1); | |
events.add(new DefaultSortCurrentEvent(i)); | |
events.add(new DefaultSortTraverseEvent(swap)); | |
events.add(new DefaultSortSwapEvent(i, swap)); | |
swap(sortedValues, i, swap); | |
} | |
events.add(new DefaultSortCurrentEvent(values.length - 1)); | |
events.add(new DefaultSortTraverseEvent(values.length - 1)); | |
DefaultSorterModel model = new DefaultSorterModel(unsortedValues, sortedValues, events); | |
return model; | |
} | |
} | |
public class InsertionSort extends AbstractSorter { | |
@Override | |
public SortModel sort(int[] values) { | |
int[] sortedValues = Arrays.copyOf(values, values.length); | |
int[] unsortedValues = Arrays.copyOf(values, values.length); | |
List<SortEvent> events = new ArrayList<>(values.length); | |
for (int current = 1; current < unsortedValues.length; current++) { | |
int traverse = current; | |
events.add(new DefaultSortCurrentEvent(current)); | |
while (traverse > 0 && sortedValues[traverse] < sortedValues[traverse - 1]) { | |
events.add(new DefaultSortSwapEvent(traverse, traverse - 1)); | |
swap(sortedValues, traverse, traverse - 1); | |
traverse--; | |
events.add(new DefaultSortTraverseEvent(traverse)); | |
} | |
} | |
events.add(new DefaultSortCurrentEvent(values.length - 1)); | |
events.add(new DefaultSortTraverseEvent(values.length - 1)); | |
DefaultSorterModel model = new DefaultSorterModel(unsortedValues, sortedValues, events); | |
return model; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This basically generates a "replayable" event based model, which can be animated - this prevents all the issues with trying to sync threads and trying to keep painting clean and all the other issues