I am new to JavaFX but I am not new to Java. I have a big complex system that produces some results in a loop. What I am trying to achieve is to plot the results of each iteration on a JavaFX chart. I was doing this with no problem with the java jFreeChart libraries, but now I am trying to switch to JavaFX. Charts looks more fancy and I like the way style is handled. Anyway, I am struggling in trying to understand how to add points to a XYChart.Series object in a JavaFX application. All tutorials on the oracle website start with some fixed points that the application knows a-priori and they are added using something like:
`series.getData().add(new XYChart.Data(1, 23));`
But what I am trying to achieve is a bit different. In my case my application produces some results and as soon as they are produced (random time) I want to plot them on a chart.
I launch a thread with the javafx.application.Application, but when I try to add some points to the Series object I get a
java.lang.IllegalStateException: Not on FX application thread; currentThread = main
exception.
What is the correct way to pass data points to a JavaFX chart? I thought that the closest way to do this is to override the Event type, Event object and create a whole Event handling structure... but this looks way too complicated for the simple thing I am trying to archive!
Can you please tell me, in your opinion, what is the best/simplest way for doing this?
EDIT:
Here is some code for you to have a look and give me some advice:
public class Chart extends Application {
private final static XYChart.Series series = new XYChart.Series();
public static void addValue(double gen, double val) {
series.getData().add(new XYChart.Data(gen, val));
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chart");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number,Number> lineChart =
new LineChart<Number,Number>(xAxis,yAxis);
//defining training set series
series.setName("Training");
Scene scene = new Scene(lineChart, 800, 600);
lineChart.getData().add(series);
primaryStage.setScene(scene);
primaryStage.show();
}
}
class Launcher extends Thread {
#Override
public void run() {
Application.launch(Chart.class);
}
public static void main(String[] args) throws InterruptedException {
new Launcher().start();
System.out.println("Now doing something else...");
for (int i = 0; i < 1000; i++) {
double trainValue = Math.random();
Chart.addValue(i, trainValue);
Thread.sleep(500);
}
}
}
to be sure that the code to add points to the chart is executed within the JavaFX thread
you could add it into a Runnable-Object which gets executed on the JavaFX thread:
Platform.runLater(new Runnable() {
#Override
public void run() {
// code to add points to the chart
}
});
Hope this helps.
Torsten
Related
I have an options menu from which you can save the game and it should briefly close the menu and take a screenshot of the current game.
However, the menu does not close until after calling getScreenCapture even though it runs before causing the screenshot to always be of the settings menu.
(The screenshot itself is working it's just controlling what displays at the time of the screenshot)
!! Without the screenshot line it updates the scene immediately even though the scene change line comes before.
So far I have tried Thread.sleep but no length of time allows for the scene to update
//The initial call to save:
private void saveRoom() {
program.changeState(previous);
room.save();
}
//The changing of scenes: (State extends Scene)
public void changeState(State state) {
currentState = state;
stage.setScene(currentState);
currentState.paintAll();
}
//How the game scene draws to screen:
#Override
public void paintAll() {
ArrayList<Shape> toAdd = new ArrayList<>();
for(Furniture f : furniture) {
f.paint(toAdd);
}
optionsButton.paint(toAdd);
addFurnitureButton.paint(toAdd);
root.getChildren().clear();
root.getChildren().addAll(toAdd);
}
//How the options menu draws to screen (same as game):
#Override
public void paintAll() {
ArrayList<Shape> toAdd = new ArrayList<>();
toolsButton.paint(toAdd);
saveButton.paint(toAdd);
saveAndExitButton.paint(toAdd);
exitButton.paint(toAdd);
closeButton.paint(toAdd);
root.getChildren().clear();
root.getChildren().addAll(toAdd);
}
//The capture command:
robot.getScreenCapture(null, new Rectangle2D(x,y,x1,y1));
Update: I am now using the Scene.snapshot command to take the screenshot instead and it does not cause the problem, but am still open to finding out how to go about using Robot to take a screenshot since unlike Scene.snapshot, it captures the whole screen rather than just the program which could come in handy in a future project.
this is my first game using JavaFX so I admittedly have made some bad design decisions, probably.
Anyway, I want to transition from a splash page (Splash class) to a cutscene (Cutscene class) and then to a playable level (PlayableLevel class). The game is launched from my Main class and transitions are supposed to be done with keyboard inputs (Enter button).
The method in Main that starts the game looks like this:
public void start (Stage s) {
// create your own game here
Splash splashPage = new Splash();
Cutscene cs = new Cutscene();
PlayableLevel play = new PlayableLevel();
// attach game to the stage and display it
Scene scene0 = splashPage.init(SIZE, SIZE);
Scene scene1 = cs.init(SIZE, SIZE, 0);
Scene scene2 = play.init(SIZE, SIZE, 0);
s.setScene(scene0);
s.show();
// sets the game's loop
KeyFrame frame = new KeyFrame(Duration.millis(MILLISECOND_DELAY),
e -> myGame.step(SECOND_DELAY));
Timeline animation = new Timeline();
animation.setCycleCount(Timeline.INDEFINITE);
animation.getKeyFrames().add(frame);
animation.play();
}
My question in particular is what should I do to make it so that the Splash class can communicate with the Main class so that once a keystroke event is recorded, the stage can set a new scene? I'm currently reading about EventHandlers but I'm unsure of the exact implementation as of now.
EDIT: One idea I had was to make a linked list of Scenes, and then once some event happens (keystroke), then I would set the scene to the next one in the list.
You could do something like this:
public class Splash {
private Runnable nextSceneHandler ;
public void setNextSceneHandler(Runnable handler) {
nextSceneHandler = handler ;
}
public Scene init(double width, double height) {
Scene scene = new Scene();
// Just an example handler, you could do the same for
// button events, menus, etc., or even just handlers for the
// end of an animation
scene.setOnKeyPressed(e -> {
if (nextSceneHandler != null) {
if (e.getCode() == ...) {
nextSceneHandler.run();
}
}
}
// existing code...
}
// existing code ...
}
and similarly for CutScene.
Then
public void start (Stage s) {
// create your own game here
Splash splashPage = new Splash();
Cutscene cs = new Cutscene();
PlayableLevel play = new PlayableLevel();
// attach game to the stage and display it
Scene scene0 = splashPage.init(SIZE, SIZE);
Scene scene1 = cs.init(SIZE, SIZE, 0);
Scene scene2 = play.init(SIZE, SIZE, 0);
splashPage.setNextSceneHandler(() -> s.setScene(scene1));
cs.setNextSceneHandler(() -> s.setScene(scene2));
s.setScene(scene0);
s.show();
// sets the game's loop
KeyFrame frame = new KeyFrame(Duration.millis(MILLISECOND_DELAY),
e -> myGame.step(SECOND_DELAY));
Timeline animation = new Timeline();
animation.setCycleCount(Timeline.INDEFINITE);
animation.getKeyFrames().add(frame);
animation.play();
}
Your linked list idea should work too. You need a mechanism for passing the linked list instance (or perhaps an iterator from it) to each of the scene generating classes; their event handlers would execute code like
scene.getWindow().setScene(sceneIterator.next());
I kind of prefer setting the runnable on the objects, as it feels a little more flexible. Just a question of style though.
I've been using JavaFx lately, as a beginner, and have been very impressed. At the moment I'm stuck trying to set a pagination slideshow to automatically
move the slideshow forward every 5 seconds (and back to the first slide to continue when the last slide is reached). Can any one steer me in the right direction here?
#FXML
public void slideshow(ActionEvent event) {
// TODO Auto-generated method stub
String[] photos = { "housestark.jpg", "housefrey.jpg", "housebar.jpg",
"HouseBolton.jpg", "housegreyjoy.jpg", "houseaaryn.jpg",
"houselannis.jpg", "housemart.jpg", "housereed.jpg",
"housetully.jpg", "housetyrel.jpg", };
Pagination p = new Pagination(photos.length);
p.setPageFactory((Integer pageIndex) -> {
return new ImageView(getClass().getResource(photos[pageIndex])
.toExternalForm());
});
Stage stage = new Stage();
stage.setScene(new Scene(p));
stage.setX(1250);
stage.setY(10);
stage.setTitle("Slideshow");
stage.setResizable(false);
stage.show();
}
This is my code so far! I would appreciate any help anyone could give?
It's pretty easy. All you have to do is create a timer that runs every 5 seconds, and when it runs move the page index.
public class SO extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Pagination p = new Pagination(10);
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(5), event -> {
int pos = (p.getCurrentPageIndex()+1) % p.getPageCount();
p.setCurrentPageIndex(pos);
}));
fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE);
fiveSecondsWonder.play();
stage.setScene(new Scene(p));
stage.show();
}
}
the five second wonder came from here: JavaFX periodic background task
this feels like I am cheating or doing something wrong. I am a Java student working on a simple JavaFX project.
As I loop through and create buttons in a flowPane, I was having trouble using the loop counter i inside an inner class. It's the part where I assign event handlers. I have dealt with this issue before, I get the difference between "final" and "effectively final" so I don't believe I am asking that.
It's just that creating this copy of i by using "int thisI = i" just feels wrong, design-wise. Is there not a better way to do this? I looked into lambdas and they also have the "final or effectively final" requirement.
Here's my code, any level or criticism or suggestion for improvement is welcome, thanks!
private FlowPane addFlowPaneCenter() {
FlowPane flow = new FlowPane();
flow.setPadding(new Insets(0, 0, 0, 0));
flow.setVgap(0);
flow.setHgap(0);
flow.setPrefWrapLength(WIDTH_OF_CENTER); // width of function buttons
Button centerButtons[] = new Button[NUM_BUTTONS];
ImageView centerImages[] = new ImageView[NUM_BUTTONS];
for (int i=0; i < NUM_BUTTONS; i++) {
centerImages[i] = new ImageView(
new Image(Calculator.class.getResourceAsStream(
"images/button-"+(i)+".png")));
centerButtons[i] = new Button();
centerButtons[i].setGraphic(centerImages[i]);
centerButtons[i].setPadding(Insets.EMPTY);
centerButtons[i].setId("button-"+(i));
flow.getChildren().add(centerButtons[i]);
// add a drop shadow on mouseenter
DropShadow shadow = new DropShadow();
// ***** here's the workaround is this really a good approach
// to use this in the inner class instead of i? thanks *****
int thisI = i;
// set event handlers for click, mousein, mouseout
centerButtons[i].setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
// change graphic of button to down graphic
ImageView downImage = new ImageView(new
Image(Calculator.class.getResourceAsStream(
"images/button-"+(thisI)+"D.png")));
// call function to effect button press
System.out.println("Button click");
// change graphic back
centerButtons[thisI].setGraphic(centerImages[thisI]);
}});
centerButtons[i].addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
centerButtons[thisI].setEffect(shadow);
}
});
centerButtons[i].addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
centerButtons[thisI].setEffect(null);
}
});
}
return flow;
}
You can remove the arrays centerButtons and centerImages completely. Instead create local variables for the image and the button within the loop and use those, e.g.
final ImageView image = new ImageView(...);
final Button button = new Button();
button.setGraphic(centerImages[i]);
...
You can use the local variables in your eventhandlers, e.g.
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
...
// change graphic back
button.setGraphic(image);
}});
Two minor improvements I noticed:
Try to avoid creating an Image more than once, because every time you create an Image, the actual data will be loaded again. Your handler will create a new Image for each click. I usually create Images in static final fields.
Event handlers are a nice opportunity to practice lambda expressions. :)
I have start writing a shooting game JavaFX application. I am using Shape.intersect() to check the collision of bullet and the target. Below is my code and I made it simple so as to post here.
public class TestShapeIntersect extends Application{
AnchorPane anchorPane;
ArrayList<Rectangle> targetObjects;
public static void main(String[] arg){
launch(arg);
}
#Override
public void start(Stage stage) throws Exception {
final Rectangle gun = new Rectangle(50, 50, Color.RED);
anchorPane = new AnchorPane();
anchorPane.getChildren().add(gun);
generateTargetObjects(50); // Number of target objects
anchorPane.getChildren().addAll(targetObjects);
gun.setX(50);
gun.setY(200);
Scene scene = new Scene(anchorPane,300,300,Color.GREEN);
stage.setScene(scene);
stage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
Rectangle bullet = new Rectangle(5,10,Color.ORANGE);
bullet.setX(75);
bullet.setY(200);
anchorPane.getChildren().add(bullet);
animateBullet(bullet);
}
});
}
private void generateTargetObjects(int noOfTargetObj) {
targetObjects = new ArrayList<Rectangle>();
for(int i=1; i<=noOfTargetObj;i++){
Rectangle rect = new Rectangle(30, 30, Color.YELLOW);
targetObjects.add(rect);
}
}
void animateBullet(final Rectangle bullet){
Timeline timeline = new Timeline();
timeline.setCycleCount(500);
final KeyFrame kf = new KeyFrame(Duration.millis(2), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-1);
checkCollision(bullet);
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
//This method will check if there is any collision happened between the bullets and the targets.
//If collision happens then both bullet and target object will be disappeared.
void checkCollision(Rectangle bullet){
int noOfTargetObjs = targetObjects.size();
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
targetObjects.get(i).setVisible(false);
bullet.setVisible(false);
}
}
}
}
}
I have not yet aligned the nodes properly.Here the 'gun' rectangle will fire 'bullet' rectangle whenever any key press event is detected.
The problem is for every every first bullet fired in each application session, the very first bullet is not animated properly (means the bullet is not going in it path continuously). But after the first bullet has gone the remaining bullets are animated properly. This performance issue increases with the number of 'target' objects increases.
I have found out that the issue is because of this line:
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
Could anyone let me know why this happens and what could be the solution to resolve this issue? Or is it because of the way that I'm implementing?
I experienced a different behaviour when i executed your application. My first shot was moving fine without any interruptions in the translation. But after several shots the application began to slow down. I tried to improve the performance of your code by doing the following steps:
void animateBullet(final Rectangle bullet){
final Timeline timeline = new Timeline();
timeline.setCycleCount(125); //changed
final KeyFrame kf = new KeyFrame(Duration.millis(16), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-8); //changed
checkCollisionThreaded(bullet); //changed
//added
if(bullet.getX() < 0 || bullet.getX() > bullet.getParent().getBoundsInParent().getWidth()
|| bullet.getY() < 0 || bullet.getY() > bullet.getParent().getBoundsInParent().getHeight())
{
bullet.setVisible(false);
timeline.stop();
AnchorPane ap = (AnchorPane) bullet.getParent();
ap.getChildren().remove(bullet);
}
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
Your value for the Duration.millis factor in the KeyFrame was 2, which is not really necessary to run a fluent animation, because JavaFX has a fixed framerate of 60 frame per second, which means that every 16,7 milliseconds a new frame is rendered and displayed. So you can use 16ms as frame-duration without making the animation stutter.
The if-statement checks if the bullet is outside the visible screen, which could happen in your previous code. Non-visible nodes should be removed from the scene graph. It doesn't help if you set a node to setVisible(false), because the node will stay on the scene graph. The Timeline animation should also be stopped, because it would trigger new checkCollision calls. As you can see, i changed the method checkCollision to checkCollisionThreaded. The method is shown below.
public void checkCollisionThreaded(final Rectangle bullet)
{
final int noOfTargetObjs = targetObjects.size();
Task<Integer> t = new Task<Integer>()
{
#Override
protected Integer call() throws Exception
{
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
return i;
}
}
}
return -1;
}
#Override
protected void succeeded()
{
super.succeeded();
if(this.getValue().intValue() != -1)
{
Node obj = targetObjects.get(this.getValue().intValue());
obj.setVisible(false);
AnchorPane ap = (AnchorPane) obj.getParent();
ap.getChildren().remove(obj);
targetObjects.remove(this.getValue().intValue());
bullet.setVisible(false);
}
}
};
Thread thread = new Thread(t);
thread.start();
}
There are some violations against the rule "don't touch any objects on the scene graph with a thread different to the JavaFX application thread", but as far as i can see, only reading methods access the scene graph (and it's objects) in the call() method. This method is run on a new Thread, which improves performance. The method succeeded() is run on the JavaFX Application Thread, so that we can safely remove things from our scene graph. I assumed that you want to remove your targets from the scene once they were hit.
It should be said that there might be issues related to the multithreaded code. There could be errors when getting final int noOfTargetObjs = targetObjects.size(); while modifying it on another thread. I left out any synchronization to reduce the complexity of the code.
My guess is that you're sending way too many requests to Shape.intersect(...), which is probably a fairly expensive method to execute. Initially this is causing performance problems, but when the number of calls to the method hits some threshold, the JVM's JIT compiler kicks in and compiles that method, relieving some of the problems. (Again, this is all guesswork.)
Using a TranslateTransition for the bullet and listening to its boundsInParent property to check for collisions seems to work better. I think the reason is that using this technique only checks for collisions when the JavaFX machinery actually moves the bullet. In your code you are performing these checks much more often.
Here's an example.