Pass mouse click event through a shape - java

I'm working on a project where i need
to make circles and relate between them with cubicCurve
as shown in this image:
And i need to pass mouseclick events through the part of the cubicCurve colored with "ALICEBLUE", but still being able to pass mouseclick events to the Line colored with "BLACK".
cubicCurve Code:
public class transLine extends CubicCurve {
private Polygon polygon;
private Circle to;
private Circle from;
private double arrowHeadTransLength;
public Circle getTo() {
return to;
}
public void setTo(Circle to) {
this.to = to;
}
public Circle getFrom() {
return from;
}
public void setFrom(Circle from) {
this.from = from;
}
public transLine() {
setStroke(Color.BLACK);
setFill(Color.ALICEBLUE);
// doesn't work because i can't pass click event to the black line
setMouseTransparent(true);
// just arrow head part
polygon = new Polygon();
fixPolygon();
}
public void fixPolygon(){
Point2D A = new Point2D(getEndX(),getEndY());
Point2D Ap = new Point2D(getControlX2(),getControlY2());
Point2D Ms = getMSecond(A,Ap,arrowHeadTransLength);
Point2D P1 = getRotatePoint(Ms, A, Math.PI/7);
Point2D P2 = getRotatePoint(Ms, A, -Math.PI/7);
getPolygon().getPoints().setAll(new Double[]
{A.getX(),A.getY(),P1.getX(),P1.getY(),P2.getX(),P2.getY()});
}
public Point2D getMSecond(Point2D A, Point2D B, double r){
if( A.getX()==B.getX() && A.getY()==B.getY() ){
return B;
}
double a = (A.getY()-B.getY())/(A.getX()-B.getX());
double b = A.getY()-a*A.getX();
double xs = -r*(A.getX()-B.getX())/A.distance(B)+A.getX();
double ys = a*xs+b;
return new Point2D(xs,ys);
}
public Point2D getRotatePoint(Point2D P, Point2D O, double theta){
double rx = Math.cos(theta)*(P.getX()-O.getX()) - Math.sin(theta)*
(P.getY()-O.getY()) + O.getX();
double ry = Math.sin(theta)*(P.getX()-O.getX()) + Math.cos(theta)*
(P.getY()-O.getY()) + O.getY();
return new Point2D(rx,ry);
}
}

I can't see a particularly easy way to do this. I think that in order to have different mouse behaviors for the fill and the curve, you need those to be different nodes. So the idea is to create the curve and the fill as separate pieces, place them in a Group, and then use the Group in the display. Then make the piece representing the fill mouse-transparent.
Actually implementing that is a bit tricky; particularly getting a cubic curve that doesn't respond to mouse clicks in its "interior". The only way I could find to do this was to use a Path comprising of a MoveTo, a CubicCurveTo, and then another CubicCurveTo that retraced the reverse path (this ensures the interior is essentially empty). Then, as suggested, place that in a group along with a regular CubicCurve representing the filled portion.
SSCCE:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class ConnectingCubicCurve extends Application {
#Override
public void start(Stage primaryStage) {
Circle start = createDraggingCircle(100, 100, 10, Color.CORAL);
Circle end = createDraggingCircle(300, 300, 10, Color.CORAL);
Connection connection = new Connection();
connection.setFromCircle(start);
connection.setToCircle(end);
Pane pane = new Pane(connection.asNode(), start, end);
pane.setOnMouseClicked(e -> System.out.println("Click on pane"));
connection.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("Click on connection"));
start.setOnMouseClicked(e -> System.out.println("Click on start"));
end.setOnMouseClicked(e -> System.out.println("Click on end"));
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createDraggingCircle(double centerX, double centerY, double radius, Paint fill) {
Circle circle = new Circle(centerX, centerY, radius, fill);
ObjectProperty<Point2D> mouseLoc = new SimpleObjectProperty<>();
circle.setOnDragDetected(e ->
mouseLoc.set(new Point2D(e.getSceneX(), e.getSceneY())));
circle.setOnMouseReleased(e -> mouseLoc.set(null));
circle.setOnMouseDragged(e -> {
if (mouseLoc.get() == null) return ;
double x = e.getSceneX() ;
double y = e.getSceneY() ;
double deltaX = x - mouseLoc.get().getX() ;
double deltaY = y - mouseLoc.get().getY() ;
circle.setCenterX(circle.getCenterX() + deltaX);
circle.setCenterY(circle.getCenterY() + deltaY);
mouseLoc.set(new Point2D(x, y));
});
return circle ;
}
public static class Connection {
private Path connectingLine ;
private CubicCurve fill ;
private Group group ;
private ObjectProperty<Circle> fromCircle = new SimpleObjectProperty<>();
private ObjectProperty<Circle> toCircle = new SimpleObjectProperty<>();
public Connection() {
connectingLine = new Path();
MoveTo start = new MoveTo();
CubicCurveTo curve = new CubicCurveTo();
CubicCurveTo reverseCurve = new CubicCurveTo();
reverseCurve.xProperty().bind(start.xProperty());
reverseCurve.yProperty().bind(start.yProperty());
reverseCurve.controlX1Property().bind(curve.controlX2Property());
reverseCurve.controlX2Property().bind(curve.controlX1Property());
reverseCurve.controlY1Property().bind(curve.controlY2Property());
reverseCurve.controlY2Property().bind(curve.controlY1Property());
connectingLine.getElements().addAll(start, curve, reverseCurve);
fill = new CubicCurve();
fill.setMouseTransparent(true);
group = new Group();
group.getChildren().addAll(fill, connectingLine);
connectingLine.setStroke(Color.BLACK);
connectingLine.setStrokeWidth(3);
fill.setStrokeWidth(0);
fill.setStroke(Color.TRANSPARENT);
fill.setFill(Color.ALICEBLUE);
fill.startXProperty().bind(start.xProperty());
fill.startYProperty().bind(start.yProperty());
fill.controlX1Property().bind(curve.controlX1Property());
fill.controlX2Property().bind(curve.controlX2Property());
fill.controlY1Property().bind(curve.controlY1Property());
fill.controlY2Property().bind(curve.controlY2Property());
fill.endXProperty().bind(curve.xProperty());
fill.endYProperty().bind(curve.yProperty());
fromCircle.addListener((obs, oldCircle, newCircle) -> {
if (oldCircle != null) {
start.xProperty().unbind();
start.yProperty().unbind();
}
if (newCircle != null) {
start.xProperty().bind(newCircle.centerXProperty());
start.yProperty().bind(newCircle.centerYProperty());
}
});
toCircle.addListener((obs, oldCircle, newCircle) -> {
if (oldCircle != null) {
curve.xProperty().unbind();
curve.yProperty().unbind();
}
if (newCircle != null) {
curve.xProperty().bind(newCircle.centerXProperty());
curve.yProperty().bind(newCircle.centerYProperty());
}
});
ChangeListener<Number> endpointListener = (obs, oldValue, newValue) -> {
Point2D startPoint = new Point2D(start.getX(), start.getY());
Point2D end = new Point2D(curve.getX(), curve.getY());
Point2D vector = end.subtract(startPoint);
Point2D perpVector = new Point2D(-vector.getY(), vector.getX());
Point2D control1 = startPoint.add(perpVector);
Point2D control2 = end.add(perpVector);
curve.setControlX1(control1.getX());
curve.setControlX2(control2.getX());
curve.setControlY1(control1.getY());
curve.setControlY2(control2.getY());
};
start.xProperty().addListener(endpointListener);
start.yProperty().addListener(endpointListener);
curve.xProperty().addListener(endpointListener);
curve.yProperty().addListener(endpointListener);
}
public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
connectingLine.addEventHandler(eventType, eventHandler);
}
public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
connectingLine.removeEventHandler(eventType, eventHandler);
}
public Node asNode() {
return group ;
}
public final ObjectProperty<Circle> fromCircleProperty() {
return this.fromCircle;
}
public final javafx.scene.shape.Circle getFromCircle() {
return this.fromCircleProperty().get();
}
public final void setFromCircle(final javafx.scene.shape.Circle fromCircle) {
this.fromCircleProperty().set(fromCircle);
}
public final ObjectProperty<Circle> toCircleProperty() {
return this.toCircle;
}
public final javafx.scene.shape.Circle getToCircle() {
return this.toCircleProperty().get();
}
public final void setToCircle(final javafx.scene.shape.Circle toCircle) {
this.toCircleProperty().set(toCircle);
}
}
public static void main(String[] args) {
launch(args);
}
}

Related

JavaFX: moving Rectangle with AnimationTimer

I am trying to code a very simple Pong-game, I think that is the name of this old game where a ball bounces between the player and a bunch of bricks with the ball going up and down. The player can move horizontally at the bottom.
So basically just some moving rectangles, eventually they are supposed to collide and so on.
However, I can allow the player to move the Rectangle with the buttons, but I can't manage to make objects move with the help of the AnimationTimer. (javafx.animation.AnimationTimer)
I already searched a lot but only found examples with mistakes that I managed to avoid.
(eg. like this AnimationTimer & JavaFX: Rectangle won't move horizontally using the setTranslateX() method. How to move Rectangle?).
I tried debugging it to see where the unexpected happens, but I couldn't find it. here is my code:
package bounceBall;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.control.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class BounceBallApp extends Application {
private Pane root = new Pane();
private Sprite player = new Sprite(400, 500, 40, 4, "player", Color.BLUE);
private boolean play;
private Parent createContent() {
this.play = false;
root.setPrefSize(800, 600);
root.getChildren().add(player);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
update();
}
};
timer.start();
for (int y = 0; y < 10; y++) {
for (int x = 0; x < 40; x++) {
Sprite s = new Sprite(20*x, y*8, 18, 5, "brick", Color.AQUA);
root.getChildren().add(s);
}
}
return root;
}
private List<Sprite> getSprites(){
List<Sprite> list = new ArrayList<Sprite>();
for (Node n : root.getChildren()) {
if (n instanceof Sprite) {
Sprite s = (Sprite)n;
list.add(s);
}
}
return list;
}
private void update() {
getSprites().forEach(b -> {
if (b.type.equals("bullet")) {
b.moveRight(b.getXspeed());
b.moveDown(b.getYspeed());
}
});
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(createContent());
scene.setOnKeyPressed(e -> {
//as soon as any key gets hit the game starts with the shoot method
if (play == false) {
shoot(player);
play = true;
}
switch (e.getCode()) {
case A:{
player.moveLeft(10);
break;
}
case D:{
player.moveRight(10);
break;
}
default:
break;
}
});
primaryStage.setScene(scene);
primaryStage.setTitle("Bounce Ball");
primaryStage.show();
}
/*this method should be called once at the beginning to initiate the ball
the ball appears but it doesn't move, instead remains still
if I try to make the player move with AnimationTimer it doesn't work either, it works just by pressing keys*/
private void shoot(Sprite who) {
Sprite s = new Sprite((int)who.getTranslateX(), (int)who.getTranslateY()-2, 4, 4, "bullet", Color.BLACK);
s.setYspeed(-10);
root.getChildren().add(s);
System.out.println(root.getChildren().size());
}
private static class Sprite extends Rectangle {
final String type;
private int Xspeed, Yspeed;
public Sprite(int x, int y, int w, int h, String type, Color color) {
super(w, h, color);
this.type = type;
setTranslateX(x);
setTranslateY(y);
Xspeed = 0;
Yspeed = 0;
}
public void moveLeft(int d) {
setTranslateX(getTranslateX() - d);
}
public void moveRight(int d) {
setTranslateX(getTranslateX() + d);
}
public void moveUp(int d) {
setTranslateX(getTranslateY() - d);
}
public void moveDown(int d) {
setTranslateX(getTranslateY() + d);
}
public int getXspeed() {
return Xspeed;
}
public int getYspeed() {
return Yspeed;
}
public void setXspeed(int xspeed) {
Xspeed = xspeed;
}
public void setYspeed(int yspeed) {
Yspeed = yspeed;
}
}
public static void main(String[] args) {
launch(args);
}
}
I expect the ball to go straight up at the beginning but it appears at the wrong position (ca. 50px right from where I expect).
Most importantly it doesn't move.
Thanks a lot in advance.

Define object position at runtime with javafx

I'm making an application with javaFX. The application consists of creating a kind of "graph" from the user.
The user through a button creates a node (is created by a circle with JAVAFX figure) and associates it to a variable, repeating the process, creating another node, and so on. Now I need to figure out how to define the node position inside a specially reserved space. Obviously, after creating nodes, through another button, the user creates the arcs (is created by a line connecting two nodes) associated with the nodes, thus defining a graph.
My problem is that I do not understand how to indicate the position of lines that will act as arcs in my graph.
Please help me.I'm not very experienced and I'm trying to tackle this problem.
First of all you need to tell us what is your "reserved space"? If it's a Canvas then you can draw shapes with Canva's GraphicsContext.
Canvas canvas = new Canvas(300, 250);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillOval(10, 60, 30, 30);
gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN);
Otherwise if you are working inside a pane with layout you need to know if the components are managed or not. For example inside a Pane or AnchorPane the node's automatic layout is disabled, thus you need to specify their layoutX and layoutY by yourself (+ node dimensions) like :
node.setLayoutX(12);
node.setLayoutY(222);
node.setPrefWidth(500);
node.setPrefHeight(500);
If you are using a pane like VBox which manage the layout of it's nodes you need to set the nodes inside the pane to be unmanaged in order for you to apply specific transformations. You can do that just by setting :
node.setManaged(false)
I will not recommend you to use Canvas cause handling the shapes will be very hard, for example if you need to remove something you probably have to clear everything and redraw only the visible shapes.
Well I had some time so here is a small example ( It might not be the optimal solution, but you can take is as a reference)
GraphTest
import java.util.ArrayList;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GraphTest extends Application {
private double orgSceneX, orgSceneY;
private double orgTranslateX, orgTranslateY;
private Group root = new Group();
#Override
public void start(Stage primaryStage) throws Exception {
GraphNode node1 = createNode("A", 100, 100, Color.RED);
GraphNode node2 = createNode("B", 300, 200, Color.GREEN);
GraphNode node3 = createNode("C", 80, 300, Color.PURPLE);
connectNodes(node1, node2, "C1");
connectNodes(node3, node1, "C2");
connectNodes(node3, node2, "C3");
root.getChildren().addAll(node1, node2, node3);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
private void connectNodes(GraphNode node1, GraphNode node2, String edgeText) {
Line edgeLine = new Line(node1.getCenterX(), node1.getCenterY(), node2.getCenterX(), node2.getCenterY());
Label edgeLabel = new Label(edgeText);
node1.addNeighbor(node2);
node2.addNeighbor(node1);
node1.addEdge(edgeLine, edgeLabel);
node2.addEdge(edgeLine, edgeLabel);
root.getChildren().addAll(edgeLine, edgeLabel);
}
private GraphNode createNode(String nodeName, double xPos, double yPos, Color color) {
GraphNode node = new GraphNode(nodeName, xPos, yPos, color);
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
return node;
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
GraphNode node = (GraphNode) t.getSource();
orgTranslateX = node.getTranslateX();
orgTranslateY = node.getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
GraphNode node = (GraphNode) t.getSource();
node.setTranslateX(newTranslateX);
node.setTranslateY(newTranslateY);
updateLocations(node);
}
};
private void updateLocations(GraphNode node) {
ArrayList<GraphNode> connectedNodes = node.getConnectedNodes();
ArrayList<Line> edgesList = node.getEdges();
for (int i = 0; i < connectedNodes.size(); i++) {
GraphNode neighbor = connectedNodes.get(i);
Line l = edgesList.get(i);
l.setStartX(node.getCenterX());
l.setStartY(node.getCenterY());
l.setEndX(neighbor.getCenterX());
l.setEndY(neighbor.getCenterY());
}
}
public static void main(String[] args) {
launch(args);
}
}
GraphNode
import java.util.ArrayList;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
public class GraphNode extends StackPane {
private Circle circle;
private Label text;
private ArrayList<GraphNode> connectedNodesList = new ArrayList<>();
private ArrayList<Line> edgesList = new ArrayList<>();
private ArrayList<Label> edgesLabelList = new ArrayList<>();
private double radius = 50.0;
public GraphNode(String name, double xPos, double yPos, Color color) {
circle = new Circle(radius, color);
text = new Label(name);
text.setTextFill(Color.WHITE);
setLayoutX(xPos);
setLayoutY(yPos);
getChildren().addAll(circle, text);
layout();
}
public void addNeighbor(GraphNode node) {
connectedNodesList.add(node);
}
public void addEdge(Line edgeLine, Label edgeLabel) {
edgesList.add(edgeLine);
edgesLabelList.add(edgeLabel);
// If user move the node we should translate the edge labels as well
// one way of doing that is by make a custom binding to the layoutXProperty as well
// as to layoutYProperty. We will listen for changes to the currentNode translate properties
// and for changes of our neighbor.
edgeLabel.layoutXProperty().bind(new DoubleBinding() {
{
bind(translateXProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateXProperty());
}
#Override
protected double computeValue() {
// We find the center of the line to translate the text
double width = edgeLine.getEndX() - edgeLine.getStartX();
return edgeLine.getStartX() + width / 2.0;
}
});
edgeLabel.layoutYProperty().bind(new DoubleBinding() {
{
bind(translateYProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateYProperty());
}
#Override
protected double computeValue() {
double width = edgeLine.getEndY() - edgeLine.getStartY();
return edgeLine.getStartY() + width / 2.0;
}
});
}
public ArrayList<GraphNode> getConnectedNodes() {
return connectedNodesList;
}
public ArrayList<Line> getEdges() {
return edgesList;
}
public double getX() {
return getLayoutX() + getTranslateX();
}
public double getY() {
return getLayoutY() + getTranslateY();
}
public double getCenterX() {
return getX() + radius;
}
public double getCenterY() {
return getY() + radius;
}
}
And the Output would look like that :
You can move the nodes with your mouse and you will see all the labels will follow the shapes locations ( Cicle , Lines )

Object instantiated through a another separate class is not appearing on panel

I need to create some circles on a panel and I want them to have some properties, which I plan to use in future like orientation, x and y position etc. For this, I created a general class Robot which extends circle. But, when I am creating an object of this class, it is not being created in the panel.
It works if I am not using a separate class and create object directly from Circle. But I need to maintain some variables with each object. Anybody knows what I am doing wrong in below code:
class Robot extends Circle
{
int radius;
double x;
double y;
int orientation;
Robot(int i)
{
radius = i;
orientation = getRandomIntCoordinates(360);
System.out.println("Inside the constructor");
}
private int getRandomIntCoordinates(int range)
{
Random randInt = new Random();
return randInt.nextInt(range);
}
}
//Inside Another class
private void loadPlayers(Pane playground)
{
robot = new Robot(25);
robot.relocate(200,200);
robot.setFill(Color.RED);
/*for(int i =0; i<100; i++)
{
particles.add(new Circle(7, Color.GREEN));
particles.get(i).relocate(getRandomCoordinates(720), getRandomCoordinates(520));
}*/
//playground.getChildren().addAll(particles);
playground.getChildren().add(robot);
}
Update: I have noticed that nothing gets added to the pane in case I am adding the object of class Robot, while it works flawlessly in case Circle's object is used. Is this a limitation with this inbuilt method? If yes, what can I do to overcome this?
A circle has x and y co-ordinates and a radius, you don't need to duplicate those values in your inherited Robot class. You do need to set the radius and position values of the circle appropriately (e.g. by calling the super constructor) or you won't see the circle.
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class BorgCollective extends Application {
private static final int PLAYGROUND_SIZE = 300;
private static final int NUM_ROBOTS = 500;
private static final int ROBOT_RADIUS = 10;
private final Random random = new Random(42);
class Robot extends Circle {
private static final int PATROL_LENGTH = 60;
private DoubleProperty orientation = new SimpleDoubleProperty();
public double getOrientation() {
return orientation.get();
}
public DoubleProperty orientationProperty() {
return orientation;
}
public void setOrientation(double orientation) {
this.orientation.set(orientation);
}
public Robot(int radius) {
super(radius);
orientation.set(
random.nextInt(360)
);
patrol();
}
private void patrol() {
Rotate rotate = new Rotate(orientation.get());
Point2D unitVector = rotate.transform(new Point2D(1, 0));
Point2D patrolVector = unitVector.multiply(PATROL_LENGTH);
TranslateTransition patrolAnimation = new TranslateTransition(
Duration.seconds(2),
this
);
patrolAnimation.setCycleCount(Animation.INDEFINITE);
patrolAnimation.setAutoReverse(true);
patrolAnimation.setFromX(0);
patrolAnimation.setFromY(0);
patrolAnimation.setByX(patrolVector.getX());
patrolAnimation.setByY(patrolVector.getY());
patrolAnimation.play();
}
public void relocateWithinPatrolRegionSize(int regionSize) {
int mx = ROBOT_RADIUS + Robot.PATROL_LENGTH;
int my = mx;
int dx = random.nextInt(regionSize - 2 * (ROBOT_RADIUS + PATROL_LENGTH));
int dy = random.nextInt(regionSize - 2 * (ROBOT_RADIUS + PATROL_LENGTH));
setCenterX(mx + dx);
setCenterY(my + dy);
}
}
#Override
public void start(Stage stage) {
Pane playground = new Pane();
playground.setPrefSize(
PLAYGROUND_SIZE,
PLAYGROUND_SIZE
);
IntStream
.range(0, NUM_ROBOTS)
.mapToObj(i ->
createRobot()
)
.collect(
Collectors.toCollection(
playground::getChildren
)
);
stage.setScene(new Scene(playground));
stage.show();
}
private Robot createRobot() {
Robot robot = new Robot(ROBOT_RADIUS);
robot.setFill(randomColor());
robot.relocateWithinPatrolRegionSize(PLAYGROUND_SIZE);
return robot;
}
private Color randomColor() {
return Color.rgb(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256)
);
}
public static void main(String[] args) {
launch(args);
}
}

Display 2d array as grid in JavaFX

for my Java coursework I have to create a grid based animation. Basically I currently have a 2d array containing certain values, which change each time the program is run. For example it could be a 20x20 2d array, or a 32x32 etc. Inside the array certain values are stored, chars represent animals, and numbers represent food. The animals smell the food and then move towards the food after each cycle of the program, hence their position in the array change after each cycle. How the program works isn't really relevant to the question I'm asking.
Basically I now have to implement this in JavaFX (it currently works in the console, displaying the array as a grid each cycle). I was just wondering which control would be best to use in JavaFX to display a 2d array, or if perhaps someone could point me in the right direction of how to start coding this?
I'm new to java (and JavaFX) so am not sure of which controls to use...
I wouldn't use a control. I'd rather create a node for each of the items in the array and put them on the scene. Something like this:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class NodeDemo extends Application {
private double sceneWidth = 1024;
private double sceneHeight = 768;
private int n = 10;
private int m = 10;
double gridWidth = sceneWidth / n;
double gridHeight = sceneHeight / m;
MyNode[][] playfield = new MyNode[n][m];
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// initialize playfield
for( int i=0; i < n; i++) {
for( int j=0; j < m; j++) {
// create node
MyNode node = new MyNode( "Item " + i + "/" + j, i * gridWidth, j * gridHeight, gridWidth, gridHeight);
// add node to group
root.getChildren().add( node);
// add to playfield for further reference using an array
playfield[i][j] = node;
}
}
Scene scene = new Scene( root, sceneWidth, sceneHeight);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class MyNode extends StackPane {
public MyNode( String name, double x, double y, double width, double height) {
// create rectangle
Rectangle rectangle = new Rectangle( width, height);
rectangle.setStroke(Color.BLACK);
rectangle.setFill(Color.LIGHTBLUE);
// create label
Label label = new Label( name);
// set position
setTranslateX( x);
setTranslateY( y);
getChildren().addAll( rectangle, label);
}
}
}
This way you can create animated movement of the nodes easily with a PathTransition. Like this shuffle mechanism:
import java.util.Random;
import javafx.animation.Animation.Status;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
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 NodeDemo extends Application {
private double sceneWidth = 1024;
private double sceneHeight = 768;
private int n = 10;
private int m = 10;
double gridWidth = sceneWidth / n;
double gridHeight = sceneHeight / m;
MyNode[][] playfield = new MyNode[n][m];
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// initialize playfield
for( int i=0; i < n; i++) {
for( int j=0; j < m; j++) {
// create node
MyNode node = new MyNode( "Item " + i + "/" + j, i * gridWidth, j * gridHeight, gridWidth, gridHeight);
// add node to group
root.getChildren().add( node);
// add to playfield for further reference using an array
playfield[i][j] = node;
}
}
Scene scene = new Scene( root, sceneWidth, sceneHeight);
primaryStage.setScene( scene);
primaryStage.show();
animate();
}
private void animate() {
Random random = new Random();
int ai = random.nextInt(n);
int aj = random.nextInt(m);
int bi = random.nextInt(n);
int bj = random.nextInt(m);
// make sure that A and B are never the same
if( ai == bi && aj == bj) {
ai++;
if( ai >= n)
ai = 0;
}
MyNode nodeA = playfield[ai][aj];
nodeA.toFront();
MyNode nodeB = playfield[bi][bj];
nodeB.toFront();
// swap on array to keep array consistent
playfield[ai][aj] = nodeB;
playfield[bi][bj] = nodeA;
// A -> B
Path pathA = new Path();
pathA.getElements().add (new MoveTo ( nodeA.getTranslateX() + nodeA.getBoundsInParent().getWidth() / 2.0, nodeA.getTranslateY() + nodeA.getBoundsInParent().getHeight() / 2.0));
pathA.getElements().add (new LineTo( nodeB.getTranslateX() + nodeB.getBoundsInParent().getWidth() / 2.0, nodeB.getTranslateY() + nodeB.getBoundsInParent().getHeight() / 2.0));
PathTransition pathTransitionA = new PathTransition();
pathTransitionA.setDuration(Duration.millis(1000));
pathTransitionA.setNode( nodeA);
pathTransitionA.setPath(pathA);
pathTransitionA.play();
// B -> A
Path pathB = new Path();
pathB.getElements().add (new MoveTo ( nodeB.getTranslateX() + nodeB.getBoundsInParent().getWidth() / 2.0, nodeB.getTranslateY() + nodeB.getBoundsInParent().getHeight() / 2.0));
pathB.getElements().add (new LineTo( nodeA.getTranslateX() + nodeA.getBoundsInParent().getWidth() / 2.0, nodeA.getTranslateY() + nodeA.getBoundsInParent().getHeight() / 2.0));
PathTransition pathTransitionB = new PathTransition();
pathTransitionB.setDuration(Duration.millis(1000));
pathTransitionB.setNode( nodeB);
pathTransitionB.setPath(pathB);
pathTransitionB.play();
pathTransitionA.setOnFinished( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( pathTransitionB.getStatus() == Status.RUNNING)
return;
animate();
}
});
pathTransitionB.setOnFinished( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( pathTransitionA.getStatus() == Status.RUNNING)
return;
animate();
}
});
}
public static void main(String[] args) {
launch(args);
}
public static class MyNode extends StackPane {
public MyNode( String name, double x, double y, double width, double height) {
// create rectangle
Rectangle rectangle = new Rectangle( width, height);
rectangle.setStroke(Color.BLACK);
rectangle.setFill(Color.LIGHTBLUE);
// create label
Label label = new Label( name);
// set position
setTranslateX( x);
setTranslateY( y);
getChildren().addAll( rectangle, label);
}
}
}
And here's an example about how you could handle the cells via subclassing. But that's just one way to do it:
public class NodeDemo extends Application {
private double sceneWidth = 1024;
private double sceneHeight = 768;
private int n = 10;
private int m = 10;
double gridWidth = sceneWidth / n;
double gridHeight = sceneHeight / m;
MyNode[][] playfield = new MyNode[n][m];
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// initialize playfield
for( int i=0; i < n; i++) {
for( int j=0; j < m; j++) {
MyNode node = null;
// create bug
if( i == 0 && j == 0) {
node = new Bug( "Bug", Color.ORANGE, i, j);
}
// create food
else if( i == 5 && j == 5) {
node = new Food( "Food", Color.GREEN, i, j);
}
// create obstacle
else if( i == 3 && j == 3) {
node = new Obstacle( "Obstacle", Color.GRAY, i, j);
}
// add node to group
if( node != null) {
root.getChildren().add( node);
// add to playfield for further reference using an array
playfield[i][j] = node;
}
}
}
Scene scene = new Scene( root, sceneWidth, sceneHeight);
primaryStage.setScene( scene);
primaryStage.show();
// move bugs
animate();
}
private void animate() {
// TODO
}
public static void main(String[] args) {
launch(args);
}
private class Food extends MyNode {
public Food(String name, Color color, double x, double y) {
super(name, color, x, y);
}
}
private class Obstacle extends MyNode {
public Obstacle(String name, Color color, double x, double y) {
super(name, color, x, y);
}
}
private class Bug extends MyNode {
public Bug(String name, Color color, double x, double y) {
super(name, color, x, y);
}
}
private class MyNode extends StackPane {
public MyNode( String name, Color color, double x, double y) {
// create rectangle
Rectangle rectangle = new Rectangle( gridWidth, gridHeight);
rectangle.setStroke( color);
rectangle.setFill( color.deriveColor(1, 1, 1, 0.7));
// create label
Label label = new Label( name);
// set position
setTranslateX( x * gridWidth);
setTranslateY( y * gridHeight);
getChildren().addAll( rectangle, label);
}
}
}
You can start looking at the "Getting Started" section at the documentation. Focus on the simple examples like the HelloWorld and LoginForm sample programs.
For you structure you'll probably want to use a GridPane.

How to animate drawing edges with JUNG

I'm new to JUNG
I have a FRLayout that represents a network topology with key nodes or vertices that are color as red and the other vertices blue
The edges from the starting node to the end node are blue
I want to be to demonstrate an animation of the path to the end node.
How can animate drawing the edges from a start_node to end_node with a specified time interval? Can you provide or reference an example?
You could attach a keyframe to your Edge data. Then, every time you draw (using a transformer) you can use the keyframe to adjust the gradient of the edge:
RenderContext<V, E> context = vv.getRenderContext();
context.setEdgeDrawPaintTransformer(new KeyframeGradientTransformer());
public class KeyframeGradientTransformer() implements Transformer<E, Paint> {
#Override
public Paint transform(Edge edge) {
// TODO: Here you would determine the gradient information
// based on the edge.getKeyframe().
Paint gradient = new GradientPaint(...);
return gradient;
}
}
EDIT:
I wrote up a quick example:
This animates from one vertex to another (along one edge). If you want to animate going through multiple vertices, that will require more logic. However, this looks pretty cool and should give you a start. If you (or anyone else) need more comments, just let me know and I can try and make it more clear.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.algorithms.generators.random.EppsteinPowerLawGenerator;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.VisualizationViewer;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(1024, 768));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel content = new JPanel();
// Set up the graph and the display.
int numV = 70;
int numE = 50;
EppsteinPowerLawGenerator<String, String> gen = new EppsteinPowerLawGenerator<String, String>(
new GraphFactory(), new CountFactory(),
new CountFactory(), numV, numE, 10);
Graph<String, String> graph = gen.create();
Layout<String, String> layout = new SpringLayout<String, String>(
graph);
VisualizationViewer<String, String> vv = new VisualizationViewer<String, String>(
layout);
vv.getRenderContext().setEdgeStrokeTransformer(
new Transformer<String, Stroke>() {
#Override
public Stroke transform(String edge) {
return new BasicStroke(1.5f);
}
});
content.add(vv);
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// Animate the edges!
AnimationTimerTask at = new AnimationTimerTask(vv);
Timer timer = new Timer();
timer.scheduleAtFixedRate(at, 10, 30);
}
});
}
static class AnimationTimerTask extends TimerTask {
private final double width = 0.1; // Size of the colored line.
private final double stepsize = 0.01;
private double keyframe = 0 + width; // Between 0.0 and 1.0
private VisualizationViewer<String, String> vv = null;
public AnimationTimerTask(VisualizationViewer<String, String> vv) {
this.vv = vv;
}
#Override
public void run() {
vv.getRenderContext().setEdgeDrawPaintTransformer(
new Transformer<String, Paint>() {
#Override
public Paint transform(String edge) {
// Find both points of the edge.
Pair<String> vs = vv.getGraphLayout().getGraph()
.getEndpoints(edge);
Point2D p1 = vv.getGraphLayout().transform(
vs.getFirst());
Point2D p2 = vv.getGraphLayout().transform(
vs.getSecond());
// This code won't handle self-edges.
if (p1.equals(p2)) {
return Color.red;
}
Color[] colors = { Color.gray, Color.red,
Color.gray };
float start = (float) Math.max(0.0, keyframe
- width);
float end = (float) Math.min(1.0, keyframe + width);
float[] fractions = { start, (float) keyframe, end };
return new LinearGradientPaint(p1, p2, fractions,
colors);
}
});
vv.repaint();
keyframe += stepsize;
keyframe %= 1.0;
}
}
static class GraphFactory implements Factory<Graph<String, String>> {
#Override
public Graph<String, String> create() {
return new SparseMultigraph<String, String>();
}
}
static class CountFactory implements Factory<String> {
private int count = 0;
#Override
public String create() {
return String.valueOf(count++);
}
}
}
Also, I've taken a bit of heat for this before so: this requires the JUNG library. If you don't have it, you can't run the SSCCEE.

Categories