Lay nodes out in circle - java

I am trying to layout my nodes like this:
Here is my current layout, called CircularPane:
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
public class CircularPane extends Pane {
#Override
protected void layoutChildren() {
final int radius = 50;
final double increment = 360 / getChildren().size();
double degreese = 0;
for (Node node : getChildren()) {
double x = radius * Math.cos(Math.toRadians(degreese)) + getWidth() / 2;
double y = radius * Math.sin(Math.toRadians(degreese)) + getHeight() / 2;
layoutInArea(node, x - node.getBoundsInLocal().getWidth() / 2, y - node.getBoundsInLocal().getHeight() / 2, getWidth(), getHeight(), 0.0, HPos.LEFT, VPos.TOP);
degreese += increment;
}
}
}
Here is my main class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
CircularPane pane = new CircularPane();
for(int i = 0; i < 6; i++) {
Button button = new Button("" + i);
pane.getChildren().add(button);
}
stage.setScene(new Scene(pane));
stage.show();
}
}
And here is my current display:
The nodes are not at the bottom touching, they are equally spread out around the circle. I want to make it so they go to the bottom, but can't figure out how to.

Your approach to layout the buttons over a circle is correct, but in this line you are defining how they will be layouted:
final double increment = 360 / getChildren().size();
This gives the same angle between any two buttons refered from the center of the circle! And that's why you get your current display.
If you want to layout the nodes like in your figure, if I get it right, these are the conditions:
Every node has its center over the circle
The nodes are equally separated in horizontal: the horizontal gap goes from 0 to some value.
The initial gap from the circle to the first node goes from 0 to some value.
The size of each node may be adjusted to fulfill the previous conditions
So let's define some fields for those values, and adjust the size of the pane:
class CircularPane extends Pane {
private final double radius;
private final double ext_gap;
private final double int_gap;
public CircularPane(double radius, double ext_gap, double int_gap){
this.radius=radius;
this.ext_gap=ext_gap;
this.int_gap=int_gap;
setMinSize(2*radius, 2d*radius);
setPrefSize(2*radius, 2d*radius);
setMaxSize(2*radius, 2d*radius);
}
}
And now, given any n buttons, the above conditions can be turned into one single equation that solves the size of the node. If the total available length (2*radius) minus two exterior gaps (2*ext_gap) is the same as n buttons of size buttonSize and n-1 interior gaps (int_size), then, the size of every button has to be:
#Override
protected void layoutChildren() {
int n=getChildren().size();
double buttonSize = (2*radius-2*ext_gap-(n-1)*int_gap)/n;
}
Finally, now you can set the size of the button and layout every node, just by increasing the x coordinate (by the size of the button plus an inner gap), and then getting the y coordinate from the circle equation:
#Override
protected void layoutChildren() {
int n=getChildren().size();
double buttonSize = (2*radius-2*ext_gap-(n-1)*int_gap)/n;
double x=ext_gap+buttonSize/2d, y;
for (Node node : getChildren()) {
((Button)node).setMinSize(buttonSize, buttonSize);
((Button)node).setPrefSize(buttonSize, buttonSize);
((Button)node).setMaxSize(buttonSize, buttonSize);
node.setStyle("-fx-font-size: "+Math.round(buttonSize/3));
node.setManaged(false);
y=getHeight()/2d+Math.sqrt(radius*radius-Math.pow(x-radius,2d));
layoutInArea(node, x-buttonSize/2d, y-buttonSize/2d, getWidth(), getHeight(), 0.0, HPos.LEFT, VPos.TOP);
x+=buttonSize+int_gap;
}
}
Note that you can also change the size of the font, to get a visible number for any size of the button.
Note also that node.setManaged(false); avoids the calls to layoutChildren() when you click the buttons (due to changes in the size of the clicked button when being focused or clicked).
Finally this will create the circular pane and draw a circle:
#Override
public void start(Stage primaryStage) {
CircularPane pane = new CircularPane(200,20,10);
for(int i = 0; i < 6; i++) {
Button button = new Button("" + (i+1));
pane.getChildren().add(button);
}
Circle circle = new Circle(200);
circle.setFill(null);
circle.setStroke(Color.BLACK);
StackPane stack=new StackPane(circle,pane);
Scene scene = new Scene(stack, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
With this result:

Related

How to lower the number of calling of drawFrame() in java?

I've been learning Java from a book called "Introduction to Java by David Eck". In that book, there is a Graphic with GUI chapter where the Rectangles are moved to the center. The book was explaining the stuff that was going on, but most importantly it says that "the drawFrame() subroutine will automatically be called about 60 times per second.". So, how can we lower the number of times the drawFrame() is called. The reason is I want to see the rectangles moving in slow motion.
This is the complete code:
package com.example.ch3movingrects;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.layout.BorderPane;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
/**
* When run as a program, this class opens a window on the screen that
* shows a set of nested rectangles that seems to be moving infinitely
* inward towards the center. The animation continues until the user
* closes the window.
*/
public class ch3MovingRects extends Application {
/**
* Draws a set of nested rectangles. This subroutine is called 60 times per
* second and is responsible for redrawing the entire drawing area. The
* parameter g is used for drawing. The frameNumber starts at zero and
* increases by 1 each time this subroutine is called. The parameters width
* and height give the size of the drawing area, in pixels.
* The sizes and positions of the rectangles that are drawn depend
* on the frame number, giving the illusion of motion.
*/
public void drawFrame(GraphicsContext g, int frameNumber, double elapsedSeconds, int width, int height) {
g.setFill(Color.WHITE);
g.fillRect(0,0,width,height); // Fill drawing area with white.
double inset; // Gap between edges of drawing area and the outer rectangle.
double rectWidth, rectHeight; // The size of one of the rectangles.
g.setStroke(Color.BLACK); // Draw the rectangle outlines in black.
inset = frameNumber % 15 + 0.5; // The "+ 0.5" is a technicality to produce a sharper image.
rectWidth = width - 2*inset;
rectHeight = height - 2*inset;
while (rectWidth >= 0 && rectHeight >= 0) {
g.strokeRect(inset, inset, rectWidth, rectHeight);
inset += 15; // rectangles are 15 pixels apart
rectWidth -= 30;
rectHeight -= 30;
}
}
//------ Implementation details: DO NOT EXPECT TO UNDERSTAND THIS ------
public void start(Stage stage) {
int width = 800; // The width of the image. You can modify this value!
int height = 600; // The height of the image. You can modify this value!
Canvas canvas = new Canvas(width,height);
drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height);
BorderPane root = new BorderPane(canvas);
root.setStyle("-fx-border-width: 4px; -fx-border-color: #444");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Infinte Moving Rects"); // STRING APPEARS IN WINDOW TITLEBAR!
stage.show();
stage.setResizable(false);
AnimationTimer anim = new AnimationTimer() {
private int frameNum;
private long startTime = -1;
private long previousTime;
public void handle(long now) {
if (startTime < 0) {
startTime = previousTime = now;
drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height);
}
else if (now - previousTime > 0.95e9/60) {
// The test in the else-if is to make sure that drawFrame() is
// called about once every 1/60 second. It is required since
// handle() can be called by the system more often than that.
frameNum++;
drawFrame(canvas.getGraphicsContext2D(), frameNum, (now-startTime)/1e9, width, height);
previousTime = now;
}
}
};
anim.start();
}
public static void main(String[] args) {
launch();
}
} // end MovingRects
Any ideas on how can we lower the number of calls to drawFrame subroutine?

JavaFX: How to resize a rectangle with set aspect ratio

I have an app where user can draw a rectangle, and resize it or move it. I'm interested if is possible to some how bind rectangles width and height in some aspect ratio.
E.g. if aspect ratio is 1:2 that user can draw only that kinds of rectangles, or if is 1:1 that user can only draw square.
EDIT
My eventHandler for MOUSE_DRAGGEDevent looks something like this
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if (event.isSecondaryButtonDown())
return;
double offsetX = event.getX() - rectangleStartX;
double offsetY = event.getY() - rectangleStartY;
if (offsetX > 0) {
if (event.getX() > imageView.getFitWidth()) {
selectionRectangle.setWidth(imageView.getFitWidth() - rectangleStartX);
} else
selectionRectangle.setWidth(offsetX);
} else {
if (event.getX() < 0)
selectionRectangle.setX(0);
else
selectionRectangle.setX(event.getX());
selectionRectangle.setWidth(rectangleStartX - selectionRectangle.getX());
}
if (offsetY > 0) {
if (event.getY() > imageView.getFitHeight())
selectionRectangle.setHeight(imageView.getFitHeight() - rectangleStartY);
else
selectionRectangle.setHeight(offsetY);
} else {
if (event.getY() < 0)
selectionRectangle.setY(0);
else
selectionRectangle.setY(event.getY());
selectionRectangle.setHeight(rectangleStartY - selectionRectangle.getY());
}
};
This app demos how to adjust a Rectangle height and width based on a ratio. Comments in code.
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication122 extends Application
{
double orgSceneX, orgSceneY;//Used to help keep up with change in mouse position
#Override
public void start(Stage primaryStage)
{
double RATIO = .5;//The ration of height to width is 1/2
Rectangle rectangle = new Rectangle(100, 50);
rectangle.setX(400 - 50);
rectangle.setY(250 - 25);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setStroke(Color.BLACK);
//Circles will be used to do the event handling/movements
Circle leftAnchor = new Circle(400 - 50, 250, 5);
Circle topAnchor = new Circle(400, 250 - 25, 5);
leftAnchor.setOnMouseDragEntered((event) -> {
((Circle) event.getSource()).getScene().setCursor(Cursor.MOVE);
});
leftAnchor.setOnMousePressed((event) -> {
orgSceneX = event.getSceneX();//Store current mouse position
});
leftAnchor.setOnMouseDragged((event) -> {
double offSetX = event.getSceneX() - orgSceneX;//Find change in mouse X position
leftAnchor.setCenterX(event.getSceneX());
rectangle.setX(event.getSceneX());//move rectangle left side with mouse
rectangle.setWidth(rectangle.getWidth() - offSetX);//Change rectangle's width with movement of mouse
topAnchor.setCenterX(topAnchor.getCenterX() + offSetX / 2);//Adjust top circle as rectangle's size change
rectangle.setHeight(rectangle.getWidth() * RATIO);//Change the height so that it meets the ratio requirements
leftAnchor.setCenterY((rectangle.getY() + rectangle.getHeight()) - (rectangle.getHeight() / 2));//Adjust the left circle with the growth of the rectangle
orgSceneX = event.getSceneX();//save last mouse position to recalculate change in mouse postion as the circle moves
});
leftAnchor.setOnMouseExited((event) -> {
leftAnchor.getScene().setCursor(null);
});
topAnchor.setOnMouseDragEntered((event) -> {
topAnchor.getScene().setCursor(Cursor.MOVE);
});
topAnchor.setOnMousePressed((event) -> {
orgSceneY = event.getSceneY();//store current mouse position
});
topAnchor.setOnMouseDragged((event) -> {
double offSetY = event.getSceneY() - orgSceneY;
topAnchor.setCenterY(event.getSceneY());
rectangle.setY(event.getSceneY());//move rectangle top side with mouse
rectangle.setHeight(rectangle.getHeight() - offSetY);//Change rectangle's height with movement of mouse
leftAnchor.setCenterY(leftAnchor.getCenterY() + offSetY / 2);//Adjust left circle as rectangle's size change
rectangle.setWidth(rectangle.getHeight() * (1 / RATIO));//Change the width so that it meets the ratio requirements
topAnchor.setCenterX((rectangle.getX() + rectangle.getWidth()) - (rectangle.getWidth() / 2));//Adjust the top circle with the growth of the rectangle
orgSceneY = event.getSceneY();//save last mouse position to recalculate change in mouse postion as the circle moves
});
topAnchor.setOnMouseExited((event) -> {
topAnchor.getScene().setCursor(null);
});
Pane root = new Pane();
root.getChildren().addAll(rectangle, leftAnchor, topAnchor);
Scene scene = new Scene(root, 800, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

3D Scatter Chart JavaFX: How to show legend and measures near axis

Looking this post, I've tried to implement in javaFX, with many difficulties, a Scatter Chart 3D where the grid is my x,y and z axis and the spheres are my points.
How Can I put a legend, axis labels and the range numbers along the axis? I can use only javaFX without external library.
I'm desperate.. I'm trying for days..without results
Please:help me
Thanks.
Code
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class GraphingData extends Application {
private static Random rnd = new Random();
// size of graph
int graphSize = 400;
// variables for mouse interaction
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(150, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(120, Rotate.Y_AXIS);
#Override
public void start(Stage primaryStage) {
// create axis walls
Group grid = createGrid(graphSize);
// initial cube rotation
grid.getTransforms().addAll(rotateX, rotateY);
// add objects to scene
StackPane root = new StackPane();
root.getChildren().add(grid);
root.setStyle( "-fx-border-color: red;");
// create bars
double gridSizeHalf = graphSize / 2;
double size = 30;
//Drawing a Sphere
Sphere sphere = new Sphere();
//Setting the properties of the Sphere
sphere.setRadius(10.0);
sphere.setTranslateX(-50);
sphere.setTranslateY(-50);
//Preparing the phong material of type specular color
PhongMaterial material6 = new PhongMaterial();
//setting the specular color map to the material
material6.setDiffuseColor(Color.GREEN);
sphere.setMaterial(material6);
grid.getChildren().addAll(sphere);
// scene
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
makeZoomable(root);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Axis wall
*/
public static class Axis extends Pane {
Rectangle wall;
public Axis(double size) {
// wall
// first the wall, then the lines => overlapping of lines over walls
// works
wall = new Rectangle(size, size);
getChildren().add(wall);
// grid
double zTranslate = 0;
double lineWidth = 1.0;
Color gridColor = Color.RED;
for (int y = 0; y <= size; y += size / 10) {
Line line = new Line(0, 0, size, 0);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateY(y);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
for (int x = 0; x <= size; x += size / 10) {
Line line = new Line(0, 0, 0, size);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateX(x);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
}
public void setFill(Paint paint) {
wall.setFill(paint);
}
}
public void makeZoomable(StackPane control) {
final double MAX_SCALE = 20.0;
final double MIN_SCALE = 0.1;
control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = control.getScaleX();
if (event.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
scale = clamp(scale, MIN_SCALE, MAX_SCALE);
control.setScaleX(scale);
control.setScaleY(scale);
event.consume();
}
});
}
/**
* Create axis walls
*
* #param size
* #return
*/
private Group createGrid(int size) {
Group cube = new Group();
// size of the cube
Color color = Color.LIGHTGRAY;
List<Axis> cubeFaces = new ArrayList<>();
Axis r;
// back face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(0.5 * size);
cubeFaces.add(r);
// bottom face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(0);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// right face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
r.setTranslateX(-1 * size);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// left face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
r.setTranslateX(0);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// top face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-1 * size);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// front face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(-0.5 * size);
// cubeFaces.add( r);
cube.getChildren().addAll(cubeFaces);
return cube;
}
public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
public static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
public static Color randomColor() {
return Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
}
public static void main(String[] args) {
launch(args);
}
}
Here's a basic idea to create some measures on the axes. It is not production-ready but should give you enough to start with.
private Group createGrid(int size) {
// existing code omitted...
cube.getChildren().addAll(cubeFaces);
double gridSizeHalf = size / 2;
double labelOffset = 30 ;
double labelPos = gridSizeHalf - labelOffset ;
for (double coord = -gridSizeHalf ; coord < gridSizeHalf ; coord+=50) {
Text xLabel = new Text(coord, labelPos, String.format("%.0f", coord));
xLabel.setTranslateZ(labelPos);
xLabel.setScaleX(-1);
Text yLabel = new Text(labelPos, coord, String.format("%.0f", coord));
yLabel.setTranslateZ(labelPos);
yLabel.setScaleX(-1);
Text zLabel = new Text(labelPos, labelPos, String.format("%.0f", coord));
zLabel.setTranslateZ(coord);
cube.getChildren().addAll(xLabel, yLabel, zLabel);
zLabel.setScaleX(-1);
}
return cube;
}
I would just place a legend outside the graph, which would just be a 2D grid pane not rotating...
I know this question is getting old but 2D labels in a JavaFX 3D scene is a topic that comes up a lot and I never see it answered "the right way".
Translating the labels like in James_D's answer will translate into 3D space a 2D label which will look correct until you move the camera. Assuming you want a scatter chart that doesn't move or rotate then this will be fine. Other wise you will need to automatically transform the 2D labels whenever you move your camera. (ie... the mouse handler). You could remove your scatter chart and readd the whole thing to the scene each time but that will be murder on your heap memory and won't be feasible for data sets of any real useful size.
The right way to do it is to use OpenGL or DirectDraw text renders which redraw the labels on each render loop pass but JavaFX 3D doesn't give you access (currently). So the "right way in JavaFX" is to float 2D labels on top of a 3D subscene and then translate them whenever the camera moves. This requires that you transform the 3D coordinate projection of the 3D location you want the label to a 2D screen projection.
To generically manage 2D labels connected to a Point3D in JavaFX 3D you need to do a transform along the following:
Point3D coordinates = node.localToScene(javafx.geometry.Point3D.ZERO);
SubScene oldSubScene = NodeHelper.getSubScene(node);
coordinates = SceneUtils.subSceneToScene(oldSubScene, coordinates);
double x = coordinates.getX();
double y = coordinates.getY();
label.getTransforms().setAll(new Translate(x, y));
Where the node is some actual 3D object already in the 3D subscene. For my applications I simply use a Sphere of an extremely small size it cannot be seen. If you were to follow James_D's example, you could translate the sphere(s) to the same locations that you translated the original axis labels.
The label is a standard JavaFX 2D label that you add to your scene... typically through a StackPane such that the labels are floating on top of the 3D subscene.
Now whenever the camera moves/rotates, this causes this transform to be called which slides the label on the 2D layer. Without direct access to the underlying GL or DD calls this is pretty much the only way to do something like this in JavaFX 3D but it works pretty well.
Here is a video example of it working.
Here is an open source example of implementing a simple version of floating 2D labels. (Warning, I'm the contributing author for the sample, not trying to promote the library.)

Proper way to move a JavaFx8 node around

I am working on an app which needs to move nodes around on a pane.
I've just recently started learning javaFx 8 so I have bare understanding of the workflow, but while I managed to get a working draggable node code working, it uses node.setTranslateX() and .setTranslateY().
This is all fine, until I tried to make the node snap to a grid that divides the scene area in both 10 height and width subdivisions.
Whenever I try to use modulos to get some sort of snapping going on, I'm stuck with the fact that I'm calling Translation transformation. I'd like to directly set the coordinates of the node myself. I've tried node.relocate(x,y) to no avail. As for node.setX() or node.setY(). These do no seem to do much.
So basically, I have two questions:
Is there a way to directly set the coordinates of a node ? If yes, how ?
What is the correct way to snap a node to a grid while dragging it with the mouse ?
My current code uses these methods to drag the node around:
public class Boat extends ImageView {
private int size ;
private String name ;
private Double dragDeltaX, dragDeltaY;
//Constructor etc here//
this.setOnMousePressed(event -> {
this.setCursor(Cursor.CLOSED_HAND);
dragDeltaX = this.getLayoutX() + event.getX();
dragDeltaY = this.getLayoutY() + event.getY();
});
this.setOnMouseReleased(event -> {
this.setCursor(Cursor.OPEN_HAND);
this.relocate(event.getSceneX(), event.getSceneY());
});
this.setOnMouseDragged(event -> {
this.setTranslateX(event.getSceneX() - dragDeltaX);
this.setTranslateY(event.getSceneY() - dragDeltaY);
});
this.setOnMouseEntered(event -> {
this.setCursor(Cursor.OPEN_HAND);
});
}
Thanks in advance for the help,
In most Pane subclasses, if a Node is managed, then the parent pane of the node will manage its layoutX and layoutY properties. So setting layoutX and layoutY for these nodes will have no visible effect on the position of the node.
Transformations (including the translation defined by translateX and translateY) are applied to nodes after the layout calculations (whether the node is managed by its parent or not).
So one way to manage dragging is just to manipulate the translateX and translateY properties. Another way is to manipulate the layoutX and layoutY properties (by setting them directly or by calling relocate), and make sure the node is not managed by its parent. I would not recommend mixing the two techniques, as your code will be harder to follow.
You can make the node unmanaged setManaged(false) on the node, or by putting it in a Pane instead of one of the subclasses (Pane does not manage layout of its child nodes).
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageViewDragExample extends Application {
#Override
public void start(Stage primaryStage) {
Image image = createImage();
DraggableImageView imageView = new DraggableImageView(image);
// if the node is placed in a parent that manages its child nodes,
// you must call setManaged(false);
// imageView.setManaged(false);
// StackPane root = new StackPane(imageView);
// or use a plain `Pane`, which does not manage its child nodes:
Pane root = new Pane(imageView);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Image createImage() {
WritableImage image = new WritableImage(50, 50);
for (int y = 0 ; y < 50; y++) {
for (int x = 0 ; x < 50 ; x++) {
Color c ;
if ((x > 20 && x < 30) || (y > 20 && y < 30)) {
c = Color.AZURE ;
} else {
c = Color.CORNFLOWERBLUE ;
}
image.getPixelWriter().setColor(x, y, c);
}
}
return image ;
}
public static class DraggableImageView extends ImageView {
private double mouseX ;
private double mouseY ;
public DraggableImageView(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX() ;
mouseY = event.getSceneY() ;
});
setOnMouseDragged(event -> {
double deltaX = event.getSceneX() - mouseX ;
double deltaY = event.getSceneY() - mouseY ;
relocate(getLayoutX() + deltaX, getLayoutY() + deltaY);
mouseX = event.getSceneX() ;
mouseY = event.getSceneY() ;
});
}
}
public static void main(String[] args) {
launch(args);
}
}

Java JPanel Scaling

Hey Guys I have succesfully made a GUI in java that will scale polygons and circles using a slider. Everything works but I was wondering if there is a way to change the Origin point(Where it scales from). Right now it scales from the corner and I would like it to scale from the middle so I can start it in the middle and it scales out evenly. Also, If anyone could tell me an easy way to replace the Rectangle I have with an Image of some kind so you can scale the Picture up and down would be great! Thank you! Here is my code:
import javax.swing.*;
public class Fred
{
public static void main(String[] args)
{
TheWindow w = new TheWindow();
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //X wont close the window with out this line
w.setSize(375,375);
w.setVisible(true);
}
}
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
public class TheWindow extends JFrame
{
private JSlider slider; //declare slider
private drawRect myPanel; //declare/ create panel
public TheWindow()
{
super("Slider Example"); //make title
myPanel = new drawRect();
myPanel.setBackground(Color.green); //change background color
slider = new JSlider(SwingConstants.VERTICAL, 0, 315, 10);// restrains the slider from scaling square to 0-300 pixels
slider.setMajorTickSpacing(20); //will set tick marks every 10 pixels
slider.setPaintTicks(true); //this actually paints the ticks on the screen
slider.addChangeListener
(
new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
myPanel.setD(slider.getValue()); //Wherever you set the slider, it will pass that value and that will paint on the screen
}
}
);
add(slider, BorderLayout.WEST); //similar to init method, adds slider and panel to GUI
add(myPanel, BorderLayout.CENTER);
}
}
import java.awt.*;
import javax.swing.*;
public class drawRect extends JPanel
{
private int d = 25; //this determines the beginning length of the rect.
public void paintComponent(Graphics g)//paints circle on the screen
{
super.paintComponent(g); //prepares graphic object for drawing
g.fillRect(15,15, d, d); //paints rectangle on screen
//x , y, width, height
}
public void setD(int newD)
{
d = (newD >= 0 ? newD : 10); //if number is less than zero it will use 10 for diameter(compressed if statement)
repaint();
}
public Dimension getPrefferedSize()
{
return new Dimension(200, 200);
}
public Dimension getMinimumSize()
{
return getPrefferedSize();
}
}
Changing the "origin point" so it becomes the center of the "zoom" is basically just the process of subtract half of d from the center point.
So, assuming the the center point is 28 ((25 / 2) + 15), you would simply then subtract d / 2 (25 / 2) from this point, 28 - (25 / 2) = 15 or near enough...
I modified the paintComponent method for testing, so the rectangle is always at the center of the panel, but you can supply arbitrary values in place of the originX and originY
#Override
public void paintComponent(Graphics g)//paints circle on the screen
{
super.paintComponent(g); //prepares graphic object for drawing
int originX = getWidth() / 2;
int originY = getHeight() / 2;
int x = originX - (d / 2);
int y = originY - (d / 2);
System.out.println(x + "x" + y);
g.fillRect(x, y, d, d); //paints rectangle on screen
//x , y, width, height
}
As for scaling an image, you should look at Graphics#drawImage(Image img, int x, int y, int width, int height, ImageObserver observer), beware though, this will scaling the image to the absolute size, it won't keep the image ratio.
A better solution might be to use a double value of between 0 and 1 and multiple the various elements by this value to get the absolute values you want

Categories

Resources