Javafx 8 drawing a line between translated nodes - javafx-8

How do you draw a line between the centers of translated nodes? Given for example the following code snippet:
public class Test extends Application{
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Circle circle1=new Circle(10, Color.GREEN);
root.getChildren().add(circle1);
Circle circle2=new Circle(10, Color.RED);
root.getChildren().add(circle2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
circle1.setTranslateX(100);
Line line=new Line(circle1.getCenterX(), circle1.getCenterY(), circle2.getCenterX(), circle2.getCenterY());
root.getChildren().add(line);
}
public static void main(String[] args) {
launch(args);
}
}
Running this application will clearly show a red and a green circle. However, there won't be a line because each of the centers of the circles are at the coordinates (0,0). Nevertheless, the circles do not cover each other because one of the circles is translated. This doesn't work:
Line line=new Line(circle1.getCenterX()+circle1.getTranslateX(), circle1.getCenterY()+circle1.getTranslateY(), circle2.getCenterX()+circle2.getTranslateX(), circle2.getCenterY()+circle2.getTranslateY());
Finally, let's assume that there is an approach to draw a line connecting the centers of the two circles. If, after the line is drawn, I would invoke circle2.setTranslateX(50);, how do I ensure that the endpoint of the line on the side of circle2 moves accordingly?

A StackPane is a managed layout pane, meaning that it manages the positions of its child nodes (by default it centers them); the translation is applied after the StackPane positions the nodes. This is why the circles appear in different locations but the line is not where you expect. Using a Pane instead of a StackPane will make things work as you expect.
To keep the line in the correct position relative to the circles when the circles are repositioned dynamically, bind the startX, startY, endX, and endY properties, instead of just setting them.
import javafx.animation.Animation;
import javafx.animation.ParallelTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;
public class LineConnectingCircles extends Application{
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Circle circle1=new Circle(10, Color.GREEN);
root.getChildren().add(circle1);
Circle circle2=new Circle(10, Color.RED);
root.getChildren().add(circle2);
// move circles so we can see them:
circle1.setTranslateX(100);
circle2.setTranslateY(50);
Line line = new Line();
// bind ends of line:
line.startXProperty().bind(circle1.centerXProperty().add(circle1.translateXProperty()));
line.startYProperty().bind(circle1.centerYProperty().add(circle1.translateYProperty()));
line.endXProperty().bind(circle2.centerXProperty().add(circle2.translateXProperty()));
line.endYProperty().bind(circle2.centerYProperty().add(circle2.translateYProperty()));
root.getChildren().add(line);
// create some animations for the circles to test the line binding:
Button button = new Button("Animate");
TranslateTransition circle1Animation = new TranslateTransition(Duration.seconds(1), circle1);
circle1Animation.setByY(150);
TranslateTransition circle2Animation = new TranslateTransition(Duration.seconds(1), circle2);
circle2Animation.setByX(150);
ParallelTransition animation = new ParallelTransition(circle1Animation, circle2Animation);
animation.setAutoReverse(true);
animation.setCycleCount(2);
button.disableProperty().bind(animation.statusProperty().isEqualTo(Animation.Status.RUNNING));
button.setOnAction(e -> animation.play());
BorderPane.setAlignment(button, Pos.CENTER);
BorderPane.setMargin(button, new Insets(10));
Scene scene = new Scene(new BorderPane(root, null, null, button, null), 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

My circles won't show. What am I missing here?

In Javafx, I am trying to create a pane where I can add points through a mouse click event. When you click on the pane a circle should appear at your mouse position. The circles are being created, as I am tracking them in the console, but they are not showing in the graphics.
I did a similar program to this that auto drew an image that resized with the stage/window, I am using all the same techniques but that project didn't include event handling.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
public class ClickToShape extends Application {
private ClickPane clickPane = new ClickPane();
#Override
public void start(Stage primaryStage) throws Exception {
Pane clickPane = new ClickPane();
clickPane.setOnMouseClicked(new ClickHandler());
// create the scene
Scene clickScene = new Scene(clickPane, 500, 500);
// set up the window/stage
primaryStage.setTitle("Click To Draw");
primaryStage.setScene(clickScene); // add the scene to the stage
primaryStage.show(); // fire it off
}
public static void main(String[] args) {
launch(args);
}
class ClickHandler implements EventHandler<MouseEvent> {
#Override
public void handle(MouseEvent e) {
System.out.println("MouseEvent occured");
clickPane.addPoint(e.getX(), e.getY());
}
}
}
class ClickPane extends Pane{
private ArrayList<Circle> points = new ArrayList<Circle>();
private Color color1 = Color.BLACK;
public void addPoint(double x, double y) {
System.out.println("A new point function ran");
Circle newPoint = new Circle (x, y, 300, color1 );
System.out.println(newPoint.toString());
points.add(newPoint);
getChildren().clear();
getChildren().add(newPoint);
}
}
There are no error messages.
the problem is that you instantiated two ClickPane objects, one outside the start method, and another inside the start method, you added the second one to the scene but used the first one to add points, and that's why points aren't showing in your scene
what you can do about this is delete the first line in your start method, so the application will be using the same instance to fire events as to add to the scene, the code would look like this
import java.util.ArrayList;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
public class ClickToShape extends Application {
private ClickPane clickPane = new ClickPane();
#Override
public void start(Stage primaryStage) throws Exception {
clickPane.setOnMouseClicked(new ClickHandler());
// create the scene
Scene clickScene = new Scene(clickPane, 500, 500);
// set up the window/stage
primaryStage.setTitle("Click To Draw");
primaryStage.setScene(clickScene); // add the scene to the stage
primaryStage.show(); // fire it off
}
public static void main(String[] args) {
launch(args);
}
class ClickHandler implements EventHandler<MouseEvent> {
#Override
public void handle(MouseEvent e) {
System.out.println("MouseEvent occured");
clickPane.addPoint(e.getX(), e.getY());
}
}
}
class ClickPane extends Pane{
private ArrayList<Circle> points = new ArrayList<Circle>();
private Color color1 = Color.BLACK;
public void addPoint(double x, double y) {
System.out.println("A new point function ran");
Circle newPoint = new Circle (x, y, 10, color1 );
System.out.println(newPoint.toString());
points.add(newPoint);
getChildren().setAll(newPoint);
}
}

ControlsFX HiddenSidesPane hides When clicked

I am using ControlsFX - HiddenSidesPane where i add some link(ToggleButtons) to be clicked for navigation.
The problem i have is whenever anything is clicked, the HiddenSiddesPane hides.
The desired behavior is when anything inside it is clicked it should not close/hide, unless cursor hovers out of it.
SSCCE to demonstrate undesired behavior
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.HiddenSidesPane;
public class MyHiddenSidesPaneDemo extends Application{
public static void main(String[] args) { Application.launch(args); }
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
TableView tv = new TableView();
HiddenSidesPane hiddenSidesPane = new HiddenSidesPane();
hiddenSidesPane.setContent(tv);
hiddenSidesPane.setLeft(new ListView());
root.getChildren().addAll(hiddenSidesPane);
primaryStage.setTitle("HiddenSidesPane Example Demo");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Try the following:
ListView listView = new ListView();
hiddenSidesPane.setLeft(listView);
listView.setOnMouseEntered(e->hiddenSidesPane.setPinnedSide(Side.LEFT)); //Keep left side pinned
listView.setOnMouseExited(e->hiddenSidesPane.setPinnedSide(null)); //unpin when mouse exits

JavaFX8 - How to draw random circles with random x/y centers?

I am trying to draw random circles with random x/y centers, but the result of my code is only one circle at the center of the stage (window).
I use Task class to update my UI every 1 second.
This is my code:
package javafxupdateui;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class JavaFXUpdateUI extends Application {
private Stage window;
private StackPane layout;
private Scene scene;
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("JavaFX - Update UI");
layout = new StackPane();
scene = new Scene(layout, 500, 500);
window.setScene(scene);
window.show();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run() {
drawCircles();
}
});
Thread.sleep(1000);
}
}
};
public void drawCircles() {
Circle circle;
float x = (float)(Math.random()*501);
float y = (float)(Math.random()*501);
circle = new Circle(x, y, 25, Color.RED);
layout.getChildren().add(circle);
scene.setRoot(layout);
window.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
The result of the above code is:
Result GUI
What is going wrong
StackPane is a layout pane, it centers everything by default. As you want to manually place the circles at random locations, you don't want to use a pane which manages the layout for you.
How to fix it
Use a Pane or a Group instead of StackPane. Neither Pane nor Group manage the layout of items for you, so children you add to them at specific locations will remain at those locations.
Aside
You might wish to use a Timeline for your periodic updates rather than a Task with runLater (though the later will still work OK, with a Timeline you don't have to deal with additional complexities of concurrent code).

JavaFX: How to make a Node partially mouse transparent?

Simplified Problem:
Make one Node "A" that is on top of another Node "B" to be half transparent to MouseEvents, so the Events will reach the underlying Node "B". Both Nodes are of equal size but Node "A" has a half transparent background image so one half of Node "B" is visible.
Real Problem:
I have a menu of tabs. Each tab can be dragged to expand the corresponding menu layer. Therefore each tab layer is a Pane with a partially transparent background (basically a polygon) of which the transparent part should be also transparent to MouseEvents.
The illustration (which I can't post yet, see link: Illustration of tabs, the dark green line is the border of the green Pane) shows the basic principle: just imagine only the tabs are visible and the layer itself can be pulled to the right to view it's content.
So the question is, how do I make a region of a Node transparent to MouseEvents without making the whole Node transparent?
Thank you for your help!
Update:
To clarify the simple problem here is the corresponding code:
//Create parent group
Group root = new Group();
//Create linear gradient, so one side is transparent
Stop[] stops = new Stop[] { new Stop(0, Color.rgb(0, 255, 0, 0.0)), new Stop(1, Color.rgb(0, 255, 0, 1.0))};
LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops);
//Create the rectangles
Rectangle A = new Rectangle(100, 50, lg1);
Rectangle B = new Rectangle(100,50, Color.RED);
//Add eventHandlers
A.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("Clicked A");
}
});
B.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("Clicked B");
}
});
root.getChildren().addAll(B, A);
//Add to Scene..
Hope this helps.
Consider the pickOnBounds property, it may help in your situation, but it is not clear to me without seeing your code attempt which fails for the simplified problem.
node.setPickOnBounds(true)
If pickOnBounds is true, then picking is computed by intersecting with the bounds of this node, else picking is computed by intersecting with the geometric shape of this node.
The code below demonstrates how this may be used by creating a square overlaid by an ImageView for an Image which contains tranparent pixels. If pickOnBounds is set to true for the ImageView, then, even if you click on the transparent pixels in the image, the ImageView will receive the mouseClick event. If pickOnBounds is set to false for the ImageView, then, even if you click on the transparent pixels in the image, the ImageView will not process the click and the click event will be received by the node behind the image.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class PickOnBoundsDemo extends Application {
public static void main(String[] args) { Application.launch(args); }
#Override public void start(Stage stage) {
final Rectangle back = new Rectangle(128, 128);
back.setFill(Color.FORESTGREEN);
final ImageView front = new ImageView("http://icons.iconarchive.com/icons/aha-soft/free-large-boss/128/Wizard-icon.png");
// icon: Linkware (Backlink to http://www.aha-soft.com required)
final StackPane pickArea = new StackPane();
pickArea.getChildren().addAll(
back,
front
);
final ToggleButton pickTypeSelection = new ToggleButton("Pick On Bounds");
final Label pickResult = new Label();
Bindings.bindBidirectional(front.pickOnBoundsProperty(), pickTypeSelection.selectedProperty());
front.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent t) {
pickResult.setText("Front clicked");
}
});
back.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent t) {
pickResult.setText("Back clicked");
}
});
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().setAll(
pickArea,
new Label("Click inside the above area to test mouse picking."),
pickTypeSelection,
pickResult
);
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}

Adjusting Height,Width of JavaFX Chart

I'm new to Java with PHP, HTML, CSS experience. When I try to change the width and height my chart takes up in the window NetBeans gives me the error:
error: setWidth(double) has protected access in Region chart.setWidth(450);
I've searched through the javafx docs and found that width/height is bound to region, but I'm not sure what that is in my code, I tried a few things but haven't found it...
I'm sure this is simple..
thanks in advance, Brad.
package test;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.shape.Rectangle;
public class Test extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root,1000,1000));
root.getStylesheets().add("test/Chart.css");
Rectangle rect = new Rectangle(35,70);
rect.setLayoutX(30);
rect.setLayoutY(30);
rect.getStyleClass().add("my-rect");
NumberAxis xAxis = new NumberAxis("X Axis", -24d, 24.0d, 2.0d);
NumberAxis yAxis = new NumberAxis("Y Axis", -24.0d, 24.0d, 1.0d);
ObservableList<XYChart.Series> data = FXCollections.observableArrayList(
new ScatterChart.Series("Series 1", FXCollections.<ScatterChart.Data>observableArrayList(
new XYChart.Data(0.2, 3.5),
new XYChart.Data(0.7, 4.6),
new XYChart.Data(1.8, 1.7),
new XYChart.Data(2.1, 2.8),
new XYChart.Data(4.0, 2.2),
new XYChart.Data(4.1, 2.6),
new XYChart.Data(4.5, 2.0),
new XYChart.Data(6.0, 3.0),
new XYChart.Data(7.0, 2.0),
new XYChart.Data(7.8, 4.0)
)),
new ScatterChart.Series("Series 2", FXCollections.<ScatterChart.Data>observableArrayList(
new XYChart.Data(6.2,3.0),
new XYChart.Data(6.0,4.0),
new XYChart.Data(5.8,5.0)
))
);
ScatterChart chart = new ScatterChart(xAxis, yAxis, data);
chart.setWidth(450);
chart.setHeight(450);
chart.setLayoutX(250);
chart.setLayoutY(250);
root.getChildren().addAll(chart,rect);
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
The javadoc of ScatterChart.getHeight() (which in turn is Region.getHeight()) says
Gets the value of the property height.
Property description:
The height of this resizable node. This property is set by the region's
parent during layout and may not be set by the application. If an
application needs to explicitly control the size of a region, it
should override its preferred size range by setting the minHeight,
prefHeight, and maxHeight properties.
Namely you can adjust and constraint the size of any chart with:
ScatterChart.setPrefHeight(double)
ScatterChart.setMinHeight(double)
ScatterChart.setMaxHeight(double)
ScatterChart.setPrefWidth(double)
ScatterChart.setMinWidth(double)
ScatterChart.setMaxWidth(double)
ScatterChart.setPrefSize(double, double)
ScatterChart.setMinSize(double, double)
ScatterChart.setMaxSize(double, double)