JAVAFX zoom, scroll in ScrollPane - java

I have JAVAFX application with zoom and scale as described here:
Scale at pivot point in an already scaled node
What I need to achieve is to place the image to right pane and keep the image on the left as depicted in the figure below.
Question
How can I embed this application into SPlitPane, where on left will be another panel.
SplitPane splitPane = new SplitPane();
splitPane.getItems().add(new Label("Left Panel"));
splitPane.getItems().add(group);
Scene scene = new Scene(splitPane, 1024, 768);
Unfortunately the code results in wrong coordinates,

For zooming inside a ScrollPane, you need to adjust the scroll positions. This is a bit more complex than just appending a transformation , which could be done, if no ScrollPane was used.
Basically you need to check the current scroll position before scaling the node and after scaling modify the scroll positions to prevent the pivot point from changing it's position because of the changed scale factor.
The following example lets you move a circle around "in" a grid and move around the grid when not clicking on a circle (pannable ScrollPane) in addition to allowing you to adjust the zoom when moving the mouse wheel while holding down the CTRL key.
import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class PivotZoom extends Application {
public static Region createContent() {
double width = 1000;
double height = 1000;
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.LIGHTGREY);
gc.fillRect(0, 0, width, height);
gc.setStroke(Color.BLUE);
gc.beginPath();
for (int i = 50; i < width; i += 50) {
gc.moveTo(i, 0);
gc.lineTo(i, height);
}
for (int i = 50; i < height; i += 50) {
gc.moveTo(0, i);
gc.lineTo(width, i);
}
gc.stroke();
Pane content = new Pane(
new Circle(50, 50, 20),
new Circle(120, 90, 20, Color.RED),
new Circle(200, 70, 20, Color.GREEN)
);
StackPane result = new StackPane(canvas, content);
result.setAlignment(Pos.TOP_LEFT);
class DragData {
double startX;
double startY;
double startLayoutX;
double startLayoutY;
Node dragTarget;
}
DragData dragData = new DragData();
content.setOnMousePressed(evt -> {
if (evt.getTarget() != content) {
// initiate drag gesture, if a child of content receives the
// event to prevent ScrollPane from panning.
evt.consume();
evt.setDragDetect(true);
}
});
content.setOnDragDetected(evt -> {
Node n = (Node) evt.getTarget();
if (n != content) {
// set start paremeters
while (n.getParent() != content) {
n = n.getParent();
}
dragData.startX = evt.getX();
dragData.startY = evt.getY();
dragData.startLayoutX = n.getLayoutX();
dragData.startLayoutY = n.getLayoutY();
dragData.dragTarget = n;
n.startFullDrag();
evt.consume();
}
});
// stop dragging when mouse is released
content.setOnMouseReleased(evt -> dragData.dragTarget = null);
content.setOnMouseDragged(evt -> {
if (dragData.dragTarget != null) {
// move dragged node
dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX);
dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY);
Point2D p = new Point2D(evt.getX(), evt.getY());
evt.consume();
}
});
return result;
}
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Region zoomTarget = createContent();
zoomTarget.setPrefSize(1000, 1000);
zoomTarget.setOnDragDetected(evt -> {
Node target = (Node) evt.getTarget();
while (target != zoomTarget && target != null) {
target = target.getParent();
}
if (target != null) {
target.startFullDrag();
}
});
Group group = new Group(zoomTarget);
// stackpane for centering the content, in case the ScrollPane viewport
// is larget than zoomTarget
StackPane content = new StackPane(group);
group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// keep it at least as large as the content
content.setMinWidth(newBounds.getWidth());
content.setMinHeight(newBounds.getHeight());
});
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPannable(true);
scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// use vieport size, if not too small for zoomTarget
content.setPrefSize(newBounds.getWidth(), newBounds.getHeight());
});
content.setOnScroll(evt -> {
if (evt.isControlDown()) {
evt.consume();
final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2;
Bounds groupBounds = group.getLayoutBounds();
final Bounds viewportBounds = scrollPane.getViewportBounds();
// calculate pixel offsets from [0, 1] range
double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
// convert content coordinates to zoomTarget coordinates
Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY())));
// calculate adjustment of scroll position (pixels)
Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
// do the resizing
zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX());
zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY());
// refresh ScrollPane scroll positions & content bounds
scrollPane.layout();
// convert back to [0, 1] range
// (too large/small values are automatically corrected by ScrollPane)
groupBounds = group.getLayoutBounds();
scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
}
});
StackPane left = new StackPane(new Label("Left Menu"));
SplitPane root = new SplitPane(left, scrollPane);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Try adding what it is you wish to zoom and scroll to a group layout. Then add the group to a scrollpane. This will keep the node you are zooming on the left position of the scrollpane. Hope this solves your problem!
Node you are zooming inside group,
Group inside scrollpane,
Scrollpane on the rightside of the
Splitpane!!

Related

How to create a new ball every 5 seconds (5000 milliseconds) in JavaFX?

I am new to JavaFX and I am creating a simple program. What I'm trying to achieve is to create a ball every 5 seconds that bounces off the walls and all balls should move every tens times per second (10 milliseconds). Also, feel free to leave other suggestions about my code.
Here's the source code:
public class Main extends Application {
#Override
public void start(Stage stage) {
//Sets the title, adds a group, and background color
BorderPane canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
Circle ball = new Circle(10, Color.RED);
ball.relocate(0, 10);
canvas.getChildren().add(ball);
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(20), new EventHandler<ActionEvent>() {
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
#Override
public void handle(ActionEvent t) {
//move the ball
ball.setLayoutX(ball.getLayoutX() + dx);
ball.setLayoutY(ball.getLayoutY() + dy);
Bounds bounds = canvas.getBoundsInLocal();
//If the ball reaches the left or right border make the step negative
if(ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius()) ){
dx = -dx;
}
//If the ball reaches the bottom or top border make the step negative
if((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))){
dy = -dy;
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Here is one way to do it as suggested in comments:
Create a custom object that holds the ball and its orientation information.
Add the newly created object in the list and the ball in canvas.
Loop through all balls and position them based on their orientation information.
When your desired time is reached add a new ball.
To keep it simple, you don't need any concurrent related stuff. Maybe using them will improve the implementation (but I am not touching that now). :-)
Here is a demo of using the points I mentioned.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
public class CanvasBallCreation_Demo extends Application {
List<Ball> balls = new ArrayList<>();
BorderPane canvas;
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
double refresh = 20;//ms
double addBallDuration = 5000;//ms
double temp = 0;
SecureRandom rnd = new SecureRandom();
#Override
public void start(Stage stage) {
canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
addBall();
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(refresh), e->moveBalls()));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private void addBall(){
Ball ball = new Ball();
balls.add(ball);
canvas.getChildren().add(ball.getBall());
}
private void moveBalls() {
temp = temp + refresh;
if(temp>addBallDuration){
temp = 0;
addBall();
}
Bounds bounds = canvas.getBoundsInLocal();
balls.forEach(obj -> {
Circle ball = obj.getBall();
double tx = obj.getTx();
double ty = obj.getTy();
ball.setLayoutX(ball.getLayoutX() + dx*tx);
ball.setLayoutY(ball.getLayoutY() + dy*ty);
//If the ball reaches the left or right border make the step negative
if (ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius())) {
obj.setTx(-tx);
}
//If the ball reaches the bottom or top border make the step negative
if ((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))) {
obj.setTy(-ty);
}
});
}
class Ball {
Circle ball = new Circle(10, Color.RED);
double tx = 1;
double ty = 1;
public Ball(){
// Placing the ball at a random location between 0,0 and 10,10 to generate random paths
ball.relocate(rnd.nextInt(10), rnd.nextInt(10));
}
public Circle getBall() {
return ball;
}
public double getTx() {
return tx;
}
public void setTx(double tx) {
this.tx = tx;
}
public double getTy() {
return ty;
}
public void setTy(double ty) {
this.ty = ty;
}
}
}
There are a few options, java.util.concurrent.TimeUnit or Thread.sleep.
You may want to look at both, and if you need two operations going at once (creating new balls, balls moving) I would look into using a new thread for each ball.
You can read more about using threads by using these articles, and your simple program actually seems like a great use case for learning about them.
https://www.geeksforgeeks.org/multithreading-in-java/
https://www.tutorialspoint.com/java/java_multithreading.htm
https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html

check if any part of the circle drawn visible in the screen

In my application, I'm drawing a lot of circles of different radii on a javafx canvas using the fillOval and strokeOval methods. I am trying to optimize this draw operation by drawing only those circles which will be visible(partially also) in my screen. So I want to know is there a method that can tell me that drawing this circle, text, line, image etc. will be visible in the screen.
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Pane pane;
Canvas canvas;
double transX = 0.0, transY = 0.0;
public void draw() {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(canvas.getLayoutX(), canvas.getLayoutY(), canvas.getWidth(), canvas.getHeight());
gc.setStroke(Color.FIREBRICK);
gc.setFill(Color.TRANSPARENT);
gc.setLineWidth(2.0);
for(int i = 1; i <= 10000; ++i) {
double r = 10 * i;
// some function to check the below drawing operations will be visible or not
// Pls. note centre of the circles need not be at the centre of the screen, if canvas is panned to right/left/top/bottom by mouse drag operation where translation values will be stored in transX and transY
gc.fillOval(pane.getWidth() / 2 - r + transX, pane.getHeight() / 2 - r + transY, 2 * r, 2 * r);
gc.strokeOval(pane.getWidth() / 2 - r + transX, pane.getHeight() / 2 - r + transY, 2 * r, 2 * r);
}
}
#Override
public void start(Stage primaryStage) {
pane = new Pane();
canvas = new Canvas();
canvas.layoutXProperty().bind(pane.layoutXProperty());
canvas.layoutYProperty().bind(pane.layoutYProperty());
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
pane.widthProperty().addListener((observableValue, number, t1) -> draw());
pane.heightProperty().addListener((observableValue, number, t1) -> draw());
pane.getChildren().addAll(canvas);
primaryStage.setScene(new Scene(pane, primaryStage.getWidth(), primaryStage.getHeight()));
primaryStage.setTitle("Canvas Demo");
primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}

Define object position at runtime with javafx

I'm making an application with javaFX. The application consists of creating a kind of "graph" from the user.
The user through a button creates a node (is created by a circle with JAVAFX figure) and associates it to a variable, repeating the process, creating another node, and so on. Now I need to figure out how to define the node position inside a specially reserved space. Obviously, after creating nodes, through another button, the user creates the arcs (is created by a line connecting two nodes) associated with the nodes, thus defining a graph.
My problem is that I do not understand how to indicate the position of lines that will act as arcs in my graph.
Please help me.I'm not very experienced and I'm trying to tackle this problem.
First of all you need to tell us what is your "reserved space"? If it's a Canvas then you can draw shapes with Canva's GraphicsContext.
Canvas canvas = new Canvas(300, 250);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillOval(10, 60, 30, 30);
gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN);
Otherwise if you are working inside a pane with layout you need to know if the components are managed or not. For example inside a Pane or AnchorPane the node's automatic layout is disabled, thus you need to specify their layoutX and layoutY by yourself (+ node dimensions) like :
node.setLayoutX(12);
node.setLayoutY(222);
node.setPrefWidth(500);
node.setPrefHeight(500);
If you are using a pane like VBox which manage the layout of it's nodes you need to set the nodes inside the pane to be unmanaged in order for you to apply specific transformations. You can do that just by setting :
node.setManaged(false)
I will not recommend you to use Canvas cause handling the shapes will be very hard, for example if you need to remove something you probably have to clear everything and redraw only the visible shapes.
Well I had some time so here is a small example ( It might not be the optimal solution, but you can take is as a reference)
GraphTest
import java.util.ArrayList;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GraphTest extends Application {
private double orgSceneX, orgSceneY;
private double orgTranslateX, orgTranslateY;
private Group root = new Group();
#Override
public void start(Stage primaryStage) throws Exception {
GraphNode node1 = createNode("A", 100, 100, Color.RED);
GraphNode node2 = createNode("B", 300, 200, Color.GREEN);
GraphNode node3 = createNode("C", 80, 300, Color.PURPLE);
connectNodes(node1, node2, "C1");
connectNodes(node3, node1, "C2");
connectNodes(node3, node2, "C3");
root.getChildren().addAll(node1, node2, node3);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
private void connectNodes(GraphNode node1, GraphNode node2, String edgeText) {
Line edgeLine = new Line(node1.getCenterX(), node1.getCenterY(), node2.getCenterX(), node2.getCenterY());
Label edgeLabel = new Label(edgeText);
node1.addNeighbor(node2);
node2.addNeighbor(node1);
node1.addEdge(edgeLine, edgeLabel);
node2.addEdge(edgeLine, edgeLabel);
root.getChildren().addAll(edgeLine, edgeLabel);
}
private GraphNode createNode(String nodeName, double xPos, double yPos, Color color) {
GraphNode node = new GraphNode(nodeName, xPos, yPos, color);
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
return node;
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
GraphNode node = (GraphNode) t.getSource();
orgTranslateX = node.getTranslateX();
orgTranslateY = node.getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
GraphNode node = (GraphNode) t.getSource();
node.setTranslateX(newTranslateX);
node.setTranslateY(newTranslateY);
updateLocations(node);
}
};
private void updateLocations(GraphNode node) {
ArrayList<GraphNode> connectedNodes = node.getConnectedNodes();
ArrayList<Line> edgesList = node.getEdges();
for (int i = 0; i < connectedNodes.size(); i++) {
GraphNode neighbor = connectedNodes.get(i);
Line l = edgesList.get(i);
l.setStartX(node.getCenterX());
l.setStartY(node.getCenterY());
l.setEndX(neighbor.getCenterX());
l.setEndY(neighbor.getCenterY());
}
}
public static void main(String[] args) {
launch(args);
}
}
GraphNode
import java.util.ArrayList;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
public class GraphNode extends StackPane {
private Circle circle;
private Label text;
private ArrayList<GraphNode> connectedNodesList = new ArrayList<>();
private ArrayList<Line> edgesList = new ArrayList<>();
private ArrayList<Label> edgesLabelList = new ArrayList<>();
private double radius = 50.0;
public GraphNode(String name, double xPos, double yPos, Color color) {
circle = new Circle(radius, color);
text = new Label(name);
text.setTextFill(Color.WHITE);
setLayoutX(xPos);
setLayoutY(yPos);
getChildren().addAll(circle, text);
layout();
}
public void addNeighbor(GraphNode node) {
connectedNodesList.add(node);
}
public void addEdge(Line edgeLine, Label edgeLabel) {
edgesList.add(edgeLine);
edgesLabelList.add(edgeLabel);
// If user move the node we should translate the edge labels as well
// one way of doing that is by make a custom binding to the layoutXProperty as well
// as to layoutYProperty. We will listen for changes to the currentNode translate properties
// and for changes of our neighbor.
edgeLabel.layoutXProperty().bind(new DoubleBinding() {
{
bind(translateXProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateXProperty());
}
#Override
protected double computeValue() {
// We find the center of the line to translate the text
double width = edgeLine.getEndX() - edgeLine.getStartX();
return edgeLine.getStartX() + width / 2.0;
}
});
edgeLabel.layoutYProperty().bind(new DoubleBinding() {
{
bind(translateYProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateYProperty());
}
#Override
protected double computeValue() {
double width = edgeLine.getEndY() - edgeLine.getStartY();
return edgeLine.getStartY() + width / 2.0;
}
});
}
public ArrayList<GraphNode> getConnectedNodes() {
return connectedNodesList;
}
public ArrayList<Line> getEdges() {
return edgesList;
}
public double getX() {
return getLayoutX() + getTranslateX();
}
public double getY() {
return getLayoutY() + getTranslateY();
}
public double getCenterX() {
return getX() + radius;
}
public double getCenterY() {
return getY() + radius;
}
}
And the Output would look like that :
You can move the nodes with your mouse and you will see all the labels will follow the shapes locations ( Cicle , Lines )

Placing text in a circle in javafx

I am currently attempting to create a series of characters in a circle within an application of Java. Essentially, the phrase "Welcome to Java" will be placed in a circle, starting at the far right with W. As characters move down the circle, they are rotated in a fashion such that the bottoms of the letters are facing inwards, in order to form a circle.
My code seems to rotate the characters appropriately, but they will not appear anywhere other than the center of the pane. So my question is: How do you space the letters away from the center in a pane using javafx utilities? My code is below:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.geometry.Pos;
public class Characters extends Application{
#Override
public void start (Stage primaryStage){
//Create the pane
GridPane pane = new GridPane();
pane.setPrefSize(600, 600);
pane.setAlignment(Pos.CENTER);
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double x = Math.cos(rotation)*100;
double y = Math.sin(rotation)*100;
//Loop
for (int i = 0; i < welcome.length(); i++){
x = Math.cos(Math.toRadians(rotation));
y = Math.sin(Math.toRadians(rotation));
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i)+"");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
public static void main (String [] args){
launch(args);
}
}
This will get you a little bit closer to your desired result. There are several mistakes in there. You should not use a GridPane here because its layout will interfere with your text placement and your formula is wrong too.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Characters extends Application {
#Override
public void start(Stage primaryStage) {
// Create the pane
// GridPane pane = new GridPane();
Pane pane = new Pane();
pane.setPrefSize(600, 600);
// pane.setAlignment(Pos.CENTER);
// Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
// Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double offset = pane.getPrefWidth() / 2;
double radius = 100;
double x = offset + Math.cos(rotation) * radius;
double y = offset + Math.sin(rotation) * radius;
// Loop
for (int i = 0; i < welcome.length(); i++) {
x = offset + Math.cos(Math.toRadians(rotation)) * radius;
y = offset + Math.sin(Math.toRadians(rotation)) * radius;
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i) + "");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
// Create the scene for the application
// Scene scene = new Scene(pane, 500, 500);
Scene scene = new Scene(pane);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
// Display
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
GridPane will basically ignore your positioning and decide the position of it's children on it's own. In this case all Text elements are put in the center of the "cell" with column=0; row=0) of the GridPane.
I personally prefer putting the Text nodes in a Group and add the Group to the parent layout instead of placing it in a Parent with a complex layouting algorithm.
The rotation is slightly easier to achieve using a Rotate transform, since this allows you to specify the pivot point of the rotation:
#Override
public void start(Stage primaryStage) {
//Create the pane
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
Group textGroup = new Group();
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double radius = 100d;
//Loop
for (char c : welcome.toCharArray()) {
// ignore whitespace, otherwise add rotated char
if (!Character.isWhitespace(c)) {
Text text = new Text(Character.toString(c));
text.setFont(font);
Rotate rotationMatrix = new Rotate(rotation, 0, radius);
text.getTransforms().add(rotationMatrix);
textGroup.getChildren().add(text);
}
rotation += 22.5;
}
pane.getChildren().add(textGroup);
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
Result
You can do it with a PathTransition, but there is no kerning for the Text. So the characters won't look pretty well placed, also I had to insert some whitespace. Maybe some other one will make it a bit nicer.
But as a nice add-on, if you comment the line timer.start();, then the text is rotating around the circle.
import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BezierTextPlotter extends Application {
private static final String CIRCLE_TEXT = " Welcome to Java ";
#Override
public void start(final Stage stage) throws Exception {
final Text text = new Text(CIRCLE_TEXT);
text.setStyle("-fx-font-size: 40px");
Circle circle = new Circle(200, 200, 100);
final ObservableList<Text> parts = FXCollections.observableArrayList();
final ObservableList<PathTransition> transitions
= FXCollections.observableArrayList();
for (char character : text.textProperty().get().toCharArray()) {
Text c = new Text(character + "");
c.setEffect(text.getEffect());
c.setStyle(text.getStyle());
parts.add(c);
transitions.add(createPathTransition(circle, c));
}
AnchorPane ap = new AnchorPane();
ap.getChildren().addAll(parts);
for (int i = 0; i < parts.size(); i++) {
final Transition t = transitions.get(i);
t.stop();
t.jumpTo(Duration.seconds(10).multiply((i + 0.5) * 1.0 / parts.size()));
AnimationTimer timer = new AnimationTimer() {
int frameCounter = 0;
#Override
public void handle(long l) {
frameCounter++;
if (frameCounter == 1) {
t.stop();
stop();
}
}
};
timer.start();
t.play();
}
stage.setTitle("Circle Text Sample");
stage.setScene(new Scene(ap, 400, 400, Color.ALICEBLUE));
stage.show();
}
private PathTransition createPathTransition(Shape shape, Text text) {
final PathTransition trans
= new PathTransition(Duration.seconds(10), shape, text);
trans.setAutoReverse(false);
trans.setCycleCount(PathTransition.INDEFINITE);
trans.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
trans.setInterpolator(Interpolator.LINEAR);
return trans;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
And with the line timer.start() commented:

JavaFX removing more than one circle while creating bounding rectangle

I am making a javafx application that creates a bounded rectangle around the circles the user creates when primary mouse clicking. The user can also remove a circle with the secondary mouse button and the bounding rectangle should react accordingly and use the remaining circles as its upper and lower limits. The problem with my program is that it does not work when I try to remove more than one circle in a row (it only removes and resizes for the last circle)
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collections;
public class BoundRectangle extends Application {
double minX, maxX, minY, maxY;
Pane pane = new Pane();
Rectangle rectangle;
ArrayList<Circle> allCircles = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
VBox mainBox = new VBox();
Pane mainPane = new Pane(mainBox);
mainPane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().addAll(mainPane);
mainBox.setLayoutX(10);
mainBox.setLayoutY(10);
pane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
Circle circle = new Circle(e.getX(), e.getY(), 10);
allCircles.add(circle);
pane.getChildren().add(drawRectangle());
pane.getChildren().add(circle);
System.out.println("maxX " + maxX);
System.out.println("maxY " + maxY);
System.out.println("minX " + minX);
System.out.println("minY " + minY);
circle.setOnMouseClicked(evt -> {
if (evt.getButton() == MouseButton.SECONDARY) {
pane.getChildren().remove(circle);
allCircles.remove(circle);
pane.getChildren().add(drawRectangle());
}
});
circle.setStroke(Color.BURLYWOOD);
circle.setStrokeWidth(3);
circle.setFill(Color.TRANSPARENT);
}
});
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.setTitle("click circles, make rectangle");
primaryStage.show();
}
public Rectangle drawRectangle() {
refresh();
getMinMax();
if (pane.getChildren().size() == 1)
{
Rectangle rect0 = new Rectangle(0, 0, 0, 0);
return rect0;
}
Rectangle boundingRect = new Rectangle();
boundingRect.setX(minX - 10 - 2);
boundingRect.setY(minY - 10 - 2);
boundingRect.setWidth(maxX - minX + 2.0 * 10 + 2);
boundingRect.setHeight(maxY - minY + 2.0 * 10 + 2);
boundingRect.setStroke(Color.BLACK);
boundingRect.setStrokeWidth(2);
boundingRect.setFill(Color.TRANSPARENT);
return boundingRect;
}
public void getMinMax() {
maxY = allCircles.get(0).getCenterY();
minY = allCircles.get(0).getCenterY();
maxX = allCircles.get(0).getCenterX();
minX = allCircles.get(0).getCenterX();
for (Circle c : allCircles) {
if (c.getCenterX() < minX)
minX = c.getCenterX();
if (c.getCenterX() > maxX)
maxX = c.getCenterX();
if (c.getCenterY() < minY)
minY = c.getCenterY();
if (c.getCenterY() > maxY)
maxY = c.getCenterY();
}
}
private void refresh() {
ObservableList<Node> list = pane.getChildren();
for (Node c : list) {
if (c instanceof Rectangle) {
pane.getChildren().remove(c);
break;
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
The last circle you add appears on top of the rectangle; however the rectangle appears on top of all the other circles. So only the last circle added will get the mouse clicks (clicks on other circles are targeted to the rectangle).
The quickest fix is to make the rectangle mouse transparent:
boundingRect.setMouseTransparent(true);
A better solution overall might be just to create one rectangle and update its x, y, width and height properties. That way you can just ensure the rectangle is added once and remains at the bottom of the stack.

Categories