Finding the original (x,y) coordinates after zooming - java

I am using the below code for zooming effect, this code will be applied to a group consisting of a map image which allows the user to click on a city to get information about it, but the problem is when the user zooms I need to know the original (x,y) coordinates in order to display the proper information. How can I solve this problem? what I have tried is to get the zoom ratio and then multiple (zoom out) or divide (zoom in) new coordinate by it.
private Parent createZoomPane(final Group group) {
final double SCALE_DELTA = 1.1;
final StackPane zoomPane = new StackPane();
zoomPane.getChildren().add(group);
zoomPane.setOnScroll(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
event.consume();
if (event.getDeltaY() == 0) {
return;
}
double scaleFactor =
(event.getDeltaY() > 0)
? SCALE_DELTA
: 1/SCALE_DELTA;
group.setScaleX(group.getScaleX() * scaleFactor);
group.setScaleY(group.getScaleY() * scaleFactor);
}
});
return zoomPane;
}
root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
// root is the map parent.
public void handle(MouseEvent mouseEvent) {
double x = mouseEvent.getX();
double y = mouseEvent.getY();
// Then I use x,y to find the city which the user wants, but when i apply zoom
// effect the x,y changes fron the orignal values.
}
});

Related

Saving mouse position to objects with JavaFX

I have a world map and I want to have a posibility to mark 3 dots on map by left clicking on the map, and save positions of dots (x,y) to 3 objects.
I think that the best approach would be to create 3 objects of class Point, save position (x,y) of each dot to 3 objects of class Point. I did it only for one object (redPoint) because I'm not sure how I can do this for more objects.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("../resources/fxml/test.fxml"));
primaryStage.setTitle("test");
Point point = new Point();
root.setOnMouseClicked(point::handle);
primaryStage.setScene(new Scene(root, 1280, 720));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
public class Point {
private double x;
private double y;
public Point() {
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void handle(MouseEvent mouseEvent) {
x = mouseEvent.getX();
y = mouseEvent.getY();
Point redPoint = new Point(x, y);
System.out.println(redPoint.getX() + " " + redPoint.getY());
}
}
Current output:
"459.0 220.0" - x, y of place where I clicked but only for one dot and this dot is not placed on map
So I'm not sure:
how to add colored dots in random place where I clicked
how to save position of these dots to 3 different objects
Using JFX Canvas to display the map and the dot can be clicked as a little circle with color of course. Example
Canvas screen = new Canvas(580, 500);
gc = screen.getGraphicsContext2D();
screen.setOnMouseReleased(e -> {
double x = e.getX(), y = e.getY(); // the coordinates
draw(x, y);
});
...
privat void draw(double x, double y) {
...
gc.drawImage(img, 0, 0);
gc.setFill(Color.RED);
gc.fillOval(x, y, 15, 15); // the dot
...
}
private GraphicsContext gc;
private Image img = new Image(getClass().getResourceAsStream("map.jpg"));
...
Hope that could help.

Create an Ellipse in javavfx where the scaling is from the top left (like a Rectangle) rather than center

I have a javafx program that generates a canvas where a user can draw an Ellipse. I am using the press down, drag and release technique for the mouse.
However I want to be able to scale the shape in size from the top left corner of the shape (much like a Rectangle is scaled) rather than its center. Is there any suggestions to how I could achieve this?
#Override
public void start(Stage ellipseStage) {
//Create ellipse
ellipsePane = new Pane();
ellipsePane.setMinSize(600,600);
ellipsePane.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
ellipse = new Ellipse();
ellipse.setCenterX(e.getX());
ellipse.setCenterY(e.getY());
ellipse.setStroke(Color.ORANGE);
ellipse.setFill(Color.BLACK);
ellipsePane.getChildren().add(ellipse);
}
//if we double click
if (e.getClickCount() == 2) {
ellipse = null;
}
});
//When the mouse is dragged the ellipse expands
ellipsePane.setOnMouseDragged(e -> {
if (ellipse != null) {
ellipse.setRadiusX(e.getX() - ellipse.getCenterX());
ellipse.setRadiusY(e.getY() - ellipse.getCenterY());
}
});
Scene scene = new Scene(ellipsePane);
ellipseStage.setScene(scene);
ellipseStage.show();
}
public static void main(String[] args) {
launch(args);
}}
One option is to keep track of the location of the original mouse press, then set the center X/Y properties of the Ellipse to always be the midpoint between the origin and where the mouse is currently (i.e. where it's dragged). The radii would be half the distance between the origin and the current location as well. Here's an example:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
public class Main extends Application {
private Pane pane;
private Point2D origin;
private Ellipse ellipse;
#Override
public void start(Stage primaryStage) {
pane = new Pane();
pane.setOnMousePressed(this::handleMousePressed);
pane.setOnMouseDragged(this::handleMouseDragged);
pane.setOnMouseReleased(this::handleMouseReleased);
primaryStage.setScene(new Scene(pane, 600.0, 400.0));
primaryStage.show();
}
private void handleMousePressed(MouseEvent event) {
event.consume();
origin = new Point2D(event.getX(), event.getY());
ellipse = new Ellipse(event.getX(), event.getY(), 0.0, 0.0);
pane.getChildren().add(ellipse);
}
private void handleMouseDragged(MouseEvent event) {
event.consume();
ellipse.setCenterX((origin.getX() + event.getX()) / 2.0);
ellipse.setCenterY((origin.getY() + event.getY()) / 2.0);
ellipse.setRadiusX(Math.abs(event.getX() - origin.getX()) / 2.0);
ellipse.setRadiusY(Math.abs(event.getY() - origin.getY()) / 2.0);
}
private void handleMouseReleased(MouseEvent event) {
event.consume();
ellipse = null;
origin = null;
}
}
So I presume what you mean is that when you drag the ellipse, the edge position changes but you want the edge position to stay the same and the center to move.
ellipsePane.setOnMouseDragged(e -> {
if (ellipse != null) {
double x0 = ellipse.getCenterX() - ellipse.getRadiusX();
double y0 = ellipse.getCenterY() - ellipse.getRadiusY();
//the new radii
double rxp = e.getX() - x0;
double ryp = e.getY() - y0;
//the new center positions. to keep the origin the same.
double cx = x0 + rxp;
double cy = y0 + ryp;
ellipse.setRadiusX(rxp);
ellipse.setRadiusY(ryp);
ellipse.setCenterX(cx);
ellipse.setCenterY(cy);
}
});

How to set draggable bounds for a polygon - JavaFX

I have a polygon that can be resized as required and dragged/moved around the scene as desired. However, my question is how can I stop it from being dragged over buttons or my treeview list? Here is my code:
public Polygon cfp(ActionEvent event) throws IOException {
Polygon fp = new Polygon();
ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
//Set the anchor points for the template layout
fp.getPoints().setAll(
350d, 50d,
700d, 50d,
1050d, 50d,
1050d, 350d,
1050d, 650d,
700d, 650d,
350d, 650d,
350d, 350d
);
//Allow the Floor plan to be draggable around the screen
fp.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
}
});
fp.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
fp.setLayoutX(fp.getLayoutX()+deltaX);
fp.setLayoutY(fp.getLayoutY()+deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
}
});
//Set the colour and properties of the template layout
fp.setStroke(Color.DARKRED);
fp.setStrokeWidth(4);
fp.setStrokeLineCap(StrokeLineCap.ROUND);
fp.setFill(Color.MINTCREAM);
container.getChildren().add(fp);
container.getChildren().addAll(createAnchors(fp, fp.getPoints()));
return fp;
}
private ObservableList<Anchor> createAnchors(Polygon polygon, final ObservableList<Double> points) {
ObservableList<Anchor> anchors = FXCollections.observableArrayList();
for (int i = 0; i < points.size(); i += 2) {
final int idx = i;
DoubleProperty xProperty = new ListWriteDoubleProperty(points, i);
DoubleProperty yProperty = new ListWriteDoubleProperty(points, i + 1);
//Bind the anchors to the polygon, so if its moved so are they
Anchor anchor = new Anchor(Color.BLACK, xProperty, yProperty);
anchor.layoutXProperty().bindBidirectional(polygon.layoutXProperty());
anchor.layoutYProperty().bindBidirectional(polygon.layoutYProperty());
xProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldX, Number x) {
points.set(idx, (double) x);
}
});
yProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldY, Number y) {
points.set(idx + 1, (double) y);
}
});
anchors.add(anchor);
}
return anchors;
}
//Creating circles to mark the anchor points to help users know where to modify from
class Anchor extends Circle {
private final DoubleProperty x, y;
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 5);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
this.x = x;
this.y = y;
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
//Make the circle node movable with mouse drag
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records the x and y co-ordinates.
private class Delta {
double x, y;
}
}
Here is my problem:
the scene where the polygon generates has an Anchor pane, the treeview is in a HBox and so are the buttons if that helps anyone.
What is Happening
Your check for the drag points for the anchor are based upon the scene dimensions rather than the dimensions of the parent container of the polygon.
How to Fix it
Change your checks to be based upon the parent container dimensions.
From:
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
To:
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getParent().getLayoutBounds().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getParent().getLayoutBounds().getHeight()) {
setCenterY(newY);
}
}
});
Ensure that your parent is a resizable parent (e.g. a Pane and not a Group), otherwise the parent won't automatically expand to fill the available area in which the polygon can be placed.
Additional Concerns
If you resize the scene so that the area in which the polygon can be rendered is smaller than the size of the polygon, then the polygon will still overflow the available bounds (as they have now shrunk smaller than the size of the polygon). There a couple of ways you could handle that situation.
You could place the polygon in a ScrollPane, so that the user can scroll around if the currently viewable area gets too small. This is probably the preferred solution, but is a bit more complicated to implement well (and it is isn't really the question you asked). I won't provide example code for this at the moment.
You can apply a clip to the parent container so that it does not draw outside the visible area. For example if your container Pane for the polygon is named polyPane:
Rectangle clip = new Rectangle();
clip.widthProperty().bind(polyPane.widthProperty());
clip.heightProperty().bind(polyPane.heightProperty());
polyPane.setClip(clip);
Content sourced from StackOverflow requires attribution.

JavaFX 8 3D animation

I am making a little JavaFX 8 project. I need some advices how to do the animation correctly. Something about the project. It's a program which animates the flow of a charged particle through magnetic field. All the needed values are taken from GUI where user puts them into textFields. After button click we are transfered to 3D scene where my point is shown as Sphere, all values are set up. And the field line is printed in it's directory.
The question is how to do the propper animation. I was trying to work with my Sphere X Y Z coordinates, but couldn't find any way to set those. The motion in Z plane should be linear with the same speed. And motion in XY planees should be circular. Can I do it with Path Transition?
My vision is to create the tick animation which draws the Spheres through the path. Whith a further tick the next sphere will be drew with new coordinates calculated by translation vector.
Is this possible using a PathTransition? No, paths are 2 dimensional in javafx, but you need a 3D movement.
Note that sphere coordinates not a good coordinate system to discribe such a movement either, since the calculation of the angles is a bit complex.
A coordinate system better suited would be cylinder coordinates.
You could use several transforms and animate those using a Timeline animation to achieve this kind of movement though:
private static void animateSphere(Sphere sphere) {
Rotate rot = new Rotate();
Translate radiusTranslate = new Translate(50, 0, 0);
Translate zMovement = new Translate();
sphere.getTransforms().setAll(zMovement, rot, radiusTranslate);
Timeline tl = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(zMovement.zProperty(), 0d),
new KeyValue(rot.angleProperty(), 0d)),
new KeyFrame(Duration.seconds(4),
new KeyValue(zMovement.zProperty(), 900d, Interpolator.LINEAR),
new KeyValue(rot.angleProperty(), 720, Interpolator.LINEAR))
);
tl.setCycleCount(Timeline.INDEFINITE);
tl.play();
}
#Override
public void start(Stage primaryStage) {
Sphere sphere = new Sphere(30);
Pane root = new Pane(sphere);
Scene scene = new Scene(root, 400, 400, true);
PerspectiveCamera camera = new PerspectiveCamera();
camera.setTranslateZ(-10);
camera.setTranslateX(-500);
camera.setTranslateY(-200);
camera.setRotationAxis(new Point3D(0, 1, 0));
camera.setRotate(45);
scene.setCamera(camera);
animateSphere(sphere);
primaryStage.setScene(scene);
primaryStage.show();
}
Your movement is a spiral movement and therefore the following combination of transformations will move the Sphere appropritately:
translate by radius (z component 0)
rotate to appropriate angle
translate in z direction
Note: Transforms are applied in reverse order in which they occur in the transforms list.
Alternatively you could write a helper that can be used with cylinder coordinate parameters and write the appropriate x, y and z values:
public class CylinderCoordinateAdapter {
private final DoubleProperty theta = new SimpleDoubleProperty();
private final DoubleProperty radius = new SimpleDoubleProperty();
private final DoubleProperty h = new SimpleDoubleProperty();
private static final Point3D DEFAULT_AXIS = new Point3D(0, 0, 1);
private Point3D axis2;
private Point3D axis3;
private final ObjectProperty<Point3D> axis = new SimpleObjectProperty<Point3D>() {
#Override
public void set(Point3D newValue) {
newValue = (newValue == null || newValue.equals(Point3D.ZERO)) ? DEFAULT_AXIS : newValue.normalize();
// find first value ortogonal to axis with z = 0
axis2 = newValue.getX() == 0 && newValue.getY() == 0 ? new Point3D(1, 0, 0) : new Point3D(-newValue.getY(), newValue.getX(), 0).normalize();
// find axis ortogonal to the other 2
axis3 = newValue.crossProduct(axis2);
super.set(newValue);
}
};
public CylinderCoordinateAdapter(WritableValue<Number> x, WritableValue<Number> y, WritableValue<Number> z) {
Objects.requireNonNull(x);
Objects.requireNonNull(y);
Objects.requireNonNull(z);
axis.set(DEFAULT_AXIS);
InvalidationListener listener = o -> {
Point3D ax = axis.get();
double h = getH();
double theta = getTheta();
double r = getRadius();
Point3D endPoint = ax.multiply(h).add(axis2.multiply(Math.cos(theta) * r)).add(axis3.multiply(Math.sin(theta) * r));
x.setValue(endPoint.getX());
y.setValue(endPoint.getY());
z.setValue(endPoint.getZ());
};
theta.addListener(listener);
radius.addListener(listener);
h.addListener(listener);
axis.addListener(listener);
listener.invalidated(null);
}
public final Point3D getAxis() {
return this.axis.get();
}
public final void setAxis(Point3D value) {
this.axis.set(value);
}
public final ObjectProperty<Point3D> axisProperty() {
return this.axis;
}
public final double getH() {
return this.h.get();
}
public final void setH(double value) {
this.h.set(value);
}
public final DoubleProperty hProperty() {
return this.h;
}
public final double getRadius() {
return this.radius.get();
}
public final void setRadius(double value) {
this.radius.set(value);
}
public final DoubleProperty radiusProperty() {
return this.radius;
}
public final double getTheta() {
return this.theta.get();
}
public final void setTheta(double value) {
this.theta.set(value);
}
public final DoubleProperty thetaProperty() {
return this.theta;
}
}
private static void animateSphere(Sphere sphere) {
CylinderCoordinateAdapter adapter = new CylinderCoordinateAdapter(
sphere.translateXProperty(),
sphere.translateYProperty(),
sphere.translateZProperty());
adapter.setRadius(50);
Timeline tl = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(adapter.hProperty(), 0d),
new KeyValue(adapter.thetaProperty(), 0d)),
new KeyFrame(Duration.seconds(4),
new KeyValue(adapter.hProperty(), 900d, Interpolator.LINEAR),
new KeyValue(adapter.thetaProperty(), Math.PI * 4, Interpolator.LINEAR))
);
tl.setCycleCount(Timeline.INDEFINITE);
tl.play();
}

What is the best UI element dragging algorithm

I made a GUI application but I am stuck with a workaround algorithm to drag/move ui element relatively to another surface (screen, canvas, etc.). In my case I use it for a window relative to the screen, but that's beside the point because this algorithm should work anywhere possible. Here is my algorithm:
code-listing-1.
MouseMotionAdapter(){
int prevX = -1000, prevY = -1000, getX, getY;
public void mouseDragged(MouseEvent e) {
//if initial cursor position isn't set
if(prevX==-1000){
prevX = e.getLocationOnScreen().x;
prevY = e.getLocationOnScreen().y;
getX = e.getX();
getY = e.getY();
}
//move element to new position
theFrame.setBounds(theFrame.getBounds().x+e.getX()-getX, theFrame.getBounds().y+e.getY()-getY, 880, 583);
prevX=e.getLocationOnScreen().x;
prevY=e.getLocationOnScreen().y;
}
The trouble with this algorithm is that the mouse cursor position is definitely fixed relatively to the element and if I try to move/drag the element clicking in another position/part of the element the whole element moves so that the mouse cursor is positioned at the "initial position", which is not the behaviour I want (I want it to have the behaviour we mostly know like when we move an icon on desktop or a window on the screen, etc.)
Can anyone help with that? Thanks in advance.
The usual approach in such a case is to compute the difference between the current and the previous position. This difference is then "the movement", that is added to the position of the dragged object.
(BTW: You seem to not use the prev... values in your computation at all!)
Inside a MouseMotionListener, this could roughly look as follows:
class MouseDraggingControl implements MouseMotionListener
{
private Point previousPoint = new Point();
#Override
public void mouseMoved(MouseEvent e)
{
previousPoint = e.getPoint();
}
#Override
public void mouseDragged(MouseEvent e)
{
int movementX = e.getX() - previousPoint.x;
int movementY = e.getY() - previousPoint.y;
doMovement(movementX, movementY);
previousPoint = e.getPoint();
}
}
In your case, the doMovement method might be implemented like this:
private void doMovement(int movementX, int movementY)
{
int oldX = theFrame.getX();
int oldY = theFrame.getY();
int newX = oldX + movementX;
int newY = oldY + movementY;
theFrame.setLocation(newX, newY);
}
(or similar, using the getBounds/setBounds calls)
EDIT If the mouse motion listener is attached directly to the component that should be dragged, you might have to use the "location on screen" instead:
class MouseDraggingControl implements MouseMotionListener
{
private Point previousPoint = new Point();
#Override
public void mouseMoved(MouseEvent e)
{
previousPoint = e.getLocationOnScreen();
}
#Override
public void mouseDragged(MouseEvent e)
{
Point p = e.getLocationOnScreen();
int movementX = p.x - previousPoint.x;
int movementY = p.y - previousPoint.y;
doMovement(movementX, movementY);
previousPoint = e.getLocationOnScreen();
}
}
If this does not solve your issue, I'd like to emphasize the comment from MadProgrammer: An example that demonstrates your actual problem would help here.

Categories

Resources