I'm working on sorting algorithm simulation using JavaFx. At the time of sorting array elements I wanted to show the swap of Rectangle
s. But there is a problem that the rectangles move randomly and don't follow the sequence of sorting of array elements where I have tried PathTransition
. If I try it using TranslateTransition
, after translating x when I translate y, circles follow diagonal.Why this happens? I also tried to add text to rectangles individually but failed. Here is the code:
PathTransition pathtransition1;
PathTransition pathtransition2;
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
int[] a = {5, 8, 0, 3, 1};
Text[] text = new Text[5];
Rectangle[] rect = new Rectangle[5];
for (int i = 0; i < 5; i++) {
rect[i] = new Rectangle(100 * i, 300, 40, 40);
rect[i].setArcHeight(10);
rect[i].setArcWidth(10);
rect[i].setFill(Color.ORANGE);
text[i] = new Text(Integer.toString(a[i]));
text[i].setFont(Font.font("VERDANA", FontWeight.BOLD, 12));
root.getChildren().addAll(rect[i], text[i]);
}
// Selection Sort
int min;
for (int i = 0; i < a.length; i++) {
min = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) {
int temp = a[i];
a[i] = a[min];
a[min] = temp;
swap1(rect[i], 60, (int) rect[min].getX());
swap2(rect[min], 60, (int) rect[i].getX());
Rectangle temporary = rect[i];
rect[i] = rect[min];
rect[min] = temporary;
}
System.out.println(a[i]);
}
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
void swap1(Rectangle rect, int d, int sx) {
Path path1 = new Path(new MoveTo(rect.getX(), rect.getY()),
new LineTo(rect.getX(), rect.getY() - d),
new MoveTo(rect.getX(), rect.getY() - d),
new LineTo(sx, rect.getY() - d),
new MoveTo(sx, rect.getY() - d),
new LineTo(sx, rect.getY())
);
pathtransition1 = new PathTransition(seconds(1), path1, rect);
pathtransition1.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathtransition1.setCycleCount(1);
pathtransition1.setAutoReverse(false);
pathtransition1.play();
}
void swap2(Rectangle rect, int d, int sx) {
Path path2 = new Path(new MoveTo(rect.getX(), rect.getY()),
new LineTo(rect.getX(), rect.getY() + d),
new MoveTo(rect.getX(), rect.getY() + d),
new LineTo(sx, rect.getY() + d),
new MoveTo(sx, rect.getY() + d),
new LineTo(sx, rect.getY())
);
pathtransition2 = new PathTransition(seconds(4), path2, rect);
pathtransition2.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathtransition2.setCycleCount(1);
pathtransition2.setAutoReverse(false);
pathtransition2.play();
}
In your code the sorting code runs on the application thread. This means all the swap animations will be created before any animation has finished or even started running. This means every animation runs at the same time.
The solution to your problem would be saving the data about the swaps and later retrieve that data to do the animation.
Note: the following example only uses the translation properties for positioning for simplicity:
private static class AnimationElements {
private final PathTransition transition;
private final MoveTo start;
private final LineTo horizontalMove;
public AnimationElements(double height) {
this.start = new MoveTo();
this.horizontalMove = new LineTo();
horizontalMove.setAbsolute(false);
LineTo l1 = new LineTo(0, height);
l1.setAbsolute(false);
LineTo l2 = new LineTo(0, -height);
l2.setAbsolute(false);
this.transition = new PathTransition(Duration.seconds(4), new Path(start, l1, horizontalMove, l2));
}
public void init(Node movedNode, Node moveEnd) {
// init animation according to positions of the Nodes to move
double sx = movedNode.getTranslateX();
double dx = moveEnd.getTranslateX() - sx;
start.setX(sx + movedNode.getLayoutBounds().getWidth() / 2);
start.setY(movedNode.getTranslateY() + movedNode.getLayoutBounds().getHeight() / 2);
horizontalMove.setX(dx/*+movedNode.getLayoutBounds().getWidth()/2*/);
transition.setNode(movedNode);
}
public PathTransition getTransition() {
return transition;
}
}
private static class Swap {
private final int index1;
private final int index2;
public Swap(int index1, int index2) {
this.index1 = index1;
this.index2 = index2;
}
public void init(AnimationElements animation1, AnimationElements animation2, Node[] sortNodes) {
// initialize both positions
Node n1 = sortNodes[index1];
Node n2 = sortNodes[index2];
animation1.init(n1, n2);
animation2.init(n2, n1);
// swap order to be correct for the next swap
sortNodes[index2] = n1;
sortNodes[index1] = n2;
}
}
@Override
public void start(Stage primaryStage) {
// create list of swaps to execute; could be generated by sorting algorithm
List<Swap> swaps = Arrays.asList(new Swap(0, 1), new Swap(1, 2), new Swap(3, 4), new Swap(0, 4));
AnimationElements animationElements1 = new AnimationElements(100);
AnimationElements animationElements2 = new AnimationElements(-100);
// both swap animations happen simultaniously
ParallelTransition animation = new ParallelTransition(animationElements1.getTransition(), animationElements2.getTransition());
Color[] colors = new Color[]{
Color.RED,
Color.BLUE,
Color.LIME,
Color.YELLOW,
Color.ORANGE
};
Node[] nodes = new Node[5];
for (int i = 0; i < nodes.length; i++) {
Rectangle rect = new Rectangle(100, 20, colors[i]);
rect.setTranslateY(200);
rect.setTranslateX(i * 100);
nodes[i] = rect;
}
Iterator<Swap> iterator = swaps.iterator();
animation.setOnFinished(evt -> {
if (iterator.hasNext()) {
// continue with next swap
iterator.next().init(animationElements1, animationElements2, nodes);
animation.play();
}
});
if (iterator.hasNext()) {
// execute first swap
iterator.next().init(animationElements1, animationElements2, nodes);
animation.play();
}
Pane root = new Pane(nodes);
Scene scene = new Scene(root, 500, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
What is Going Wrong and How to Fix It
- You need to slow down your swap algorithm.
Your swap is basing each iteration on the current location of each rectangle. However the animated transitions take a duration to run, so the rectangle isn't where it needs to be until the transition is complete, but the iteration is running non-stop and not waiting for transition to complete.
One way to fix this is to keep adding your path transitions into a large sequential transition and then play the entire sequence rather than a step at a time.
Another way to fix this is to run your swap algorithm in its own thread and pause it at each step until the relevant step animations have completed (that is the approach I took in the provided sample solution).
- You need to relocate the rectangle to its new location after each transition.
The path transitions will modify the translateX/Y co-ordinates of the object. After a transition step is complete, set the X/Y co-ordinates of the thing being transitioned to the newly translated co-ordinate, then reset the translateX/Y co-ordinates.
rect.setX(rect.getX() + rect.getTranslateX());
rect.setY(rect.getY() + rect.getTranslateY());
rect.setTranslateX(0);
rect.setTranslateY(0);
- There are some minor errors in your path positioning logic.
After items are swapped their final resting place is slightly out of place.
- You add text to the scene but don't position it.
Instead of using rectangles, just use labels with some colored background and move those around.
Sample screenshot
In the sample below the elements labeled 1
and 8
are in the process of a swap transition:
Updated Sample Code
import javafx.animation.PathTransition;
import javafx.application.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class Sorter extends Application {
final Pane root = new Pane();
final PathTransition[] pathtransition = new PathTransition[2];
final Lock lock = new ReentrantLock();
final Condition[] swapComplete = { lock.newCondition(), lock.newCondition() };
final int[] data = {5, 8, 0, 3, 1};
@Override
public void start(Stage stage) {
Label[] labels = createLabels(data);
root.getChildren().addAll(labels);
root.setStyle("-fx-background-color: oldlace;");
Scene scene = new Scene(root, 600, 250);
stage.setScene(scene);
stage.show();
sort(data, labels);
}
private void sort(int[] a, Label[] rect) {
// Selection Sort
Thread thread = new Thread(
() -> {
int min;
for (int i = 0; i < a.length; i++) {
min = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) {
int temp = a[i];
a[i] = a[min];
a[min] = temp;
final int finalMin = min;
final int finalI = i;
FutureTask<Void> future = new FutureTask<>(
() -> {
swap(
0,
rect[finalI],
60,
rect[finalMin].getLayoutX() - rect[finalI].getLayoutX(),
Duration.seconds(1)
);
swap(
1,
rect[finalMin],
-60,
rect[finalI].getLayoutX() - rect[finalMin].getLayoutX(),
Duration.seconds(4)
);
return null;
}
);
lock.lock();
try {
Platform.runLater(future);
future.get();
for (Condition condition: swapComplete) {
condition.await();
}
} catch (InterruptedException e) {
Thread.interrupted();
break;
} catch (ExecutionException e) {
e.printStackTrace();
break;
} finally {
lock.unlock();
}
Label temporary = rect[i];
rect[i] = rect[min];
rect[min] = temporary;
}
System.out.println(a[i]);
}
}
);
thread.setDaemon(true);
thread.start();
}
private Label[] createLabels(int[] a) {
Label[] rect = new Label[a.length];
for (int i = 0; i < a.length; i++) {
createLabel(i, a, rect);
}
return rect;
}
private void createLabel(int i, int[] a, Label[] rect) {
rect[i] = new Label(Integer.toString(a[i]));
rect[i].setMinSize(40, 40);
rect[i].setMaxSize(40, 40);
rect[i].setAlignment(Pos.CENTER);
rect[i].setStyle(
"-fx-background-radius: 10; " +
"-fx-background-color: orange; " +
"-fx-font-family: Verdana; " +
"-fx-font-size: 12pt; " +
"-fx-font-weight: bold;"
);
rect[i].relocate(100 * i + 80, 100);
}
void swap(int transitionIdx, Region node, double dy, double dx, Duration duration) {
double cx = node.getWidth() / 2;
double cy = node.getHeight() / 2;
Path path1 = new Path(
new MoveTo(cx, cy),
new LineTo(cx, cy + dy),
new LineTo(dx + cx, cy + dy),
new LineTo(dx + cx, cy)
);
pathtransition[transitionIdx] = new PathTransition(duration, path1, node);
pathtransition[transitionIdx].setOnFinished(event -> {
node.setLayoutX(node.getLayoutX() + node.getTranslateX());
node.setLayoutY(node.getLayoutY() + node.getTranslateY());
node.setTranslateX(0);
node.setTranslateY(0);
lock.lock();
try {
swapComplete[transitionIdx].signal();
} finally {
lock.unlock();
}
});
pathtransition[transitionIdx].play();
}
public static void main(String[] args) {
launch(args);
}
}