In order to work on a project Rybi's cube game, I have to turn the cubes around particular axes, so the problem is that if I turn an object around an axis, eg Y-axis with a 90 degree, so if I turn it again around x axis,the rotation will be on the direction of the Z axis because the X axis takes dircetion of Z.
Below, there is a piece of code illustrates the same situation that I just introduce you.
Is there a way to do things the way I desire
public class RybiCube extends Application {
final Group root = new Group();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final XformCamera cameraXform = new XformCamera();
public static final double CAMERA_INITIAL_DISTANCE = -1000;
public static final double CAMERA_NEAR_CLIP = 0.1;
public static final double CAMERA_FAR_CLIP = 10000.0;
public void init(Stage primaryStage) {
Box box = new Box();
box.setHeight(70);
box.setWidth(200);
box.setDepth(70);
//box.setRotationAxis(Rotate.Y_AXIS);
//box.setRotate(80);
box.getTransforms().add(new Rotate(90,Rotate.Y_AXIS));
box.getTransforms().add(new Rotate(45,Rotate.X_AXIS));
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.ORANGE);
material.setSpecularColor(Color.BLACK);
box.setMaterial(material);
root.getChildren().add(box);
buildCamera();
Scene scene = new Scene(root, 600, 600, true);
primaryStage.setScene(scene);
scene.setCamera(camera);
///
primaryStage.setResizable(false);
scene.setFill(Color.rgb(0, 0,0,0.5));
primaryStage.setScene(scene);
}
private void buildCamera() {
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
}
public void start(Stage primaryStage)
{
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class XformCamera extends Group {
final Translate tr = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(00, Rotate.X_AXIS);
final Rotate ry = new Rotate(10, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
public XformCamera() {
super();
this.getTransforms().addAll(tr, rx, ry, rz);
}
}
You could simply apply the inverse transformations to the axis (using inverseDeltaTransform):
public static void addRotate(Node node, Point3D rotationAxis, double angle) {
ObservableList<Transform> transforms = node.getTransforms();
try {
for (Transform t : transforms) {
rotationAxis = t.inverseDeltaTransform(rotationAxis);
}
} catch (NonInvertibleTransformException ex) {
throw new IllegalStateException(ex);
}
transforms.add(new Rotate(angle, rotationAxis));
}
box.getTransforms().add(new Rotate(90,Rotate.Y_AXIS));
addRotate(box, Rotate.X_AXIS, 45);
the following code may be useful for your application, it is based a 3D orientation.
import javafx.scene.Group;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class XformOrientation extends Group {
private static final int SIZE = 5;
private Affine affine;
private final List<Double> averageRoll = new ArrayList();
private final List<Double> averagePitch = new ArrayList();
private final List<Double> averageYaw = new ArrayList();
public XformOrientation() {
super();
affine = new Affine();
this.getTransforms().add(affine);
}
/**
* angles in degrees
* #param roll
* #param pitch
* #param yaw
*/
public void processEvent(double roll, double pitch, double yaw) {
double avYaw = average(averageYaw, Math.toRadians(yaw));
double avPitch = average(averagePitch, Math.toRadians(pitch));
double avRoll = average(averageRoll, Math.toRadians(roll));
matrixRotateNode(avRoll, avPitch, avYaw);
}
private void matrixRotateNode(double roll, double pitch, double yaw) {
double mxx = Math.cos(pitch) * Math.cos(yaw);
double mxy = Math.cos(roll) * Math.sin(pitch) +
Math.cos(pitch) * Math.sin(roll) * Math.sin(yaw);
double mxz = Math.sin(pitch) * Math.sin(roll) -
Math.cos(pitch) * Math.cos(roll) * Math.sin(yaw);
double myx = -Math.cos(yaw) * Math.sin(pitch);
double myy = Math.cos(pitch) * Math.cos(roll) -
Math.sin(pitch) * Math.sin(roll) * Math.sin(yaw);
double myz = Math.cos(pitch) * Math.sin(roll) +
Math.cos(roll) * Math.sin(pitch) * Math.sin(yaw);
double mzx = Math.sin(yaw);
double mzy = -Math.cos(yaw) * Math.sin(roll);
double mzz = Math.cos(roll) * Math.cos(yaw);
affine.setToTransform(mxx, mxy, mxz, 0,
myx, myy, myz, 0,
mzx, mzy, mzz, 0);
}
private double average(List<Double> list, double value) {
while (list.size() > SIZE) {
list.remove(0);
}
list.add(value);
return list.stream()
.collect(Collectors.averagingDouble(d -> d));
}
}
as an application, you can use
private final XformOrientation world = new XformOrientation();
PhongMaterial whiteMaterial = new PhongMaterial();
whiteMaterial.setDiffuseColor(Color.WHITE);
whiteMaterial.setSpecularColor(Color.LIGHTBLUE);
Box box = new Box(400, 200, 100);
box.setMaterial(whiteMaterial);
world.getChildren().addAll(box);
Runnable runnable = new Runnable() {
#Override
public void run() {
Platform.runLater(() -> {
world.processEvent(getRoll(), getPitch(), getYaw());
});
}
};
getAhrsInfo().rollProperty().addListener((observable, oldValue, newValue) -> runnable.run());
getAhrsInfo().pitchProperty().addListener((observable, oldValue, newValue) -> runnable.run());
getAhrsInfo().yawProperty().addListener((observable, oldValue, newValue) -> runnable.run());
Related
I have a problem with zooming and panning image in ScrollPane. So far I have code like this:
Image image = imageView.getImage();
scrollPane.setPrefViewportWidth(0d);
scrollPane.setPrefViewportHeight(0d);
imageView.setFitWidth(0d);
imageView.setFitHeight(0d);
Bounds viewportBounds = scrollPane.getViewportBounds();
boolean vertical = image.getWidth() > image.getHeight();
if (imageView.getRotate() == 90 || imageView.getRotate() == 270d) {
vertical = !vertical;
}
imageView.setPreserveRatio(true);
double propX = viewportBounds.getWidth() / image.getWidth();
double propY = viewportBounds.getHeight() / image.getHeight();
boolean xLead = !(propX > propY);
imageView.setScaleX((xLead) ? propX : propY);
imageView.setScaleY((xLead) ? propX : propY);
scrollPane.setContent(imageView);
scrollPane.setPannable(true);
scrollPane.setHvalue(scrollPane.getHmin() + (scrollPane.getHmax() - scrollPane.getHmin()) / 2); // center the scroll contents.
scrollPane.setVvalue(scrollPane.getVmin() + (scrollPane.getVmax() - scrollPane.getVmin()) / 2);
zoom(imageView);
private void zoom(ImageView imagePannable) {
imagePannable.setOnScroll(
new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double zoomFactor = 1.20;
double deltaY = event.getDeltaY();
if (deltaY < 0) {
zoomFactor = 0.80;
}
imagePannable.setScaleX(imagePannable.getScaleX() * zoomFactor);
imagePannable.setScaleY(imagePannable.getScaleY() * zoomFactor);
event.consume();
}
});
}
What I want to do is align image relative to mouse pointer not to center of image everytime.
I also have a problem with large images (like maps which are 8*A4 size for example). When I zooming this maps pannable function stop working. What is wrong with this code? Thanks for helps!
Several people (including me) have had this same question. I got my answer here.
In the interest of clarity, here is a working example of a panning & zooming pane using a rectangle as the zoomed node. I have implemented this in a slightly more complex way with an ImageView.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ZoomAndPanExample extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
AnchorPane.setTopAnchor(scrollPane, 10.0d);
AnchorPane.setRightAnchor(scrollPane, 10.0d);
AnchorPane.setBottomAnchor(scrollPane, 10.0d);
AnchorPane.setLeftAnchor(scrollPane, 10.0d);
AnchorPane root = new AnchorPane();
Rectangle rect = new Rectangle(80, 60);
rect.setStroke(Color.NAVY);
rect.setFill(Color.NAVY);
rect.setStrokeType(StrokeType.INSIDE);
group.getChildren().add(rect);
// create canvas
PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.3d;
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(60);
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
);
timeline.play();
}
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = scale - oldScale;
double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
double newX = f*dx + getBoundsInParent().getMinX();
double newY = f*dy + getBoundsInParent().getMinY();
setPivot(newX, newY, scale);
}
public void resetZoom () {
double scale = 1.0d;
double x = getTranslateX();
double y = getTranslateY();
setPivot(x, y, scale);
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.resetZoom();
}
}
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.fitWidth();
}
}
}
};
}
}
I have a class "Vertex" that contains double x, y.
Another class "Face" holds a list of "Vertex" objects. Neighboring faces share the same vertices.
At the moment I'm creating a javafx.scene.shape.Polygon for every Face and add them all to my scene, which looks like this:
Screenshot
Now I'm planning to modify the polygons, similar to this: JavaFX modify polygons
The problem is that the polygons don't save references to my Vertex objects but double values. When I change the position of one point, the same point in the neighboring polygons is still at the old position. How can I link those points to each other? And also how to save the changes back to my "Face" object?
Code example as requested: pastebin.com/C3JHb2nM
Here you go. Calculated common anchor positions then adjusted all of the common ones when one was moved.
Anchor contains addCommon function which adds an anchor that is common to it. The common variable stores all the common anchors. Then when handle is called, all of the common ones x and y positions change as well.
Also, I will suggest that you hold the common points in Faces. I've created a simple method that will calculate all the Faces that share a vertex in said class. But following a MVC guideline, you need to have a model that provides all of necessary data to create a GUI. I would suggest Mesh, Face, and Vertex should provide all necessary information to create the GUI, and not as a two way street. Basically, Anchor should not alter your models in any way.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class Main extends Application {
public class Vertex {
private double x, y;
public Vertex(double x, double y) {
this.x = x;
this.y = y;
}
public Double[] getPoint() {
return new Double[]{x, y};
}
}
public class Face {
private List<Vertex> verts;
public Face(Vertex... verts) {
this.verts = new ArrayList<>(Arrays.asList(verts));
}
public Polygon getPolygon() {
Polygon polygon = new Polygon();
polygon.setFill(Color.GRAY);
polygon.setStroke(Color.BLACK);
for (Vertex vertex : verts) {
polygon.getPoints().addAll(vertex.getPoint());
}
return polygon;
}
public boolean containsVertex(Vertex ver) {
for (Vertex v : this.verts) {
if (v.x == ver.x && v.y == ver.y) {
return true;
}
}
return false;
}
}
public class Mesh {
private List<Vertex> verts = new ArrayList<Vertex>();
private List<Face> faces = new ArrayList<Face>();
private Map<Vertex, List<Face>> commonVertices = new HashMap<>();
public List<Polygon> getPolygons() {
List<Polygon> polygons = new ArrayList<Polygon>();
for (Face face : faces) {
polygons.add(face.getPolygon());
}
return polygons;
}
public Mesh() {
verts.add(new Vertex(50, 50));
verts.add(new Vertex(300, 50));
verts.add(new Vertex(500, 50));
verts.add(new Vertex(50, 300));
verts.add(new Vertex(250, 300));
verts.add(new Vertex(500, 300));
verts.add(new Vertex(50, 600));
verts.add(new Vertex(300, 700));
verts.add(new Vertex(500, 700));
faces.add(new Face(verts.get(0), verts.get(1), verts.get(4), verts.get(3)));
faces.add(new Face(verts.get(4), verts.get(1), verts.get(2), verts.get(5)));
faces.add(new Face(verts.get(3), verts.get(4), verts.get(7), verts.get(6)));
faces.add(new Face(verts.get(7), verts.get(4), verts.get(5)));
faces.add(new Face(verts.get(7), verts.get(5), verts.get(8)));
findCommonVertices();
}
private void findCommonVertices() {
for (Vertex ver : this.verts) {
List<Face> share = new ArrayList<>();
for (Face face : this.faces) {
if (face.containsVertex(ver)) {
share.add(face);
}
}
commonVertices.put(ver, share);
}
}
public Map<Vertex, List<Face>> getCommonVertices() {
return this.commonVertices;
}
}
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 1024, 768);
stage.setScene(scene);
Group g = new Group();
Mesh mesh = new Mesh();
List<Polygon> polygons = mesh.getPolygons();
g.getChildren().addAll(polygons);
List<Anchor> anchors = new ArrayList<>();
for (Polygon p : polygons) {
ObservableList<Anchor> temp = createControlAnchorsFor(p.getPoints());
g.getChildren().addAll(temp);
for (Anchor kk : temp) {
anchors.add(kk);
}
}
for (int i = 0; i < anchors.size(); i++) {
List<Anchor> common = new ArrayList<>();
for (int j = 0; j < anchors.size(); j++) {
if (i != j) {
if (anchors.get(i).x.doubleValue() == anchors.get(j).x.doubleValue() && anchors.get(i).y.doubleValue() == anchors.get(j).y.doubleValue()) {
anchors.get(i).addCommon(anchors.get(j));
System.out.println("COMMON " + i + " " + j);
}
}
}
// anchors.get(i).setCommon(common);
}
scene.setRoot(g);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
// Everything below was copied from here: https://gist.github.com/jewelsea/5375786
// #return a list of anchors which can be dragged around to modify points in the format [x1, y1, x2, y2...]
private ObservableList<Anchor> createControlAnchorsFor(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 SimpleDoubleProperty(points.get(i));
DoubleProperty yProperty = new SimpleDoubleProperty(points.get(i + 1));
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(new Anchor(Color.GOLD, xProperty, yProperty));
}
return anchors;
}
// a draggable anchor displayed around a point.
class Anchor extends Circle {
private final DoubleProperty x, y;
List<Anchor> common = new ArrayList<>();
public void setCommon(List<Anchor> common) {
this.common = common;
}
public void addCommon(Anchor com) {
common.add(com);
enableDrag();
}
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
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 a node movable by dragging it around with the mouse.
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);
if (common != null) {
for (Anchor an : common) {
an.setCenterX(newX);
System.out.println("CALLED");
}
}
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
if (common != null) {
for (Anchor an : common) {
an.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 relative x and y co-ordinates.
private class Delta {
double x, y;
}
}
}
Make the x and y coordinates of the vertices properties. You could then add listeners to those properties that modify the points of the Polygons:
public class Vertex {
public DoubleProperty xProperty() {
...
}
public DoubleProperty yProperty() {
...
}
}
public class VertexListener implements InvalidationListerner {
private final Vertex vertex;
private final int firstIndex;
private final List<Double> points;
public VertexListener(Vertex vertex, Polygon polygon, int pointIndex) {
this.firstIndex = 2 * pointIndex;
this.vertex = vertex;
this.points = polygon.getPoints();
vertex.xProperty().addListener(this);
vertex.yProperty().addListener(this);
}
public void dispose() {
vertex.xProperty().removeListener(this);
vertex.yProperty().removeListener(this);
}
#Override
public void invalidated(Observable observable) {
double x = vertex.getX();
double y = vertex.getY();
points.set(firstIndex, x);
points.set(firstIndex+1, y);
}
}
This way you only need to adjust the property values in Vertex and the listeners will add all incident faces...
Need to make a sphere in JavaFX "blink" over time, like fade in and fade out. Is it possible? Or at least change shades of color. I managed to make it blink by changing SelfIlluminationMap property of the PhongMaterial I am using, with this code
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class JavaFXApplication15 extends Application {
Image im = new Image("bump.jpg");
PhongMaterial ph = new PhongMaterial(Color.GREEN);
int nums = 0;
UpdateTimer timer;
private class UpdateTimer extends AnimationTimer {
int counter = 0;
#Override
public void handle(long now) {
if (counter == 20) {
update();
counter = 0;
}
counter++;
}
}
private Parent createContent() throws Exception {
timer = new UpdateTimer();
ph = new PhongMaterial(Color.YELLOW, null, null, null, im);
Box box = new Box(5, 5, 5);
box.setMaterial(ph);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll(
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -50)
);
// Build the Scene Graph
Group root = new Group();
root.getChildren().add(camera);
root.getChildren().add(box);
// Use a SubScene
SubScene subScene = new SubScene(
root,
300, 300,
true,
SceneAntialiasing.BALANCED
);
subScene.setFill(Color.ALICEBLUE);
subScene.setCamera(camera);
Group group = new Group();
group.getChildren().add(subScene);
return group;
}
void update() {
if (ph.getSelfIlluminationMap() != null) {
ph.setSelfIlluminationMap(null);
} else {
ph.setSelfIlluminationMap(im);
}
}
#Override
public void start(Stage stage) throws Exception {
stage.setResizable(false);
Scene scene = new Scene(createContent());
stage.setScene(scene);
stage.show();
timer.start();
}
public static void main(String[] args) {
launch(args);
}
}
but is it possible to make it fade in and out? With some kind of transition possibly?
Just to see the effect, I tried animating the specularColor of a sphere's PhongMaterial. Starting from this example, I followed the approach shown here to get a color lookup table of equally spaced brightness values of a given hue.
private final Queue<Color> clut = new LinkedList<>();
The implementation of handle() simply cycles through the table.
#Override
public void handle(long now) {
redMaterial.setSpecularColor(clut.peek());
clut.add(clut.remove());
}
If the result is appealing, a more flexible approach might accrue from using a concrete subclass of Transition.
private final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
setAutoReverse(true);
setCycleCount(INDEFINITE);
}
#Override
protected void interpolate(double d) {
redMaterial.setSpecularColor(Color.hsb(LITE.getHue(), 1, d));
redMaterial.setDiffuseColor(Color.hsb(DARK.getHue(), 1, d / 2));
}
};
import java.util.LinkedList;
import java.util.Queue;
import javafx.animation.AnimationTimer;
import javafx.animation.Animation;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* #see https://stackoverflow.com/a/44447913/230513
* #see https://stackoverflow.com/a/37755149/230513
* #see https://stackoverflow.com/a/37743539/230513
* #see https://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
private static final class Content {
private static final double WIDTH = 3;
private static final Color LITE = Color.RED;
private static final Color DARK = Color.RED.darker().darker();
private final Xform group = new Xform();
private final Group cube = new Group();
private final Group axes = new Group();
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private final Sphere sphere;
private final PhongMaterial redMaterial = new PhongMaterial();
private final UpdateTimer timer = new UpdateTimer();
private class UpdateTimer extends AnimationTimer {
private static final double N = 32d;
private final Queue<Color> clut = new LinkedList<>();
public UpdateTimer() {
for (int i = 0; i < N; i++) {
clut.add(Color.hsb(LITE.getHue(), 1, 1 - (i / N)));
}
for (int i = 0; i < N; i++) {
clut.add(Color.hsb(LITE.getHue(), 1, i / N));
}
}
#Override
public void handle(long now) {
redMaterial.setSpecularColor(clut.peek());
clut.add(clut.remove());
}
}
private final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
setAutoReverse(true);
setCycleCount(INDEFINITE);
}
#Override
protected void interpolate(double d) {
redMaterial.setSpecularColor(Color.hsb(LITE.getHue(), 1, d));
redMaterial.setDiffuseColor(Color.hsb(DARK.getHue(), 1, d / 2));
}
};
private static Content create(double size) {
Content c = new Content(size);
c.cube.getChildren().addAll(c.box, c.sphere);
c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
c.group.getChildren().addAll(c.cube, c.axes);
return c;
}
private Content(double size) {
double edge = 3 * size / 4;
xAxis = createBox(edge, WIDTH, WIDTH, edge);
yAxis = createBox(WIDTH, edge / 2, WIDTH, edge);
zAxis = createBox(WIDTH, WIDTH, edge / 4, edge);
box = new Box(edge, edge / 2, edge / 4);
box.setDrawMode(DrawMode.LINE);
sphere = new Sphere(8);
redMaterial.setDiffuseColor(DARK);
redMaterial.setSpecularColor(LITE);
sphere.setMaterial(redMaterial);
sphere.setTranslateX(edge / 2);
sphere.setTranslateY(-edge / 4);
sphere.setTranslateZ(-edge / 8);
}
private Box createBox(double w, double h, double d, double edge) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
b.setTranslateX(-edge / 2 + w / 2);
b.setTranslateY(edge / 4 - h / 2);
b.setTranslateZ(edge / 8 - d / 2);
return b;
}
}
private static class Xform extends Group {
private final Point3D px = new Point3D(1.0, 0.0, 0.0);
private final Point3D py = new Point3D(0.0, 1.0, 0.0);
private Rotate r;
private Transform t = new Rotate();
public void rx(double angle) {
r = new Rotate(angle, px);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void ry(double angle) {
r = new Rotate(angle, py);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void rz(double angle) {
r = new Rotate(angle);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-2 * SIZE);
scene.setCamera(camera);
scene.setOnMousePressed((MouseEvent e) -> {
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseOldX = e.getSceneX();
mouseOldY = e.getSceneY();
});
scene.setOnMouseDragged((MouseEvent e) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (e.isShiftDown()) {
content.group.rz(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isPrimaryButtonDown()) {
content.group.rx(+mouseDeltaY * 180.0 / scene.getHeight());
content.group.ry(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isSecondaryButtonDown()) {
camera.setTranslateX(camera.getTranslateX() - mouseDeltaX * 0.1);
camera.setTranslateY(camera.getTranslateY() - mouseDeltaY * 0.1);
}
});
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
//content.timer.start();
content.animation.play();
}
public static void main(String[] args) {
launch(args);
}
}
I have written a sample program that implements Zooming function on a JavaFX Chart, I found the Zoom class from the GitHub project and am just reusing it. My challenge is that when I drag the mouse to select some region to Zoom, the selected area rectangle doesn't show in Windows 7, Linux, Mac OS X, but it works fine in Windows 10.
What am I missing, how can I make the selectedRectangle to show so the user can know what area they are zooming into?
Below are all the files that are needed to compile and run this program:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testlinechartgraphs;
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TestLineChartGraphs extends Application {
final static ObservableList<XYChart.Series<Number, Number>> lineChartData = FXCollections.observableArrayList();
#Override
public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
Random randomNumbers = new Random();
ArrayList<Integer> arrayList = new ArrayList<>();
//creating the chart
final LineChart<Number, Number> lineChart
= new LineChart<Number, Number>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
lineChart.setLegendSide(Side.RIGHT);
int randomCount = randomNumbers.nextInt(14)+1;
//System.out.println("randomCount = " + randomCount);
for (int i = 0; i < randomCount; i++) {
XYChart.Series series = new XYChart.Series();
series.setName("series_" + i);
for (int k = 0; k < 20; k++) {
int x = randomNumbers.nextInt(50);
series.getData().add(new XYChart.Data(k, x));
}
//seriesList.add(series);
lineChartData.add(series);
}
lineChart.setData(lineChartData);
final StackPane chartContainer = new StackPane();
Zoom zoom = new Zoom(lineChart, chartContainer);
chartContainer.getChildren()
.add(lineChart);
BorderPane borderPane = new BorderPane();
borderPane.setCenter(chartContainer);
//borderPane.setCenter(lineChart);
borderPane.setBottom(getLegend());
////
//Scene scene = new Scene(lineChart, 800, 600);
Scene scene = new Scene(borderPane, 800, 600);
//lineChart.getData().addAll(series, series1);
stage.setScene(scene);
scene.getStylesheets().addAll("file:///C:/Users/siphoh/Documents/NetBeansProjects/WiresharkSeqNum/src/fancychart.css");
//scene.getStylesheets().addAll(getClass().getResource("fancychart.css").toExternalForm());
stage.show();
}
public static Node getLegend() {
HBox hBox = new HBox();
for (final XYChart.Series<Number, Number> series : lineChartData) {
CheckBox checkBox = new CheckBox(series.getName());
checkBox.setSelected(true);
checkBox.setOnAction(event -> {
if (lineChartData.contains(series)) {
lineChartData.remove(series);
} else {
lineChartData.add(series);
}
});
hBox.getChildren().add(checkBox);
}
hBox.setAlignment(Pos.CENTER);
hBox.setSpacing(20);
hBox.setStyle("-fx-padding: 0 10 20 10");
return hBox;
}
public static void main(String[] args) {
launch(args);
}
}
//Zoom.java class:
package testlinechartgraphs;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author *************
*/
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
/**
* This class adds a zoom functionality to a given XY chart. Zoom means that a
user can select a region in the chart that should be displayed at a larger
scale.
*
*/
public class Zoom {
private static final String INFO_LABEL_ID = "zoomInfoLabel";
private final Pane pane;
private final XYChart<Number, Number> chart;
private final NumberAxis xAxis;
private final NumberAxis yAxis;
private final SelectionRectangle selectionRectangle;
private Label infoLabel;
private Point2D selectionRectangleStart;
private Point2D selectionRectangleEnd;
/**
* Create a new instance of this class with the given chart and pane
* instances. The {#link Pane} instance is needed as a parent for the
* rectangle that represents the user selection.
*
* #param chart the xy chart to which the zoom support should be added
* #param pane the pane on which the selection rectangle will be drawn.
*/
public Zoom(XYChart<Number, Number> chart, Pane pane) {
this.pane = pane;
this.chart = chart;
this.xAxis = (NumberAxis) chart.getXAxis();
this.yAxis = (NumberAxis) chart.getYAxis();
selectionRectangle = new SelectionRectangle();
pane.getChildren().add(selectionRectangle);
addDragSelectionMechanism();
addInfoLabel();
}
/**
* The info label shows a short info text that tells the user how to unreset
* the zoom level.
*/
private void addInfoLabel() {
infoLabel = new Label("Click ESC to reset the zoom level.");
infoLabel.setId(INFO_LABEL_ID);
pane.getChildren().add(infoLabel);
StackPane.setAlignment(infoLabel, Pos.TOP_RIGHT);
infoLabel.setVisible(false);
}
/**
* Adds a mechanism to select an area in the chart that should be displayed
* at larged scale.
*/
private void addDragSelectionMechanism() {
pane.addEventHandler(MouseEvent.MOUSE_PRESSED, new MousePressedHandler());
pane.addEventHandler(MouseEvent.MOUSE_DRAGGED, new MouseDraggedHandler());
pane.addEventHandler(MouseEvent.MOUSE_RELEASED, new MouseReleasedHandler());
pane.addEventHandler(KeyEvent.KEY_RELEASED, new EscapeKeyHandler());
}
private Point2D computeRectanglePoint(double eventX, double eventY) {
double lowerBoundX = computeOffsetInChart(xAxis, false);
double upperBoundX = lowerBoundX + xAxis.getWidth();
double lowerBoundY = computeOffsetInChart(yAxis, true);
double upperBoundY = lowerBoundY + yAxis.getHeight();
// make sure the rectangle's end point is in the interval defined by the lower and upper bounds for each
// dimension
double x = Math.max(lowerBoundX, Math.min(eventX, upperBoundX));
double y = Math.max(lowerBoundY, Math.min(eventY, upperBoundY));
return new Point2D(x, y);
}
/**
* Computes the pixel offset of the given node inside the chart node.
*
* #param node the node for which to compute the pixel offset
* #param vertical flag that indicates whether the horizontal or the
* vertical dimension should be taken into account
* #return the offset inside the chart node
*/
private double computeOffsetInChart(Node node, boolean vertical) {
double offset = 0;
do {
if (vertical) {
offset += node.getLayoutY();
} else {
offset += node.getLayoutX();
}
node = node.getParent();
} while (node != chart);
return offset;
}
/**
*
*/
private final class MousePressedHandler implements EventHandler<MouseEvent> {
#Override
public void handle(final MouseEvent event) {
// do nothing for a right-click
if (event.isSecondaryButtonDown()) {
return;
}
// store position of initial click
selectionRectangleStart = computeRectanglePoint(event.getX(), event.getY());
event.consume();
}
}
/**
*
*/
private final class MouseDraggedHandler implements EventHandler<MouseEvent> {
#Override
public void handle(final MouseEvent event) {
// do nothing for a right-click
if (event.isSecondaryButtonDown()) {
return;
}
// store current cursor position
selectionRectangleEnd = computeRectanglePoint(event.getX(), event.getY());
double x = Math.min(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
double y = Math.min(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
double width = Math.abs(selectionRectangleStart.getX() - selectionRectangleEnd.getX());
double height = Math.abs(selectionRectangleStart.getY() - selectionRectangleEnd.getY());
drawSelectionRectangle(x, y, width, height);
event.consume();
}
/**
* Draws a selection box in the view.
*
* #param x the x position of the selection box
* #param y the y position of the selection box
* #param width the width of the selection box
* #param height the height of the selection box
*/
private void drawSelectionRectangle(final double x, final double y, final double width, final double height) {
selectionRectangle.setVisible(true);
selectionRectangle.setX(x);
selectionRectangle.setY(y);
selectionRectangle.setWidth(width);
selectionRectangle.setHeight(height);
//selectionRectangle.setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
//System.out.println("Draw the rectangle ...");
}
}
/**
*
*/
private final class MouseReleasedHandler implements EventHandler<MouseEvent> {
/**
* Defines a minimum width for the selected area. If the selected
* rectangle is not wider than this value, no zooming will take place.
* This helps prevent accidental zooming.
*/
private static final double MIN_RECTANGE_WIDTH = 10;
/**
* Defines a minimum height for the selected area. If the selected
* rectangle is not wider than this value, no zooming will take place.
* This helps prevent accidental zooming.
*/
private static final double MIN_RECTANGLE_HEIGHT = 10;
#Override
public void handle(final MouseEvent event) {
hideSelectionRectangle();
if (selectionRectangleStart == null || selectionRectangleEnd == null) {
return;
}
if (isRectangleSizeTooSmall()) {
return;
}
setAxisBounds();
showInfo();
selectionRectangleStart = null;
selectionRectangleEnd = null;
// needed for the key event handler to receive events
pane.requestFocus();
event.consume();
}
private boolean isRectangleSizeTooSmall() {
double width = Math.abs(selectionRectangleEnd.getX() - selectionRectangleStart.getX());
double height = Math.abs(selectionRectangleEnd.getY() - selectionRectangleStart.getY());
return width < MIN_RECTANGE_WIDTH || height < MIN_RECTANGLE_HEIGHT;
}
/**
* Hides the selection rectangle.
*/
private void hideSelectionRectangle() {
selectionRectangle.setVisible(false);
}
private void setAxisBounds() {
disableAutoRanging();
// compute new bounds for the chart's x and y axes
double selectionMinX = Math.min(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
double selectionMaxX = Math.max(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
double selectionMinY = Math.min(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
double selectionMaxY = Math.max(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
setHorizontalBounds(selectionMinX, selectionMaxX);
setVerticalBounds(selectionMinY, selectionMaxY);
}
private void disableAutoRanging() {
xAxis.setAutoRanging(false);
yAxis.setAutoRanging(false);
}
private void showInfo() {
infoLabel.setVisible(true);
}
/**
* Sets new bounds for the chart's x axis.
*
* #param minPixelPosition the x position of the selection rectangle's
* left edge (in pixels)
* #param maxPixelPosition the x position of the selection rectangle's
* right edge (in pixels)
*/
private void setHorizontalBounds(double minPixelPosition, double maxPixelPosition) {
double currentLowerBound = xAxis.getLowerBound();
double currentUpperBound = xAxis.getUpperBound();
double offset = computeOffsetInChart(xAxis, false);
setLowerBoundX(minPixelPosition, currentLowerBound, currentUpperBound, offset);
setUpperBoundX(maxPixelPosition, currentLowerBound, currentUpperBound, offset);
}
/**
* Sets new bounds for the chart's y axis.
*
* #param minPixelPosition the y position of the selection rectangle's
* upper edge (in pixels)
* #param maxPixelPosition the y position of the selection rectangle's
* lower edge (in pixels)
*/
private void setVerticalBounds(double minPixelPosition, double maxPixelPosition) {
double currentLowerBound = yAxis.getLowerBound();
double currentUpperBound = yAxis.getUpperBound();
double offset = computeOffsetInChart(yAxis, true);
setLowerBoundY(maxPixelPosition, currentLowerBound, currentUpperBound, offset);
setUpperBoundY(minPixelPosition, currentLowerBound, currentUpperBound, offset);
}
private void setLowerBoundX(double pixelPosition, double currentLowerBound, double currentUpperBound,
double offset) {
double newLowerBound = computeBound(pixelPosition, offset, xAxis.getWidth(), currentLowerBound,
currentUpperBound, false);
xAxis.setLowerBound(newLowerBound);
}
private void setUpperBoundX(double pixelPosition, double currentLowerBound, double currentUpperBound,
double offset) {
double newUpperBound = computeBound(pixelPosition, offset, xAxis.getWidth(), currentLowerBound,
currentUpperBound, false);
xAxis.setUpperBound(newUpperBound);
}
private void setLowerBoundY(double pixelPosition, double currentLowerBound, double currentUpperBound,
double offset) {
double newLowerBound = computeBound(pixelPosition, offset, yAxis.getHeight(), currentLowerBound,
currentUpperBound, true);
yAxis.setLowerBound(newLowerBound);
}
private void setUpperBoundY(double pixelPosition, double currentLowerBound, double currentUpperBound,
double offset) {
double newUpperBound = computeBound(pixelPosition, offset, yAxis.getHeight(), currentLowerBound,
currentUpperBound, true);
yAxis.setUpperBound(newUpperBound);
}
private double computeBound(double pixelPosition, double pixelOffset, double pixelLength, double lowerBound,
double upperBound, boolean axisInverted) {
double pixelPositionWithoutOffset = pixelPosition - pixelOffset;
double relativePosition = pixelPositionWithoutOffset / pixelLength;
double axisLength = upperBound - lowerBound;
// The screen's y axis grows from top to bottom, whereas the chart's y axis goes from bottom to top.
// That's
// why we need to have this distinction here.
double offset = 0;
int sign = 0;
if (axisInverted) {
offset = upperBound;
sign = -1;
} else {
offset = lowerBound;
sign = 1;
}
double newBound = offset + sign * relativePosition * axisLength;
return newBound;
}
}
/**
*
*/
private final class EscapeKeyHandler implements EventHandler<KeyEvent> {
#Override
public void handle(KeyEvent event) {
// the ESCAPE key lets the user reset the zoom level
if (KeyCode.ESCAPE.equals(event.getCode())) {
resetAxisBounds();
hideInfo();
}
}
private void resetAxisBounds() {
xAxis.setAutoRanging(true);
yAxis.setAutoRanging(true);
}
private void hideInfo() {
infoLabel.setVisible(false);
}
}
}
//SelectionRectangle.java class
package testlinechartgraphs;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author **************
*/
import javafx.scene.shape.Rectangle;
/**
* Represents an area on the screen that was selected by a mouse drag operation.
*
*/
public class SelectionRectangle extends Rectangle {
private static final String STYLE_CLASS_SELECTION_BOX = "chart-selection-rectangle";
public SelectionRectangle() {
getStyleClass().addAll(STYLE_CLASS_SELECTION_BOX);
setVisible(false);
setManaged(false);
setMouseTransparent(true);
}
}
// fancychart.css file
.chart-line-symbol {
-fx-scale-x: 0.5;
-fx-scale-y: 0.5;
}
.chart-popup-label {
-fx-padding: 1 3 1 3;
-fx-border-radius: 1;
-fx-border-width: 1;
-fx-opacity: 0.7;
-fx-effect: dropshadow( two-pass-box , rgba(0,0,0,0.3) , 8, 0.0 , 0 , 3 );
}
.chart-legend-item {
-fx-padding : 1 23 1 23;
}
.chart-legend-item-symbol {
-fx-scale-x: 0.8;
-fx-scale-y: 0.8;
}
.chart-selection-rectangle {
-fx-stroke: rgba(135, 206, 250, 0.8);
-fx-stroke-type: inside;
-fx-fill: rgba(135, 206, 250, 0.2);
}
#zoomInfoLabel {
-fx-background-color: rgba(135, 206, 250, 0.8);
-fx-font-size: 14;
-fx-padding: 3;
-fx-background-radius: 2;
}
Any help will be greatly appreciated to resolve this issue.
thanks,
Your order is wrong. You have
final StackPane chartContainer = new StackPane();
Zoom zoom = new Zoom(lineChart, chartContainer);
chartContainer.getChildren().add(lineChart);
Which means you first create the container, then add the zoom rectangle, then add the chart. So the zoom rectangle is on the back of the chart.
You need to have it this way:
final StackPane chartContainer = new StackPane();
chartContainer.getChildren().add(lineChart);
Zoom zoom = new Zoom(lineChart, chartContainer);
How can I use lineargradient for Line in Javafx?
I tried this:
LinearGradient lg = new LinearGradient(...);
line.setFill(lg);
....
It doesn't work.
Based on this answer, you need to specify the gradient on absolute coordinates.
Something like this will work for any given ones:
LinearGradient linearGradient = new LinearGradient(x1, y1, x2, y2, false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
Based on this answer, you can create a simple app to test it how it works when you move the anchor nodes around the scene:
private final DoubleProperty x1 = new SimpleDoubleProperty();
public final double getX1() { return x1.get(); }
public final void setX1(double value) { x1.set(value); }
public final DoubleProperty x1Property() { return x1; }
private final DoubleProperty x2 = new SimpleDoubleProperty();
public final double getX2() { return x2.get(); }
public final void setX2(double value) { x2.set(value); }
public final DoubleProperty x2Property() { return x2; }
private final DoubleProperty y1 = new SimpleDoubleProperty();
public final double getY1() { return y1.get(); }
public final void setY1(double value) { y1.set(value); }
public final DoubleProperty y1Property() { return y1; }
private final DoubleProperty y2 = new SimpleDoubleProperty();
public final double getY2() { return y2.get(); }
public final void setY2(double value) { y2.set(value); }
public final DoubleProperty y2Property() { return y2; }
private Line line;
#Override
public void start(Stage primaryStage) {
Group group = new Group();
primaryStage.setScene(new Scene(group, 400, 400));
x1.set(100);
y1.set(50);
x2.set(200);
y2.set(300);
line = new Line(x1.get(), y1.get(), x2.get(), y2.get());
line.startXProperty().bind(x1);
line.startYProperty().bind(y1);
line.endXProperty().bind(x2);
line.endYProperty().bind(y2);
line.setStrokeWidth(12);
line.setMouseTransparent(true);
Anchor start = new Anchor(Color.BLUE, x1, y1);
Anchor end = new Anchor(Color.YELLOW, x2, y2);
group.getChildren().setAll(line, start, end);
x1Property().addListener(o -> updateLine());
x2Property().addListener(o -> updateLine());
y1Property().addListener(o -> updateLine());
y2Property().addListener(o -> updateLine());
updateLine();
primaryStage.show();
}
private void updateLine() {
LinearGradient linearGradient = new LinearGradient(x1.get(), y1.get(), x2.get(), y2.get(), false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
}
private class Anchor extends Circle {
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(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(mouseEvent -> {
getScene().setCursor(Cursor.HAND);
});
setOnMouseDragged(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(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
});
}
// records relative x and y co-ordinates.
private class Delta { double x, y; }
}
Use this
line.setStyle("-fx-background-color: linear-gradient(to right, #color_1, #color_2);");