I'm having problems with a javafx exercise I'm doing. Basically the problem states to make a rectangular cube and make it so that it automatically expands or contracts as you adjust the window size. I'm done with the first part of the part of the program but having difficulty with the second. Here's my code:
public void start(Stage primaryStage) {
Pane pane = new Pane();
//Draw two rectangles
Rectangle upper = new Rectangle(140, 100, 120, 100);
upper.setFill(null);
upper.setStroke(Color.BLACK);
Rectangle lower = new Rectangle(100, 140, 120, 100);
lower.setFill(null);
lower.setStroke(Color.BLACK);
//Draw the line connecting them
Line ul = new Line(140, 100, 100, 140);
Line ur = new Line(260, 100, 220, 140);
Line ll = new Line(140, 200, 100, 240);
Line lr = new Line(260, 200, 220, 240);
pane.getChildren().addAll(upper, lower, ul, ur, ll, lr);
Scene scene = new Scene(pane, 200, 200);
primaryStage.setTitle("Exercise14");
primaryStage.setScene(scene);
primaryStage.show();
}
The rectangular cube appears as thus, but I can't figure out how to make it expand or contract when I resize the window:
I'm thinking I need to bind each individual shape to the pane or something, but I'm not sure where to begin. Would appreciate a point in the right direction. Thanks.
You need to add change listeners to the scene and calculate the dimensions of the cube relative to the width and height of the scene. Something like this:
ChangeListener<Number> listenerX = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println( "X: observable: " + observable + ", oldValue: " + oldValue.doubleValue() + ", newValue: " + newValue.doubleValue());
// TODO:
// Lines:
// line.setStartX(...)
// line.setStartY(...)
// line.setEndX(...)
// line.setEndY(...)
//
// Rectangles:
// rect.setTranslateX(...)
// rect.setTranslateY(...)
// rect.setWidth(...)
// rect.setHeight(...)
// hint: instead of translate you can use rect.relocate(..., ...). translate is relative, relocate applies layoutX/Y and translateX/Y
}
};
ChangeListener<Number> listenerY = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println( "Y: observable: " + observable + ", oldValue: " + oldValue.doubleValue() + ", newValue: " + newValue.doubleValue());
// similare to listenerX
}
};
scene.widthProperty().addListener( listenerX);
scene.heightProperty().addListener( listenerY);
I would recommend the approach explained over here: http://fxexperience.com/2014/05/resizable-grid-using-canvas/
Basically you create your custom Pane and override the 'layoutChildren()' method to draw your cube. You "just" have to replace the drawing code with your cube.
This is much faster than using the listener approach: When the node is resized it will draw twice at a minimum when using listeners (when width and height are set). 'layoutChildren()' will at most do one draw per frame.
Related
I am trying to build a series chart using JavaFX, where data is inserted dynamically.
Each time that a new value is inserted I would like to check if this is the highest value so far, and if so, I want to draw an horizontal line to show that this is the maximum value.
In JFree chart I would have used a ValueMarker, but I am trying to do the same with JavaFX.
I tried using the Line object, but it is definitely not the same, because I cannot provide the Chart values, it takes the relative pixel positions in the windows.
Here is the screenshot of chart I want to achieve:
http://postimg.org/image/s5fkupsuz/
Any suggestions?
Thank you.
To convert chart values to pixels you can use method NumberAxis#getDisplayPosition() which return actual coordinates of the chart nodes.
Although these coordinates are relative to chart area, which you can find out by next code:
Node chartArea = chart.lookup(".chart-plot-background");
Bounds chartAreaBounds = chartArea.localToScene(chartArea.getBoundsInLocal());
Note localToScene() method which allows you to convert any coordinates to Scene ones. Thus you can use them to update your value marker coordinates. Make sure you make localToScene call after your Scene have been shown.
See sample program below which produces next chart:
public class LineChartValueMarker extends Application {
private Line valueMarker = new Line();
private XYChart.Series<Number, Number> series = new XYChart.Series<>();
private NumberAxis yAxis;
private double yShift;
private void updateMarker() {
// find maximal y value
double max = 0;
for (Data<Number, Number> value : series.getData()) {
double y = value.getYValue().doubleValue();
if (y > max) {
max = y;
}
}
// find pixel position of that value
double displayPosition = yAxis.getDisplayPosition(max);
// update marker
valueMarker.setStartY(yShift + displayPosition);
valueMarker.setEndY(yShift + displayPosition);
}
#Override
public void start(Stage stage) {
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(0, 100, 10), yAxis = new NumberAxis(0, 100, 10));
series.getData().add(new XYChart.Data(0, 0));
series.getData().add(new XYChart.Data(10, 20));
chart.getData().addAll(series);
Pane pane = new Pane();
pane.getChildren().addAll(chart, valueMarker);
Scene scene = new Scene(pane);
// add new value on mouseclick for testing
chart.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
series.getData().add(new XYChart.Data(series.getData().size() * 10, 30 + 50 * new Random().nextDouble()));
updateMarker();
}
});
stage.setScene(scene);
stage.show();
// find chart area Node
Node chartArea = chart.lookup(".chart-plot-background");
Bounds chartAreaBounds = chartArea.localToScene(chartArea.getBoundsInLocal());
// remember scene position of chart area
yShift = chartAreaBounds.getMinY();
// set x parameters of the valueMarker to chart area bounds
valueMarker.setStartX(chartAreaBounds.getMinX());
valueMarker.setEndX(chartAreaBounds.getMaxX());
updateMarker();
}
public static void main(String[] args) {
launch();
}
}
I am making a simple simulation, and have had a good amount of trouble finding the X and Y coordinates of a rotated, weirdly sized, imageView node. (The blue bit is the front)
The goal is to find out an XY coordinate relative to the direction that the imageView is pointing, after it has been rotated. I can find the angle that the imageView is at relative to its starting position but I cannot figure out how to get an XY coordinate of the imageView relative to this angle. Since the .setRotate(angle) method does not change the X and Y location of the imageView, how should I go about finding a point that the imageView is facing?
Minimal example of the rotation and imageView I am using:
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Image robotImage = null;
try {
robotImage = new Image(new FileInputStream("res\\robot.png"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ImageView robot = new ImageView(robotImage);
robot.setLayoutX(125);
robot.setLayoutY(125);
System.out.println("PreRotate X: " + robot.getLayoutX() + "PreRotate Y: " + robot.getLayoutY());
robot.setRotate(45);
System.out.println("PostRotate X: " + robot.getLayoutX() + "PostRotate Y: " + robot.getLayoutY());
root.getChildren().add(robot);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have already tried using the bounds of the imageView along with lines that lay on top of the imageView, but that requires me to find the new max/min x/y every time that the imageView changes its max/min x/y.
For example:
if (turnAngle < 35) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMinY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMinY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
else if (turnAngle < 55) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMaxY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMaxY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
And so on all the way to 360. DRY yikes.
How should I approach this? Am I using the wrong transformation? Did I not see a method that can be used for this? I know that there must be a better approach. Thanks for reading.
I'm not sure I 100% understand the question. The transformations between coordinate systems but it's hard to tell the coordinate systems you need to convert between from your description, so I assume you want to convert between the coordinate system of robot to the coordinate system of group.
It's possible to use localToParent to convert from the coordinate system of a node to that of the parent which accomodates for all transforms. (parentToLocal would achieve the inverse transformation, but this does not seem to be the required transformation in this case.)
The following example modifies the start and end points of a line to the coordinates of the top left and a point 100 px above of the Rectangle in the Rectangle's coordinate system:
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Rectangle robot = new Rectangle(100, 20, Color.RED);
robot.setLayoutX(125);
robot.setLayoutY(125);
Line line = new Line(125, 125, 125, 25);
robot.rotateProperty().addListener(o -> {
Point2D start = robot.localToParent(0, 0);
Point2D end = robot.localToParent(0, -100);
line.setStartX(start.getX());
line.setStartY(start.getY());
line.setEndX(end.getX());
line.setEndY(end.getY());
});
RotateTransition rotateTransition = new RotateTransition(Duration.seconds(5), robot);
rotateTransition.setCycleCount(Animation.INDEFINITE);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(360);
rotateTransition.setInterpolator(Interpolator.LINEAR);
rotateTransition.play();
root.getChildren().addAll(robot, line);
primaryStage.show();
}
I have a scroll pane in java with content as an anchorpane , the anchorpane has children which are an imageview binded with the anchorpanes height and width so it can fit the anchorpane exactly and also some circles and lines representing a graph. If i enlarge the height or width of the anchorpane then the scroll pane t becomes automatically scrollable giving me the ability to view the image as if it was zoomed , but what im trying to do is zoom in on a specified area on the pic and also , to make the circles and lines change position based on the zoom (maybe using binding properties). The whole idea im trying to implement is have a picture as a map and a graph representing links between cities , but at the same time i was to zoom in on a certain path and have it take the whole pane and navigating between the scroll pane after zooming in.
AnchorPane other;
ImageView image;
ScrollPane scroll;
Button bp;
Button bm;
public void initialize(URL arg0, ResourceBundle arg1) {
System.out.println("OK");
Circle c = new Circle(250, 250, 10);
Circle c2 = new Circle(20, 20, 10);
Circle c3 = new Circle(40, 200, 10);
c3.setFill(Color.RED);
c3.setVisible(true);
c2.setFill(Color.RED);
Line line = new Line();
Line line2 = new Line();
Line line3 = new Line();
line.setStartX(c.getCenterX());
line.setStartY(c.getCenterY());
line.setEndX(c2.getCenterX());
line.setEndY(c2.getCenterY());
line2.setStartX(c.getCenterX());
line2.setStartY(c.getCenterY());
line2.setEndX(c3.getCenterX());
line2.setEndY(c3.getCenterY());
line2.setVisible(true);
line2.setStroke(Color.BLUE);
line3.setStartX(c2.getCenterX());
line3.setStartY(c2.getCenterY());
line3.setEndX(c3.getCenterX());
line3.setEndY(c3.getCenterY());
line3.setVisible(true);
line3.setStroke(Color.BLUE);
line.setVisible(true);
line.setStroke(Color.BLUE);
c.setFill(Color.RED);
c2.setVisible(true);
c.setVisible(true);
Rectangle rect = new Rectangle(100, 100, 70, 70);
other.getChildren().addAll(line, line2, line3, c, c2, c3, rect);
System.out.println(other.getChildren());
image.setVisible(true);
image.fitHeightProperty().bind(other.prefHeightProperty());
image.fitWidthProperty().bind(other.prefWidthProperty());
scroll.setPannable(true);
}
public void bpAction() {
other.setPrefHeight(other.getPrefHeight() + 30);
other.setPrefWidth(other.getPrefWidth() + 30);
}
public void bmAction() {
other.setPrefHeight(other.getPrefHeight() - 30);
other.setPrefWidth(other.getPrefWidth() - 30);
}
So I want to put a dot on a circle. I don't want it centered I want it for example in the right corner of the circle but I want it to be a part of the circle not just another circle placed on top of this one. The reason I want it this way is to show the Rotation Transition of the circle more clearly. How do make this happen?
You could put the Circles in a Group and rotate that group instead of the Circle:
public void start(Stage primaryStage) {
Circle circle = new Circle(100);
Circle dot = new Circle(20, 30, 10, Color.RED);
Group group = new Group(circle, dot);
group.setLayoutX(100);
group.setLayoutY(200);
Pane root = new Pane(group);
Scene scene = new Scene(root, 500, 500);
RotateTransition transition = new RotateTransition(Duration.seconds(1), group);
transition.setByAngle(360);
transition.setInterpolator(Interpolator.LINEAR);
transition.setCycleCount(Animation.INDEFINITE);
transition.play();
primaryStage.setScene(scene);
primaryStage.show();
}
I'm working on a interactive sort application. I must represent numbers as rectangles, and for example, when the sorting algorithm is running, when two numbers are swapped, the rectangles must be swapped. I want to do this with animations. How can I swap the rectangles? I currently testing this using transition but I have some problems. I have two rectangles in a group. When I try to swap the rectangles, both will meet in the middle and stop. Here's the code:
Rectangle r1 = rectangles.get(numbers[0]);
Rectangle r2 = rectangles.get(numbers[1]);
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setNode(r1);
translateTransition.setDuration(Duration.millis(1000));
translateTransition.setFromX(r1.getX());
translateTransition.setToX(r2.getX());
TranslateTransition translateTransition2 = new TranslateTransition();
translateTransition2.setNode(r2);
translateTransition2.setDuration(Duration.millis(1000));
translateTransition2.setFromX(r2.getX());
translateTransition2.setToX(r1.getX());
translateTransition2.play();
translateTransition.play();
I need a pane similar with canvas. I need to be able to set the rectangles coordinates.
TranslateTransition works with translateX property of the Node. Thus, if you positioned rectangles using setLayout, relocate or just constructor parameter TranslateTransition wouldn't work for you.
You either need to start using translateX coordinates or use Timeline instead of TranslateTransition.
You can read more about layout and translate in JavaDoc for layout
Here is translateX based swap example:
public void start(Stage primaryStage) {
final Rectangle r1 = new Rectangle(50, 50, Color.RED);
final Rectangle r2 = new Rectangle(50, 50, Color.BLUE);
// note I use translate to position rectangles
r1.setTranslateX(50);
r2.setTranslateX(250);
Button btn = new Button();
btn.setText("Move it");
btn.relocate(100, 100);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
double x1 = r1.getTranslateX();
double x2 = r2.getTranslateX();
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setNode(r1);
translateTransition.setDuration(Duration.millis(1000));
translateTransition.setToX(x2);
TranslateTransition translateTransition2 = new TranslateTransition();
translateTransition2.setNode(r2);
translateTransition2.setDuration(Duration.millis(1000));
translateTransition2.setToX(x1);
translateTransition2.play();
translateTransition.play();
}
});
Pane root = new Pane();
root.getChildren().addAll(btn, r1, r2);
Scene scene = new Scene(root, 400, 350);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}