JavaFX 2 circle path for animation - javafx-2

How can I create a circle (or ellipse) javafx.scene.shape.Path in JavaFX 2?
I've found some examples using CubicCurveTo:
Path path = new Path();
path.getElements().add(new CubicCurveTo(30, 10, 380, 120, 200, 120));
but I don't understand that Bézier coordinates. I need a full circle path for animations.

You can utilize the ArcTo path element to draw circle or ellipse path:
public class ArcToDemo extends Application {
private PathTransition pathTransitionEllipse;
private PathTransition pathTransitionCircle;
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 600, 460));
// Ellipse path example
Rectangle rect = new Rectangle(0, 0, 40, 40);
rect.setArcHeight(10);
rect.setArcWidth(10);
rect.setFill(Color.ORANGE);
root.getChildren().add(rect);
Path path = createEllipsePath(200, 200, 50, 100, 45);
root.getChildren().add(path);
pathTransitionEllipse = PathTransitionBuilder.create()
.duration(Duration.seconds(4))
.path(path)
.node(rect)
.orientation(OrientationType.ORTHOGONAL_TO_TANGENT)
.cycleCount(Timeline.INDEFINITE)
.autoReverse(false)
.build();
// Cirle path example
Rectangle rect2 = new Rectangle(0, 0, 20, 20);
rect2.setArcHeight(10);
rect2.setArcWidth(10);
rect2.setFill(Color.GREEN);
root.getChildren().add(rect2);
Path path2 = createEllipsePath(400, 200, 150, 150, 0);
root.getChildren().add(path2);
pathTransitionCircle = PathTransitionBuilder.create()
.duration(Duration.seconds(2))
.path(path2)
.node(rect2)
.orientation(OrientationType.ORTHOGONAL_TO_TANGENT)
.cycleCount(Timeline.INDEFINITE)
.autoReverse(false)
.build();
}
private Path createEllipsePath(double centerX, double centerY, double radiusX, double radiusY, double rotate) {
ArcTo arcTo = new ArcTo();
arcTo.setX(centerX - radiusX + 1); // to simulate a full 360 degree celcius circle.
arcTo.setY(centerY - radiusY);
arcTo.setSweepFlag(false);
arcTo.setLargeArcFlag(true);
arcTo.setRadiusX(radiusX);
arcTo.setRadiusY(radiusY);
arcTo.setXAxisRotation(rotate);
Path path = PathBuilder.create()
.elements(
new MoveTo(centerX - radiusX, centerY - radiusY),
arcTo,
new ClosePath()) // close 1 px gap.
.build();
path.setStroke(Color.DODGERBLUE);
path.getStrokeDashArray().setAll(5d, 5d);
return path;
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
pathTransitionEllipse.play();
pathTransitionCircle.play();
}
public static void main(String[] args) {
launch(args);
}
}
Good reference of the features of ArcTo is ArcTo (JavaFX 8). Allthough it is version 8, the meanings of the features are similar.
Output:

This is an updated version of #Uluk Biy's answer.
import javafx.animation.PathTransition;
import javafx.animation.PathTransition.OrientationType;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PathTDemo extends Application
{
private PathTransition pathTransitionEllipse;
private PathTransition pathTransitionCircle;
private void init(Stage primaryStage)
{
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 600, 460));
// Ellipse path example
Rectangle rect = new Rectangle(0, 0, 40, 40);
rect.setArcHeight(10);
rect.setArcWidth(10);
rect.setFill(Color.ORANGE);
root.getChildren().add(rect);
Path path = createEllipsePath(200, 200, 50, 100, 45);
root.getChildren().add(path);
pathTransitionEllipse = new PathTransition();
pathTransitionEllipse.setDuration(Duration.seconds(4));
pathTransitionEllipse.setPath(path);
pathTransitionEllipse.setNode(rect);
pathTransitionEllipse.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransitionEllipse.setCycleCount(Timeline.INDEFINITE);
pathTransitionEllipse.setAutoReverse(false);
// Cirle path example
Rectangle rect2 = new Rectangle(0, 0, 20, 20);
rect2.setArcHeight(10);
rect2.setArcWidth(10);
rect2.setFill(Color.GREEN);
root.getChildren().add(rect2);
Path path2 = createEllipsePath(400, 200, 150, 150, 0);
root.getChildren().add(path2);
pathTransitionCircle = new PathTransition();
pathTransitionCircle.setDuration(Duration.seconds(2));
pathTransitionCircle.setPath(path2);
pathTransitionCircle.setNode(rect2);
pathTransitionCircle.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransitionCircle.setCycleCount(Timeline.INDEFINITE);
pathTransitionCircle.setAutoReverse(false);
}
private Path createEllipsePath(double centerX, double centerY, double radiusX, double radiusY, double rotate)
{
ArcTo arcTo = new ArcTo();
arcTo.setX(centerX - radiusX + 1); // to simulate a full 360 degree celcius circle.
arcTo.setY(centerY - radiusY);
arcTo.setSweepFlag(false);
arcTo.setLargeArcFlag(true);
arcTo.setRadiusX(radiusX);
arcTo.setRadiusY(radiusY);
arcTo.setXAxisRotation(rotate);
Path path = new Path();
path.getElements().addAll(
new MoveTo(centerX - radiusX, centerY - radiusY),
arcTo,
new ClosePath()); // close 1 px gap.
path.setStroke(Color.DODGERBLUE);
path.getStrokeDashArray().setAll(5d, 5d);
return path;
}
#Override
public void start(Stage primaryStage) throws Exception
{
init(primaryStage);
primaryStage.show();
pathTransitionEllipse.play();
pathTransitionCircle.play();
}
public static void main(String[] args)
{
launch(args);
}
}

I solved same problem by animation of rotateProperty of container. Just two lines for creating animation.
animationTimeLine = new Timeline(60, new KeyFrame(Duration.seconds(5), new KeyValue(circlePane.rotateProperty(), 360.0)));
animationTimeLine.setCycleCount(INDEFINITE);

Related

JavaFX reset graphics Context after scaling a canvas

I'm trying to make a chart without using any 3rd party libs. I've a zoom feature which scales the Canvas correctly, but now I need to redraw everything inside the canvas once again.
But when I do scaling the GrapphicsContext also scales and blots. I want to readjust the blotting and show points in normal drawing once zoomed. How can I achieve this?
Here is simple snippet that I'm redrawing:
private void redrawImage(Canvas canvas, int scale) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.scale(scale, scale);
gc.setStroke(Color.RED);
gc.setLineWidth(2);
gc.strokeRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.setStroke(Color.GREEN);
gc.strokeLine(0, 0, canvas.getWidth(), canvas.getHeight());
gc.strokeLine(0, canvas.getHeight(), canvas.getWidth(), 0);
gc.setStroke(Color.BLUE);
gc.strokeText("TEXT", 50, 50);
}
Even I remove gc.scale(X,Y) I still see the blotted points or text, I want the scale to be always 1, but I should also zoom or scale simultaneously.
What I want to achieve is like the GoogleMaps overlaying, you see the objects when zoomed in or out are recalibrated and adjusted to a viewable scale. This is exactly what I want to achieve.
I don't know if I understand you correct.
Your redrawImage method should get an double and not an int value for the scale. Even if you try to scale to 1.9 the cast from double to int will bring it down to 1.
I've made a little example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(100, 100);
Canvas canvas2 = new Canvas(100, 100);
FlowPane root = new FlowPane();
root.getChildren().addAll(canvas, canvas2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
redrawImage(canvas, 0.5);
redrawImage(canvas2, 1);
}
private void redrawImage(Canvas canvas, double scale) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.scale(scale, scale);
gc.setStroke(Color.RED);
gc.setLineWidth(2);
gc.strokeRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.setStroke(Color.GREEN);
gc.strokeLine(0, 0, canvas.getWidth(), canvas.getHeight());
gc.strokeLine(0, canvas.getHeight(), canvas.getWidth(), 0);
gc.setStroke(Color.BLUE);
gc.strokeText("TEXT", 50, 50);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
With the following result:

JavaFX - Create semi circle with start and end angle

How to create semi circle with adjustable start and end angle in JavaFX. I tried to use Arc or ArcTo but it never gives me what I need. Is there any easy solution to create it?
How it should look like:
A quick solution would be to create an outer circle, an inner circle, 2 lines and use Shape.subtract to create a new shape:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
double dist = 10;
double outerRadius = 100;
double innerRadius = 50;
Circle outerCircle = new Circle();
outerCircle.setRadius(outerRadius);
Circle innerCircle = new Circle();
innerCircle.setRadius(innerRadius);
Line topLeftBottomRightLine = new Line(-outerRadius, -outerRadius, outerRadius, outerRadius);
topLeftBottomRightLine.setStrokeWidth(dist);
Line bottomLeftTopRightLine = new Line(-outerRadius, outerRadius, outerRadius, -outerRadius);
bottomLeftTopRightLine.setStrokeWidth(dist);
Shape shape = Shape.subtract(outerCircle, innerCircle);
shape = Shape.subtract(shape, topLeftBottomRightLine);
shape = Shape.subtract(shape, bottomLeftTopRightLine);
shape.setStroke(Color.BLUE);
shape.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
shape.relocate(300, 100);
root.getChildren().addAll(shape);
Scene scene = new Scene(root, 800, 400);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Similarly you can create only parts of the shape by using an arc and subtracting an inner circle from it.

I am trying to rotate a triangle

I have a circle, and then a triangle in the middle of the circle. I want to rotate the triangle around the center of the triangle. Kind of like rotating it 360 degrees. This is my code so far
public void paintComponent(Graphics g)
{
g.setColor(Color.BLACK);
g.drawArc(120, 120, 100, 100, 0, 360);
g.setColor(Color.RED);
int[] x = {160, 170, 180};
int[] y = {150, 190, 150};
g.drawPolygon(x, y, 3);
}
public static void main (String[] args)
{
JFrame frame = new JFrame("SpinningCircle");
frame.setSize(400, 400);
frame.setLocation(0, 0);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new SpinningCircle());
frame.setVisible(true);
}
`
Thanks for help.

JavaFX 2.2 MouseEvent in SubScene does not work properly

I am trying to get this to work for sometime and I cannot figure out what wrong with my code. This leads me to believe there is some issues in SubScene Mouse listener. Any idea is appreciated.
Basically I have a scene contains two subscenes, one for the toolbar and one for the floor which has bunch of lines making it looks like tiles. I added mouse listeners so that when I clicked on the floor and move the mouse, the camera will move as if I am walking on the floor.
The problem is that the floor only recognize mouse event when I clicked on the intersection between the first vertical and the first horizontal line (yup, took me a while to figure that out). Mouse event should occur everywhere on entire floor.
Here is the code.
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class FloorTest extends Application {
double mousex, mousey;
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
Group bargroup = new Group();
SubScene bar = new SubScene(bargroup, 300, 20, true, SceneAntialiasing.DISABLED);
bargroup.getChildren().add(btn);
Group floorgroup = new Group();
SubScene floor = new SubScene(floorgroup, 300, 250, true, SceneAntialiasing.DISABLED);
ObservableList<Node> list = floorgroup.getChildren();
for(int i = 0; i < (300/20); i++)
{
double x = i * 20;
Line line = new Line(x, 0, x, 250);
list.add(line);
}
for(int i = 0; i < (250/20); i++)
{
double y = i * 20;
Line line = new Line(0, y, 300, y);
list.add(line);
}
PerspectiveCamera camera = new PerspectiveCamera(false);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-200);
floor.setCamera(camera);
floor.setOnMousePressed((MouseEvent event) -> {
mousex = event.getSceneX();
mousey = event.getSceneY();
});
floor.setOnMouseDragged((MouseEvent event) -> {
double x = event.getSceneX();
double y = event.getSceneY();
camera.relocate(camera.getLayoutX() + (x - mousex), camera.getLayoutY() + (y - mousey));
});
Group mainroot = new Group();
mainroot.getChildren().addAll(floor, bar);
Scene scene = new Scene(mainroot, 300, 250, true);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support. NetBeans ignores main().
*
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
It seems that setting the subscene.setPickOnBounds(true) should help in proper recognition of the mouse events for the whole subscene. Tested with javafx 8.
Please try using scene instead of floor variable in event handlers
Example:
scene.setOnMousePressed((MouseEvent event) -> {
mousex = event.getSceneX();
mousey = event.getSceneY();
});
scene.setOnMouseDragged((MouseEvent event) -> {
double x = event.getSceneX();
double y = event.getSceneY();
camera.relocate(camera.getLayoutX() + (x - mousex), camera.getLayoutY() + (y - mousey));
});
That helps me

How to make rotated text look good with Java2D

My question is not about how to rotate text with Java2D; I know how to do that. What I don't know is how to make the rotated text "look good." For example, if you create a text box in PowerPoint and rotate it, the text appears sharp and clear no matter the rotation angle. However, text drawn with g2D.drawString() looks okay at 0 or 90 degrees but not so good at other angles. Is there a way to manipulate the text to clean or sharpen it up? If so, then if someone could point me to where look to learn how to do this I would be so thankful.
Below is a little program that illustrates what I'm talking about. The bigger font isn't too bad when rotated but still doesn't look very professional. The smaller font when rotated is terrible.
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class RotateTest extends JPanel {
String message = "How does this text look?";
public RotateTest() {
this.setPreferredSize(new Dimension(640, 280));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2D.setFont(new Font("MyriadPro", Font.BOLD, 20));
g2D.drawString(message, 80, 20);
AffineTransform orig = g2D.getTransform();
double angle = Math.toRadians(7.0);
g2D.rotate(-angle, -10, 80);
g2D.drawString(message, 80, 80);
g2D.setTransform(orig);
angle = Math.toRadians(30.0);
g2D.rotate(-angle, -40, 80);
g2D.drawString(message, 60, 260);
g2D.setTransform(orig);
g2D.setFont(new Font("MyriadPro", Font.BOLD, 12));
g2D.drawString(message, 380, 20);
angle = Math.toRadians(7.0);
g2D.rotate(-angle, -10, 80);
g2D.drawString(message, 380, 120);
g2D.setTransform(orig);
angle = Math.toRadians(30.0);
g2D.rotate(-angle, -40, 80);
g2D.drawString(message, 320, 400);
g2D.setTransform(orig);
}
private void display() {
JFrame f = new JFrame("RotateTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new RotateTest().display();
}
});
}
}
I once had a similar problem, and solved it by drawing the text with high precision to an image, then drawing the rotated image.
Here's the code:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class RotatedText extends JPanel {
String message = "How does this text look?";
public RotatedText() {
this.setPreferredSize(new Dimension(640, 280));
}
public BufferedImage createStringImage(Graphics g, String s) {
int w = g.getFontMetrics().stringWidth(s) + 5;
int h = g.getFontMetrics().getHeight();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D imageGraphics = image.createGraphics();
imageGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
imageGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
imageGraphics.setColor(Color.BLACK);
imageGraphics.setFont(g.getFont());
imageGraphics.drawString(s, 0, h - g.getFontMetrics().getDescent());
imageGraphics.dispose();
return image;
}
private void drawString(Graphics g, String s, int tx, int ty, double theta, double rotx, double roty) {
AffineTransform aff = AffineTransform.getRotateInstance(theta, rotx, roty);
aff.translate(tx, ty);
Graphics2D g2D = ((Graphics2D) g);
g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2D.drawImage(createStringImage(g, s), aff, this);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("MyriadPro", Font.BOLD, 20));
drawString(g, message, 80, 20, 0, 0, 0);
drawString(g, message, 80, 80, -Math.toRadians(7.0), -10, 80);
drawString(g, message, 60, 260, -Math.toRadians(30.0), -40, 80);
g.setFont(new Font("MyriadPro", Font.BOLD, 12));
drawString(g, message, 380, 20, 0, 0, 0);
drawString(g, message, 380, 120, -Math.toRadians(7.0), -10, 80);
drawString(g, message, 320, 400, -Math.toRadians(30.0), -40, 80);
}
private void display() {
JFrame f = new JFrame("RotateTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new RotatedText().display();
}
});
}
}
I haven't got the time to test this but will the following code help?:
Graphics2D g2d;
g2d.setRenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
This will only work on Graphics2D. If you're using normal Graphics you can cast your Graphics object to the 2D version like so:
Graphics2D g2d = (Graphics2D) g; //if Graphics object name is g.
Let me know!
Good luck

Resources