JavaFX Chart: Dynamic data causes visual axis glitches when animation is enabled - animation

Application with visual glitch
Application without visual glitch
Whenever I have a chart that has dynamically updating data and I leave chart.setAnimated(true) as it is, the axis will visually glitch every now and again as shown on the screenshots.
Is it at all possible to prevent this while keeping animation enabled?
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Slider;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.geometry.Insets;
public class SavingsCalculatorApplication extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart chart = new LineChart(xAxis, yAxis);
chart.setLegendVisible(false);
chart.setCreateSymbols(false);
Slider slider = new Slider(0, 100, 10);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setPadding(new Insets(20, 40, 0, 40));
XYChart.Series data = new XYChart.Series();
chart.getData().add(data);
slider.valueProperty().addListener(event -> {
data.getData().clear();
for(int counter = 0; counter < 100; counter++) {
data.getData().add(new XYChart.Data(counter, counter * slider.getValue()));
}
});
VBox layout = new VBox();
layout.getChildren().add(slider);
layout.getChildren().add(chart);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(SavingsCalculatorApplication.class);
}
}

Related

Is it possible to do a FadeTransition to sides?

I would like to know if there is a way to do a FadeTransition from the right or left or even top of bottom kind of like the example here:
Style the label with a text fill set to a linear gradient, and change the stops of the linear gradient in a timeline.
Here's a basic example (click on "Fading Label" to see the animation). A similar approach should work for fading out.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TextFade extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label("Fading label:");
Label fadingLabel = new Label("text fades in and out");
DoubleProperty startFade = new SimpleDoubleProperty(0);
DoubleProperty endFade = new SimpleDoubleProperty(0);
fadingLabel.styleProperty().bind(Bindings.createStringBinding(() -> String.format(
"-fx-text-fill: linear-gradient(to right, -fx-text-background-color 0%%, -fx-text-background-color %f%%, transparent %f%%, transparent 100%%);",
startFade.get()*100, endFade.get()*100
), startFade, endFade));
HBox root = new HBox(2, label, fadingLabel);
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(startFade, 0), new KeyValue(endFade, 0)),
new KeyFrame(Duration.seconds(1.0/3.0), new KeyValue(startFade, 0)),
new KeyFrame(Duration.seconds(2.0/3.0), new KeyValue(endFade, 1)),
new KeyFrame(Duration.seconds(1), new KeyValue(startFade, 1)));
label.setOnMouseClicked(e -> timeline.play());
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX: Animation not moving

I am trying to make an animation in which the car moves from left to right. If it reaches the right end, it starts over again. This type of animation is easy with PathTransition. But I have to change the car speed through the keys UP/DOWN during animation. For some reason I am not able to do that with PathTransition.
So, I am making a simple animation. But the car is not moving in this case. Can someone help me find my mistake:
package exercise_15_29;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.animation.KeyFrame;
public class Exercise_15_29 extends Application {
static Group car = new Group();
Circle wheel1 = new Circle(15, 95, 5);
Circle wheel2 = new Circle(35, 95, 5);
Polygon body1 = new Polygon();
Rectangle body2 = new Rectangle(0.0, 80.0, 50, 10);
Line path = new Line(0, 90, 500, 90);
int speed = 100;
boolean play = true;
public static void main(String[] args) {
launch(args);
}
public static void moveCar(){
if(car.getLayoutX() == 500)
car.setTranslateX(-500);
else
car.setTranslateX(10);
}
#Override
public void start(Stage primaryStage) {
body1.getPoints().addAll(new Double[]{
10.0, 80.0,
20.0, 70.0,
30.0, 70.0,
40.0, 80.0
});
body1.setFill(Color.BLUE);
body2.setFill(Color.SKYBLUE);
path.setVisible(false);
car.getChildren().addAll(wheel1, wheel2, body1, body2);
Timeline animation = new Timeline(new KeyFrame(Duration.millis(speed),e -> moveCar()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
Pane root = new Pane();
root.getChildren().add(car);
root.getChildren().add(path);
Scene scene = new Scene(root, 500, 100);
scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.P) {
if (play)
animation.pause();
else
animation.play();
play = !play;
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.UP)
animation.setRate(animation.getRate() + 0.1);
else if (e.getCode() == KeyCode.DOWN){
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
});
}
}

JavaFx, how to add menuBar and drawingPane

I'an busy with some demo, drawing some lines in a scroll window. So far so good, but now it's possible to draw lines on the menuBar, which should not be possible of course. See code below. Please help!
This is what happens:
See output here
The wrong code:
package Example12a;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Example12a extends Application {
public static void main(String[] args) {
launch(args);
}
private Line curLine;
#Override
public void start(Stage stage) throws Exception {
Pane drawingPane = new Pane();
BorderPane theBorderPane = new BorderPane();
drawingPane.setPrefSize(800, 800);
drawingPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
MenuBar menuBar = new MenuBar();
// --- Menu File
Menu menuFile = new Menu("File");
MenuItem add = new MenuItem("Save");
add.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
System.out.println("Save");
}
});
menuFile.getItems().addAll(add);
//yOffset = (int)menuBar.getHeight();
Menu menuEdit = new Menu("Edit");
Menu menuView = new Menu("View");
menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
theBorderPane.setTop(menuBar);
ScrollPane scrollPane = new ScrollPane(theBorderPane);
scrollPane.setPrefSize(300, 300);
scrollPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setStyle("-fx-focus-color: transparent;");
theBorderPane.setOnMousePressed(event -> {
if (!event.isPrimaryButtonDown()) {
return;
}
curLine = new Line(
event.getX(), event.getY(),
event.getX(), event.getY()
);
theBorderPane.getChildren().add(curLine);
});
theBorderPane.setOnMouseDragged(event -> {
if (!event.isPrimaryButtonDown()) {
return;
}
if (curLine == null) {
return;
}
curLine.setEndX(event.getX());
curLine.setEndY(event.getY());
double mx = Math.max(curLine.getStartX(), curLine.getEndX());
double my = Math.max(curLine.getStartY(), curLine.getEndY());
if (mx > theBorderPane.getMinWidth()) {
theBorderPane.setMinWidth(mx);
}
if (my > theBorderPane.getMinHeight()) {
theBorderPane.setMinHeight(my);
}
});
theBorderPane.setOnMouseReleased(event -> curLine = null);
theBorderPane.setCenter(drawingPane);
Scene scene = new Scene(scrollPane);
stage.setMinWidth(100);
stage.setMinHeight(100);
stage.setScene(scene);
stage.show();
}
}
Fixed your layout.
What i did was:
The BorderPane is now your root Pane.
The ScrollPane is the center of the BorderPane and its content is the drawingPane.
The MenuBar is still the the Top of the BorderPane.
I also changed the Mouse Events from borderPane to drawingPane and the lines are added to the drawingPane instead of the borderPane.
So its working fine.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Example12a extends Application {
public static void main(String[] args) {
launch(args);
}
private Line curLine;
#Override
public void start(Stage stage) throws Exception {
Pane drawingPane = new Pane();
BorderPane theBorderPane = new BorderPane();
drawingPane.setPrefSize(800, 800);
drawingPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
MenuBar menuBar = new MenuBar();
// --- Menu File
Menu menuFile = new Menu("File");
MenuItem add = new MenuItem("Save");
add.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
System.out.println("Save");
}
});
menuFile.getItems().addAll(add);
//yOffset = (int)menuBar.getHeight();
Menu menuEdit = new Menu("Edit");
Menu menuView = new Menu("View");
menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
theBorderPane.setTop(menuBar);
ScrollPane scrollPane = new ScrollPane(drawingPane);
scrollPane.setPrefSize(300, 300);
scrollPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setStyle("-fx-focus-color: transparent;");
drawingPane.setOnMousePressed(event -> {
if (!event.isPrimaryButtonDown()) {
return;
}
curLine = new Line(
event.getX(), event.getY(),
event.getX(), event.getY()
);
drawingPane.getChildren().add(curLine);
});
drawingPane.setOnMouseDragged(event -> {
if (!event.isPrimaryButtonDown()) {
return;
}
if (curLine == null) {
return;
}
curLine.setEndX(event.getX());
curLine.setEndY(event.getY());
double mx = Math.max(curLine.getStartX(), curLine.getEndX());
double my = Math.max(curLine.getStartY(), curLine.getEndY());
if (mx > drawingPane.getMinWidth()) {
drawingPane.setMinWidth(mx);
}
if (my > drawingPane.getMinHeight()) {
drawingPane.setMinHeight(my);
}
});
theBorderPane.setOnMouseReleased(event -> curLine = null);
theBorderPane.setCenter(scrollPane);
Scene scene = new Scene(theBorderPane);
stage.setMinWidth(100);
stage.setMinHeight(100);
stage.setScene(scene);
stage.show();
}
}
Note:
if your trying to make a Drawing Programm I would prevere to Render all Lines in a Canvas instead of using the Line class. The Canvas is much faster with many Lines.

Combine Images for overlay

I try to do something similar to this post: related question
Now I try to combine several stencils. The combined stencil looks as expected, however the inverted group is either black or white, depending on the order of the group created. The result I get:
It seems that the grouping still takes some Blend-information of the childs, or maybe I just don't get the idea of the blends. Any Idea how I can achieve an overlay as in the old question but with several stencils?
Here are the images:
package application;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
Image original = new Image(getClass().getResourceAsStream("image.jpg"));
Image stencil1 = new Image(getClass().getResourceAsStream("stencil.jpg"));
Image stencil2 = new Image(getClass().getResourceAsStream("stencil2.jpg"));
Image stencil3 = new Image(getClass().getResourceAsStream("stencil3.jpg"));
ImageView iv = new ImageView(stencil1);
ImageView iv2 = new ImageView(stencil2);
ImageView iv3 = new ImageView(stencil3);
iv2.setBlendMode(BlendMode.ADD);
iv3.setBlendMode(BlendMode.ADD);
Group stencil = new Group();
stencil.getChildren().add(iv);
stencil.getChildren().add(iv2);
stencil.getChildren().add(iv3);
Rectangle whiteRect = new Rectangle(original.getWidth(), original.getHeight());
whiteRect.setFill(Color.WHITE);
whiteRect.setBlendMode(BlendMode.DIFFERENCE);
Group inverted = new Group(stencil, whiteRect);
// display the original, composite image and stencil.
HBox layout = new HBox(10);
layout.getChildren().addAll(new ImageView(original), inverted, stencil);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I finally figured it out myself. The trick is to create always new instances of the ImageView objects, as the blending modes are applied down the scene-graph 'till the imageview-objects.
here is the code snipped that worked for me:
package application;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
private ImageView originalImageView;
private ImageView stencilImageView;
private Image original;
private Image stencil;
private Image stencil2;
private Rectangle whiteRect;
private Group inverted;
private Group overlaidBlack;
private Rectangle redRect;
private Group redStencil;
private Group overlaidRed;
private ImageView stencilImageView2;
private Group to;
private Group to2;
#Override
public void start(Stage stage) {
original = new Image(getClass().getResourceAsStream("image.jpg"));
stencil = new Image(getClass().getResourceAsStream("stencil.jpg"));
stencil2 = new Image(getClass().getResourceAsStream("stencil2.jpg"));
stencilImageView = new ImageView(stencil);
// first invert the stencil so that it is black on white rather than white on black.
whiteRect = new Rectangle(stencil.getWidth(), stencil.getHeight());
whiteRect.setFill(Color.WHITE);
whiteRect.setBlendMode(BlendMode.DIFFERENCE);
stencilImageView2 = new ImageView(stencil2);
stencilImageView2.setBlendMode(BlendMode.ADD);
to = new Group(stencilImageView, stencilImageView2);
ImageView tmpIv = new ImageView(stencil2);
tmpIv.setBlendMode(BlendMode.ADD);
to2 = new Group(new ImageView(stencil), tmpIv);
inverted = new Group(to, whiteRect);
originalImageView = new ImageView(original);
overlaidBlack = new Group(originalImageView, inverted);
inverted.setBlendMode(BlendMode.MULTIPLY);
overlaidBlack = new Group(originalImageView, inverted);
// create a new mask with a red tint (red on black).
redRect = new Rectangle(stencil.getWidth(), stencil.getHeight());
redRect.setFill(Color.RED);
redRect.setBlendMode(BlendMode.MULTIPLY);
redStencil = new Group(to2, redRect);
redStencil.setBlendMode(BlendMode.ADD);
overlaidRed = new Group(overlaidBlack, redStencil);
// display the original, composite image and stencil.
HBox layout = new HBox(10);
layout.getChildren().addAll(new ImageView(original), overlaidRed);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch();
}
}

fading in and fading out of an circle in javafx canvas

I would like to fade in and fadeout a circle in a javafx canvas.
I can move a circle from one part of the screen to another but i just can't seem to get this object to fade in and out.
Below is the code i used for moving a circle from one part of the screen to the other
public class AnimatedCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty x = new SimpleDoubleProperty();
DoubleProperty y = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(x, 0),
new KeyValue(y, 0)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(x, W - D),
new KeyValue(y, H - D)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN);
gc.fillOval(
x.doubleValue(),
y.doubleValue(),
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
Your help is greatly appreciated
Animate a DoubleProperty for the opacity in values ranging from 1 to 0 in exactly the same way as you animated the properties for x and y, and then use Color.deriveColor(...) to set the color based on the changing property:
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty opacity = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(opacity, 1)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(opacity, 0)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN.deriveColor(0, 1, 1, opacity.get()));
gc.fillOval(
W/2,
H/2,
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
In general, I prefer not to use a Canvas for this, and just to use Shape instances placed in a Pane. Then you can directly change the pre-defined properties of the Shape, or in many cases use specific pre-defined animations (TranslateTransition or FadeTransition for example).
Here's the same example using this technique:
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
Circle circle = new Circle(W/2, H/2, D, Color.FORESTGREEN);
FadeTransition fade = new FadeTransition(Duration.seconds(3), circle);
fade.setFromValue(1);
fade.setToValue(0);
fade.setAutoReverse(true);
fade.setCycleCount(Timeline.INDEFINITE);
Pane pane = new Pane(circle);
stage.setScene(
new Scene(
pane, W, H, Color.CORNSILK
)
);
stage.show();
fade.play();
}
public static void main(String[] args) { launch(args); }
}

Resources