I'm writing a test application that displays an A* search in action, and it's running really slow. I profiled it using VisualVM, and got the following results:
Note that the third entry is a lambda containing some long-running code.
The problem is, I can't find a lambda with such a signature anywhere.
Is there any way for me to find out what lambda it's referring to?
Here is pacmanTest.FrontierVisual.java:
package pacmanTest;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import utils.Duple;
import utils.MainLoop;
public class FrontierVisual
extends Application {
private Stage stage;
private Scene scene;
private final int areaWidth = 800;
private final int areaHeight = 800;
private final double drawScale = 15;
private Canvas canvas = new Canvas(800, 800);
private GraphicsContext gc = canvas.getGraphicsContext2D();
private final MapArea<Wall> area = new MapArea<>(areaWidth, areaHeight);
private static List<Duple<Integer>> cellsAround(Duple<Integer> pos, List<Duple<Integer>> offsets) {
List<Duple<Integer>> surroundingCells = new ArrayList<>();
offsets.stream()
.map( offset -> pos.map(offset, (x1, x2) -> x1 + x2) )
.forEach(surroundingCells::add);
return surroundingCells;
}
private void drawSquare(Duple<Integer> position, Color color) {
gc.setFill(color);
gc.fillRect(position.getX() * drawScale, position.getY() * drawScale, drawScale, drawScale);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
BorderPane rootNode = new BorderPane();
scene = new Scene(rootNode);
stage.setScene(scene);
rootNode.setCenter(canvas);
Duple<Integer> startPos = new Duple<Integer>(20, 20);
Duple<Integer> goalPos = new Duple<Integer>(30, 30);
Wall wall = new SolidWall();
/*for (int d = 29; d <= 41; d++) {
int e = d / 2;
area.setCellAt(d, e, wall);
}*/
area.setCellAt(19, 32, wall);
area.setCellAt(20, 31, wall);
area.setCellAt(21, 30, wall);
area.setCellAt(22, 29, wall);
area.setCellAt(23, 28, wall);
area.setCellAt(24, 27, wall);
area.setCellAt(25, 26, wall);
area.setCellAt(26, 25, wall);
area.setCellAt(27, 24, wall);
area.setCellAt(28, 23, wall);
area.setCellAt(29, 22, wall);
area.setCellAt(30, 21, wall);
area.setCellAt(31, 20, wall);
area.setCellAt(32, 19, wall);
Deque<Duple<Integer>> frontier = new ArrayDeque<>();
Map<Duple<Integer>, Duple<Integer>> cameFrom = new HashMap<>();
cameFrom.put(startPos, startPos);
frontier.push(startPos);
final List<Duple<Integer>> fourDirectionOffsets = Collections.unmodifiableList(Arrays.asList(
new Duple<Integer>(1,0), new Duple<Integer>(-1,0), new Duple<Integer>(0,1), new Duple<Integer>(0,-1) ));
MainLoop mainLoop = new MainLoop(10000, t -> {
utils.Utils.clearCanvas(gc);
gc.setFill(Color.STEELBLUE);
Duple<Integer> poppedLocation = frontier.pop();
drawSquare(startPos, Color.BLACK);
drawSquare(goalPos, Color.GREEN);
List<Duple<Integer>> neighbors = cellsAround(poppedLocation, fourDirectionOffsets);
neighbors.stream()
.filter(location -> !cameFrom.containsKey(location) && area.cellIsInBounds(location) && area.getCellAt(location) == null)
.forEach( neighbor -> {
frontier.add(neighbor);
cameFrom.put(neighbor, poppedLocation);
drawSquare(neighbor, Color.CORAL);
});
frontier.stream()
.forEach( frontierPos -> {
drawSquare(frontierPos, Color.BLUE);
});
reconstructPath(cameFrom, startPos, goalPos).stream()
.forEach( pathPos -> {
if (pathPos != startPos && area.getCellAt(pathPos) == null) {
drawSquare(pathPos, Color.ORANGE);
}
});
area.forallNonEmptyCells( (pos, contents) -> {
drawSquare(pos, Color.CHOCOLATE);
});
});
mainLoop.start();
stage.show();
}
private static List<Duple<Integer>> reconstructPath(Map<Duple<Integer>, Duple<Integer>> cameFrom, Duple<Integer> start, Duple<Integer> goal) {
List<Duple<Integer>> path = new ArrayList<>();
path.add(goal);
Duple<Integer> current = goal;
do {
path.add(current);
current = cameFrom.get(current);
} while (current != null && !current.equals(start));
Collections.reverse(path);
return path;
}
public static void main(String[] args) {
launch(args);
}
}
Any ideas here would be appreciated.
Side note:
If anyone knows how to reduce the drawing time of JavaFX's canvas, that would be awesome given it's the biggest CPU hog.
Well this is your lambda$1 expression because of the "parameter list". This part is the only one which uses a Deque, 2 x Duple, List, Map, Long:
new MainLoop(10000, /* THIS -> */ t -> { ... });
BTW don't do any filtering actions in a Stream in the forEach method. Therefore use the Stream::filter.
Here:
reconstructPath(cameFrom, startPos, goalPos).stream()
.filter(pathPos != startPos && area.getCellAt(pathPos) == null)
.forEach(pathPos -> drawSquare(pathPos, Color.ORANGE));
Related
I'm a beginner who recently picked up JavaFX, I've been trying to make a program that executes an animation on a node upon clicking on it. I've tried to see if anyone else has tried something similar but I have not been able to find a similar case to what I've tried to do.
The Timeline animates Panes, which contain a Rectangle and Text, horizontally across the screen, upon finishing the execution the stage will change scenes. When the user clicks a button to go back to the original scene the buttons should perform another animation.
I've verified that it isn't the scene transition that is causing it to stop and I have also verified that the program is indeed executing .play() on the timeline. The timelines are executed within the custom listeners so the listeners are not an issue as far as I know.
Here is the code sample to recreate the issue I have, run the script and just press on. Here you can see that done is being printed to the console which is right underneath Main_Button_Animation.play(); however, nothing is being animated.
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class main extends Application{
Scene scene1;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("test");
Main_Screen MainScreen = new Main_Screen(800, 480);
scene1 = MainScreen.MainScreen(primaryStage);
MainScreen.getInitiater().Calibration_Listener(() -> {
MainScreen.getInitiater().Back_Button_Pressed();
});
primaryStage.setScene(scene1);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
//custom listeners
interface Calibration {
void menu();
}
interface Back {
void back();
}
class Initiater {
private List<Calibration> calilist = new ArrayList<Calibration>();;
private List<Back> Go_Back = new ArrayList<Back>();
public void Calibration_Listener(Calibration toAdd) {
calilist.add(toAdd);
}
public void Back_Listener(Back toAdd) {
Go_Back.add(toAdd);
}
public void Calibration_Pressed() {
System.out.println("Calibration Pressed");
for (Calibration hl : calilist)
hl.menu();
}
public void Back_Button_Pressed() {
System.out.println("Back Pressed");
for (Back hl : Go_Back)
hl.back();
}
}
//animations and setup
class Main_Screen {
Initiater initiater;
private int X;
private int Y;
public Main_Screen(int X, int Y) {
this.initiater = new Initiater();
this.X = X;
this.Y = Y;
}
private Pane UI_Button(String name, int[] pos, int[] size, Color color) {
final Text text = new Text(0, 0, name);
text.setFont(new Font(20));
final Rectangle outline = new Rectangle(0, 0, size[0], size[1]);
outline.setFill(color);
final StackPane stack = new StackPane();
stack.getChildren().addAll(outline, text);
stack.setLayoutX(pos[0]);
stack.setLayoutY(pos[1]);
return stack;
}
public int getX() {
return this.X;
}
public int getY() {
return this.Y;
}
public Initiater getInitiater() {
return this.initiater;
}
public Scene MainScreen(Stage stage) {
Scene scene = new Scene(new Group(), getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() - getX() / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.rgb(255, 0, 0));
Pane Calibration = UI_Button("Callibration", new int[] { 0, ((getY() + (getY() / 8)) / 4) * 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
((Group) scene.getRoot()).getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline();
final KeyFrame Cali_kf = new KeyFrame(Duration.millis(500), new KeyValue(Calibration.translateXProperty(), getX() / 2));
final KeyFrame Start_kf = new KeyFrame(Duration.millis(500), new KeyValue(Start.translateXProperty(), -getX() / 4));
Main_Button_Animation.getKeyFrames().addAll(Cali_kf, Start_kf);
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.setRate(1);
Main_Button_Animation.jumpTo(Duration.millis(0));
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(event -> {
Main_Button_Animation.play();
System.out.println("done");
});
});
return scene;
}
}
Edit1 : For clarity, The example above doesn't use any scene transition. Here is a example with a scene transition.
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class main extends Application{
Scene scene1, scene2;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("test");
Main_Screen MainScreen = new Main_Screen(800, 480);
scene1 = MainScreen.MainScreen(primaryStage);
MainScreen.getInitiater().Calibration_Listener(() -> {
primaryStage.setScene(scene2);
primaryStage.show();
});
Label label2 = new Label("This is the second scene");
Button button2 = new Button("Go to scene 1");
button2.setOnAction(e -> {
MainScreen.getInitiater().Back_Button_Pressed();
primaryStage.setScene(scene1);
primaryStage.show();
});
VBox layout2 = new VBox(20);
layout2.getChildren().addAll(label2, button2);
scene2 = new Scene(layout2, 800, 480);
primaryStage.setScene(scene1);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
//custom listeners
interface Calibration {
void menu();
}
interface Back {
void back();
}
class Initiater {
private List<Calibration> calilist = new ArrayList<Calibration>();;
private List<Back> Go_Back = new ArrayList<Back>();
public void Calibration_Listener(Calibration toAdd) {
calilist.add(toAdd);
}
public void Back_Listener(Back toAdd) {
Go_Back.add(toAdd);
}
public void Calibration_Pressed() {
System.out.println("Calibration Pressed");
for (Calibration hl : calilist)
hl.menu();
}
public void Back_Button_Pressed() {
System.out.println("Back Pressed");
for (Back hl : Go_Back)
hl.back();
}
}
//animations and setup
class Main_Screen {
Initiater initiater;
private int X;
private int Y;
public Main_Screen(int X, int Y) {
this.initiater = new Initiater();
this.X = X;
this.Y = Y;
}
private Pane UI_Button(String name, int[] pos, int[] size, Color color) {
final Text text = new Text(0, 0, name);
text.setFont(new Font(20));
final Rectangle outline = new Rectangle(0, 0, size[0], size[1]);
outline.setFill(color);
final StackPane stack = new StackPane();
stack.getChildren().addAll(outline, text);
stack.setLayoutX(pos[0]);
stack.setLayoutY(pos[1]);
return stack;
}
public int getX() {
return this.X;
}
public int getY() {
return this.Y;
}
public Initiater getInitiater() {
return this.initiater;
}
public Scene MainScreen(Stage stage) {
Scene scene = new Scene(new Group(), getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() - getX() / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.rgb(255, 0, 0));
Pane Calibration = UI_Button("Callibration", new int[] { 0, ((getY() + (getY() / 8)) / 4) * 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
((Group) scene.getRoot()).getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline();
final KeyFrame Cali_kf = new KeyFrame(Duration.millis(500), new KeyValue(Calibration.translateXProperty(), getX() / 2));
final KeyFrame Start_kf = new KeyFrame(Duration.millis(500), new KeyValue(Start.translateXProperty(), -getX() / 4));
Main_Button_Animation.getKeyFrames().addAll(Cali_kf, Start_kf);
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.setRate(1);
Main_Button_Animation.jumpTo(Duration.millis(0));
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(event -> {
this.initiater.Calibration_Pressed();
System.out.println("done");
});
});
this.initiater.Back_Listener(() -> {
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(e -> {
Calibration.setDisable(false);
Start.setDisable(false);
});
});
return scene;
}
}
You only define KeyValues for the frame at the end of the Timeline animation. This only allows the Timeline to interpolate between the value at the start of the animation and the target value. Inserting another KeyFrame at the beginning of the animation should fix this issue:
public Scene MainScreen(Stage stage) {
final Group root = new Group();
Scene scene = new Scene(root, getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() * 3 / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.RED);
Pane Calibration = UI_Button("Callibration", new int[] { 0, 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
root.getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(Calibration.translateXProperty(), 0),
new KeyValue(Start.translateXProperty(), 0)),
new KeyFrame(Duration.millis(500),
new KeyValue(Calibration.translateXProperty(), getX() / 2),
new KeyValue(Start.translateXProperty(), -getX() / 4)));
Main_Button_Animation.setRate(-1);
Main_Button_Animation.playFrom(Main_Button_Animation.getTotalDuration());
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.playFromStart();
Main_Button_Animation.setOnFinished(event -> {
this.initiater.Calibration_Pressed();
System.out.println("done");
});
});
this.initiater.Back_Listener(() -> {
Main_Button_Animation.setRate(-1);
Main_Button_Animation.playFrom(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.setOnFinished(e -> {
Calibration.setDisable(false);
Start.setDisable(false);
});
});
return scene;
}
Note that there have been a few additional changes here:
Combining KeyFrames at the same time to a single one with multiple KeyValues
Keep a reference to the scene root to avoid the use of the getter/cast
Using playFrom and playFromStart instead of jumpTo/play
Simplifications of some of the parameters passed to the UI_Button method (rounding could make the result differ by 1 though)
...
I'm trying to build a space invaders like game.
I've drawn a square and I want to move it down incrementally by using a loop and thread.sleep. However, the square just gets drawn immediately. I understand there are animation paths that could be used but I want to stay low level and just use a coordinate system.
Is there a way of making a timeline animation by using a loop like this?
package com.company;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle;
public class Main extends Application {
public static void main(String[] args) {
// write your code here
launch(args);
}
public void start(Stage myStage) throws InterruptedException {
myStage.setTitle("space invaders");
Pane rootNode= new Pane();
Scene myScene=new Scene(rootNode, 400, 800);
myStage.setScene(myScene);
Rectangle r = new Rectangle();
myStage.show();
rootNode.getChildren().add(r);
r.setX(50);
r.setY(50);
r.setWidth(20);
r.setHeight(20);
for (int i = 0; i < 500; i++){
Thread.sleep(2000);
r.setTranslateY(i);
}
}
}
This is a terrible implementation. I would probably use AnimationTimer. This is done with Timeline. It's basically moving right or left. If you hit the right or left bound, drop then move in the opposite direction.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class JavaFXApplication343 extends Application
{
int invaderWidth = 30;
int invaderHeight = 10;
int gapBetweenInvaderX = 5;
int gapBetweenInvaderY = 5;
int locationTrackerX;
int locationTrackerY;
int screenWidth = 300;
int screenHeight = 400;
double timeBetweenFrames = .25;
boolean direction = true;
Timeline timeline;
#Override
public void start(Stage primaryStage)
{
Pane pane = new Pane();
locationTrackerX = (screenWidth - (invaderWidth * 6 + gapBetweenInvaderX * 5)) / 2;
locationTrackerY = (screenHeight - (invaderHeight * 6 + gapBetweenInvaderY * 5)) / 7;
List<Rectangle> invaders = new ArrayList();
for (int i = 0; i < 36; i++) {
Rectangle rectangle = new Rectangle(locationTrackerX, locationTrackerY, invaderWidth, invaderHeight);
rectangle.setFill(Color.YELLOW);
invaders.add(rectangle);
System.out.println(locationTrackerX);
locationTrackerX += invaderWidth + gapBetweenInvaderX;
if ((i + 1) % 6 == 0) {
locationTrackerX = (screenWidth / 2) - ((invaderWidth * 6 + gapBetweenInvaderX * 5) / 2);
locationTrackerY += invaderHeight + gapBetweenInvaderY;
}
}
timeline = new Timeline(new KeyFrame(Duration.seconds(timeBetweenFrames), (event) -> {
//Check to see if invader hits bounds
Optional<Rectangle> hitRightOptional = invaders.stream().filter(invader -> invader.getBoundsInLocal().getMaxX() >= pane.getWidth()).findFirst();
Optional<Rectangle> hitLeftOptional = invaders.stream().filter(invader -> invader.getBoundsInLocal().getMinX() <= 0).findFirst();
//Move invaders
if (hitRightOptional.isPresent()) {
invaders.forEach((tempInvader) -> tempInvader.setY(tempInvader.getY() + 10));
timeline.stop();
PauseTransition pause = new PauseTransition(Duration.seconds(timeBetweenFrames));
pause.setOnFinished((pauseEvent) -> {
invaders.forEach(invader -> invader.setX(invader.getX() - 10));
timeline.play();
});
pause.play();
direction = false;
}
else if (hitLeftOptional.isPresent()) {
invaders.forEach((tempInvader) -> tempInvader.setY(tempInvader.getY() + 10));
timeline.stop();
PauseTransition pause = new PauseTransition(Duration.seconds(timeBetweenFrames));
pause.setOnFinished((pauseEvent) -> {
invaders.forEach(invader -> invader.setX(invader.getX() + 10));
timeline.play();
});
pause.play();
direction = true;
}
else {
if (direction) {
invaders.forEach(invader -> invader.setX(invader.getX() + 10));
}
else {
invaders.forEach(invader -> invader.setX(invader.getX() - 10));
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
Button btn = new Button();
btn.setText("Start Game");
btn.setOnAction((ActionEvent event) -> {
timeline.play();
btn.setDisable(true);
});
pane.getChildren().addAll(invaders);
pane.setPrefSize(screenWidth, screenHeight);
VBox root = new VBox(pane, new StackPane(btn));
Scene scene = new Scene(root, screenWidth, screenHeight);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
Solved :
Task task = new Task<Void>() {
#Override public Void call() throws InterruptedException {
for (int i = 1; i <= 800; i=i+10) {
Thread.sleep(25);
//updateProgress(i, max);
r.setTranslateY(i);
}
return null;
}
};
new Thread(task).start();
I am going to add some shapes on LineChart. I put LineChart and AnchorPane into the StackPane. I added shapes to AnchorPane by getting x and y coordinates from the chart series. Here is example.
LineChartApp.java
package shapes;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class LineChartApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new ChartContent()));
primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ChartContent.java
package shapes;
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class ChartContent extends StackPane {
private AnchorPane objectsLayer;
private LineChart<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
private int level = 0;
private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
{ 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
{ 1000, 1341, 1211, 1562, 1400, 1600, 1550 }
};
private List<Shape> shapes = new ArrayList<Shape>();
public ChartContent() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
yAxis.setForceZeroInRange(false);
xAxis.setForceZeroInRange(false);
chart = new LineChart<Number, Number>(xAxis, yAxis);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
chart.setAnimated(false);
chart.setVerticalZeroLineVisible(false);
Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
chartRefresh();
}
}));
timer.setCycleCount(datas.length - 1);
timer.play();
objectsLayer = new AnchorPane();
objectsLayer.prefHeightProperty().bind(heightProperty());
objectsLayer.prefWidthProperty().bind(widthProperty());
getChildren().addAll(chart, objectsLayer);
chartRefresh();
}
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
series.getData().add(
new Data<Number, Number>(i, datas[level][i]));
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
reDrawShapes(series);
}
private void reDrawShapes(Series<Number, Number> series) {
Node chartPlotBackground = chart.lookup(".chart-plot-background");
chartPlotBackground.setStyle("-fx-background-color:white");
Circle circle;
objectsLayer.getChildren().removeAll(shapes);
shapes.clear();
double top = chart.getPadding().getTop(), left = chart.getPadding()
.getLeft();
double minX = chartPlotBackground.getBoundsInParent().getMinX();
double minY = chartPlotBackground.getBoundsInParent().getMinY();
for (Data<Number, Number> data : series.getData()) {
circle = new Circle(minX
+ chart.getXAxis().getDisplayPosition(data.getXValue())
+ left, minY
+ chart.getYAxis().getDisplayPosition(data.getYValue())
+ top, 3, Color.RED);
shapes.add(circle);
}
objectsLayer.getChildren().addAll(shapes);
}
}
I am refreshing chart series every five seconds and redrawing its shapes as well. But after the shapes added to the AnchorPane, they are not there where I expect them to be.
Expected Result
Actual Result
First, note that for the exact functionality you're trying to achieve, this can be done simply by setting a node on the data.
(Aside: it could be argued, and I would argue, that making a node a property of the data displayed in the chart violates pretty much every good practice on the separation of view from data in UI development. The Chart API has a number of bad design flaws, imho, and this is one of them. There probably should be something like a Function<Data<X,Y>, Node> nodeFactory property of the Chart itself for this. However, it is what it is.)
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
data.setNode(new Circle(3, Color.RED));
series.getData().add(data);
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
// reDrawShapes(series);
}
This works if your node is simple enough that centering it on the point is what you need.
If you want something more complex, for which this doesn't work, the supported mechanism is to subclass the chart class and override the layoutPlotChildren() method. Here's the complete class using this approach:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class ChartContent extends StackPane {
private LineChart<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
private int level = 0;
private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
{ 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
{ 1000, 1341, 1211, 1562, 1400, 1600, 1550 }
};
public ChartContent() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
yAxis.setForceZeroInRange(false);
xAxis.setForceZeroInRange(false);
chart = new LineChart<Number, Number>(xAxis, yAxis) {
private List<Shape> shapes = new ArrayList<>();
#Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
getPlotChildren().removeAll(shapes);
shapes.clear();
for (Data<Number, Number> d : series.getData()) {
double x = xAxis.getDisplayPosition(d.getXValue());
double y = yAxis.getDisplayPosition(d.getYValue());
shapes.add(new Circle(x, y, 3, Color.RED));
}
getPlotChildren().addAll(shapes);
}
};
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
chart.setAnimated(false);
chart.setVerticalZeroLineVisible(false);
Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
chartRefresh();
}
}));
timer.setCycleCount(datas.length - 1);
timer.play();
getChildren().addAll(chart);
chartRefresh();
}
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
data.setNode(new Circle(3, Color.RED));
series.getData().add(data);
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
}
}
This results in
You can use this technique to, for example, add best fit lines to scatter plots or trend lines to line charts, etc.
I can't tell exactly why the code you used doesn't work, but it makes several assumptions about how the layout is managed (i.e. the location of chart-plot-background in relation to the overall chart itself) and also about when measurements are taken in order to do things like compute the scale in the axes for the mapping from "chart coordinates" to "pixel coordinates". It's not too hard to imagine these becoming invalid when the data changes and only being recalculated at the beginning of the layout process, for example. Logging the "data values" (data.getXValue() and data.getYValue()) alongside the values you get from Axis.getDisplayValue(...) for those values suggests that something akin to the latter explanation may be the case, as those definitely do not seem to produce the correct transformations.
Hooking into the layoutPlotChildren() method is more reliable.
I am building a Simon Says game in JavaFX. I have gotten most of it working, my only issue now is when you run the game it runs a for loop to generate the colours depending on which level you are on.
It seems to display one colour from the loop but it doesn't wait for the KeyFrame to finish before it flies through the rest of the loop and stores the values. How can I make the loop wait for the KeyFrame to complete so it displays all of the colour changes?
package assign3;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import javafx.animation.FillTransition;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Question2 extends Application
{
public static final int RED = 1;
public static final int GREEN = 2;
public static final int BLUE = 3;
public static final int ORANGE = 4;
private int thisGameScore = 0;
private int level = 1;
private BorderPane obBorder;
private HBox obPane;
private HBox obStart;
private Timeline tlRed;
private Timeline tlBlue;
private Timeline tlGreen;
private Timeline tlOrange;
private SequentialTransition stList = new SequentialTransition();
private Button btStart;
private ArrayList<Integer> colours;
private ArrayList<Integer> guesses;
#Override
public void start( Stage obPrimeStage ) throws Exception
{
boolean runGame = true;
int guessIndex = 0;
obBorder = new BorderPane();
obPane = new HBox();
obStart = new HBox();
Button btRed = new Button("Red");
Button btGreen = new Button("Green");
Button btBlue = new Button("Blue");
Button btOrange = new Button("Orange");
btStart = new Button("Start");
class RedTimeLine
{
Timeline tlRed;
RedTimeLine()
{
tlRed = new Timeline();
tlRed.getKeyFrames().add(new KeyFrame(Duration.ZERO,
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)))));
tlRed.getKeyFrames().add(new KeyFrame(Duration.seconds(1),
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)))));
}
}
tlBlue = new Timeline();
tlBlue.getKeyFrames().add(new KeyFrame(Duration.ZERO,
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)))));
tlBlue.getKeyFrames().add(new KeyFrame(Duration.seconds(1),
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)))));
tlGreen = new Timeline();
tlGreen.getKeyFrames().add(new KeyFrame(Duration.ZERO,
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY)))));
tlGreen.getKeyFrames().add(new KeyFrame(Duration.seconds(1),
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)))));
tlOrange = new Timeline();
tlOrange.getKeyFrames().add(new KeyFrame(Duration.ZERO,
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.ORANGE, CornerRadii.EMPTY, Insets.EMPTY)))));
tlOrange.getKeyFrames().add(new KeyFrame(Duration.seconds(1),
new KeyValue(obBorder.backgroundProperty(),
new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)))));
obStart.getChildren().add(btStart);
obPane.getChildren().addAll(btRed, btGreen, btBlue, btOrange);
obBorder.setCenter(obPane);
obBorder.setBottom(obStart);
obPane.setAlignment(Pos.CENTER);
obStart.setAlignment(Pos.CENTER);
Scene obScene = new Scene(obBorder, 400, 400);
obPrimeStage.setTitle("Simon Says");
obPrimeStage.setScene(obScene);
obPrimeStage.show();
btStart.setOnAction((ActionEvent start) -> {
colours = new ArrayList<>();
guesses = new ArrayList<>();
obChange.handle(start);
stList.play();
System.out.println("Started new game");
});
btRed.setOnAction((ActionEvent e) ->
{
guesses.add(RED);
if(guesses.get(guessIndex) != colours.get(guessIndex) )
{
obStart.getChildren().add(btStart);
level = 1;
}
else
{
if(guesses.size() == colours.size())
{
level += 1;
colours = new ArrayList<>();
guesses = new ArrayList<>();
for(int i = 0; i < level; i++)
{
obChange.handle(e);
}
stList.play();
}
}
});
btGreen.setOnAction((ActionEvent e) ->
{
guesses.add(GREEN);
if(guesses.get(guessIndex) != colours.get(guessIndex) )
{
obStart.getChildren().add(btStart);
level = 1;
}
else
{
if(guesses.size() == colours.size())
{
level += 1;
colours = new ArrayList<>();
guesses = new ArrayList<>();
for(int i = 0; i < level; i++)
{
obChange.handle(e);
}
stList.play();
}
}
});
btBlue.setOnAction((ActionEvent e) ->
{
guesses.add(BLUE);
if(guesses.get(guessIndex) != colours.get(guessIndex) )
{
obStart.getChildren().add(btStart);
level = 1;
}
else
{
if(guesses.size() == colours.size())
{
level += 1;
colours = new ArrayList<>();
guesses = new ArrayList<>();
for(int i = 0; i < level; i++)
{
obChange.handle(e);
}
stList.play();
}
}
});
btOrange.setOnAction((ActionEvent e) ->
{
guesses.add(ORANGE);
if(guesses.get(guessIndex) != colours.get(guessIndex) )
{
obStart.getChildren().add(btStart);
level = 1;
}
else
{
if(guesses.size() == colours.size())
{
level += 1;
guesses = new ArrayList<>();
for(int i = 0; i < level; i++)
{
obChange.handle(e);
}
stList.play();
}
}
});
}
class ChangeColour implements EventHandler<ActionEvent>
{
#Override
public void handle( ActionEvent arg0 )
{
thisGameScore = 0;
int randomColour = (int)((Math.random() * 4) + 1);
if(randomColour == RED)
{
colours.add(RED);
stList.getChildren().add(new RedTimeLine());
}
else if(randomColour == BLUE)
{
colours.add(BLUE);
stList.getChildren().add(tlBlue);
}
else if(randomColour == GREEN)
{
colours.add(GREEN);
stList.getChildren().add(tlGreen);
}
else if(randomColour == ORANGE)
{
colours.add(ORANGE);
stList.getChildren().add(tlOrange);
}
obStart.getChildren().remove(btStart);
}
}
ChangeColour obChange = new ChangeColour();
public static void main( String[] args )
{
Application.launch(args);
}
}
I guess I would do something like this:
public void playSequence(int sequenceLength, double speed) {
Timeline timeline = new Timeline();
for (int i = 0; i < sequenceLength; i++) {
Color color = colors[random.nextInt(colors.length)];
Segment segment = segmentMap.get(color);
timeline.getKeyFrames().addAll(
new KeyFrame(
Duration.seconds(i), new KeyValue(segment.litProperty(), true)
),
new KeyFrame(
Duration.seconds(i + 0.9), new KeyValue(segment.litProperty(), false)
)
);
isPlaying.set(true);
timeline.setOnFinished(event -> isPlaying.set(false));
timeline.setRate(speed);
timeline.play();
}
}
The above example uses a Timeline, with the start time at which each segment is lit varying to light each segment in sequence.
Full sample
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static javafx.scene.paint.Color.*;
public class SimpleSimon extends Application {
private int sequenceLength = 1;
private int speed = 1;
#Override
public void start(Stage stage) throws Exception {
Simon simon = new Simon();
Button play = new Button("Play");
play.setStyle("-fx-font-size: 20px;");
play.disableProperty().bind(simon.isPlayingProperty());
play.setOnAction(event -> {
simon.playSequence(sequenceLength, speed);
sequenceLength++;
speed *= 1.05;
});
VBox layout = new VBox(10, simon, play);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class Simon extends Group {
private static final Random random = new Random(42);
private final Color[] colors = {
RED, GREEN, BLUE, ORANGE
};
private Map<Color, Segment> segmentMap = new HashMap<>();
private ReadOnlyBooleanWrapper isPlaying = new ReadOnlyBooleanWrapper();
public Simon() {
for (int i = 0; i < colors.length; i++) {
Segment segment = new Segment(colors[i], i * 90);
getChildren().add(segment);
segmentMap.put(colors[i], segment);
}
}
public void playSequence(int sequenceLength, double speed) {
Timeline timeline = new Timeline();
for (int i = 0; i < sequenceLength; i++) {
Color color = colors[random.nextInt(colors.length)];
Segment segment = segmentMap.get(color);
timeline.getKeyFrames().addAll(
new KeyFrame(
Duration.seconds(i), new KeyValue(segment.litProperty(), true)
),
new KeyFrame(
Duration.seconds(i + 0.9), new KeyValue(segment.litProperty(), false)
)
);
}
isPlaying.set(true);
timeline.setOnFinished(event -> isPlaying.set(false));
timeline.setRate(speed);
timeline.play();
}
public boolean isPlaying() {
return isPlaying.get();
}
public ReadOnlyBooleanWrapper isPlayingProperty() {
return isPlaying;
}
}
class Segment extends Arc {
private BooleanProperty lit = new SimpleBooleanProperty();
private static final double RADIUS = 100;
private static final ColorAdjust litEffect = new ColorAdjust(0, 0, 0.5, 0);
private static final ColorAdjust unlitEffect = new ColorAdjust(0, 0, 0, 0);
public Segment(Color color, double angleOffset) {
super(RADIUS, RADIUS, RADIUS, RADIUS, angleOffset, 90);
setFill(color);
setType(ArcType.ROUND);
setEffect(unlitEffect);
lit.addListener((observable, oldValue, newValue) ->
setEffect(lit.get() ? litEffect : unlitEffect)
);
}
public void setLit(boolean lit) {
this.lit.set(lit);
}
public boolean isLit() {
return lit.get();
}
public BooleanProperty litProperty() {
return lit;
}
}
A similar effect could be achieved via a SequentialTransition.
I am trying to do a neural network training visualization with JavaFX 8. The network cells are shown as dots changing their color depending on the output value. The calculations and the drawing are done in a thread that can be started or stopped by clicking a button. For a while everything works fine but after a number of iterations the display is no longer updated.
What needs to be done to reliably update the display?
I am using JRE version 1.8.0_45.
Here's a simplified version of my code:
import javafx.application.*;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.*;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class TestView extends Application {
public static final int SCENE_WIDTH = 1000;
public static final int SCENE_HEIGHT = 800;
public static final int BUTTON_PANEL_HEIGHT = 80;
private Canvas canvas;
private GraphicsContext gc;
private Task<Void> task = null;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("MLP");
BorderPane borderPane = new BorderPane();
canvas = new Canvas( SCENE_WIDTH, 3*SCENE_HEIGHT-BUTTON_PANEL_HEIGHT );
gc = canvas.getGraphicsContext2D();
borderPane.setCenter(new ScrollPane(canvas));
GridPane buttonPanel = new GridPane();
Button buttonTrain = new Button("Train");
buttonTrain.setMinWidth(SCENE_WIDTH/2);
buttonPanel.setPadding(new Insets(0, 0, 0, 0));
buttonPanel.add(buttonTrain, 1, 0);
borderPane.setBottom(buttonPanel);
buttonTrain.setOnMouseClicked( e -> {
if (task != null) {
task.cancel();
task = null;
}
else {
task = new Task<Void>() {
#Override
protected Void call() throws Exception {
for (int i = 1; i <= 10000000; i++) {
if (isCancelled()) {
break;
}
// dummy calculation
doSomeStuff();
// dummy graphics update
gc.setFill(Color.GREEN);
gc.fillOval(50, 50, 20, 20);
gc.clearRect(200, 10, 200, 100);
gc.setFill(Color.BLACK);
gc.fillText("" + i, 200, 50);
}
return null;
}
};
new Thread(task).start();
}
});
Scene scene = new Scene( borderPane, SCENE_WIDTH, SCENE_HEIGHT );
primaryStage.setScene(scene);
primaryStage.show();
}
private double doSomeStuff() {
double r = 0.5;
for ( int i = 0; i < 10000; i++ ) {
r = Math.sin(r);
}
return r;
}
}