I am trying to create a JavaFX node. The node appears to work well, but I noticed that maximizing the stage causes it to grow large and then return to its correct size. This also happens when I click to go back to the original screen size. If I drag the stage's width and height, the node size behaves as expected. This behavior happens quickly, but is easier to see when returning to the original stage size. How do I calculate the sizes so that this behavior no longer happens?
-
Product Version: Apache NetBeans IDE 30
-
Java: 26.0.2; OpenJDK 64-Bit Server VM 26.0.2+10
-
Runtime: OpenJDK Runtime Environment 26.0.2+10
-
System: Windows 11 version 10.0 running on amd64; UTF-8; en_US (nb)
OverlapGraphView
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
public class OverlapGraphView extends Region {
// Style properties
private final ObjectProperty<Paint> colorA = new SimpleObjectProperty<>(Color.web("#3B82F6", 0.2));
private final ObjectProperty<Paint> strokeA = new SimpleObjectProperty<>(Color.web("#3B82F6"));
private final ObjectProperty<Paint> colorBoth = new SimpleObjectProperty<>(Color.web("#8B5CF6", 0.4));
private final ObjectProperty<Paint> strokeBoth = new SimpleObjectProperty<>(Color.web("#8B5CF6"));
private final ObjectProperty<Paint> colorB = new SimpleObjectProperty<>(Color.web("#EC4899", 0.2));
private final ObjectProperty<Paint> strokeB = new SimpleObjectProperty<>(Color.web("#EC4899"));
// Exposed click event handlers
private final ObjectProperty<EventHandler<? super MouseEvent>> onAOnlyClicked = new SimpleObjectProperty<>();
private final ObjectProperty<EventHandler<? super MouseEvent>> onBothClicked = new SimpleObjectProperty<>();
private final ObjectProperty<EventHandler<? super MouseEvent>> onBOnlyClicked = new SimpleObjectProperty<>();
// Persistent shape views to prevent infinite layout recursion loops
private final SVGPath pathAOnly = new SVGPath();
private final SVGPath pathBoth = new SVGPath();
private final SVGPath pathBOnly = new SVGPath();
public OverlapGraphView() {
setPrefSize(400, 200);
// Bind presentation settings once in the constructor
pathAOnly.fillProperty().bind(colorA);
pathAOnly.strokeProperty().bind(strokeA);
pathAOnly.setStrokeWidth(2);
pathBoth.fillProperty().bind(colorBoth);
pathBoth.strokeProperty().bind(strokeBoth);
pathBoth.setStrokeWidth(3);
pathBOnly.fillProperty().bind(colorB);
pathBOnly.strokeProperty().bind(strokeB);
pathBOnly.setStrokeWidth(2);
setupHoverAnimation(pathAOnly);
setupHoverAnimation(pathBoth);
setupHoverAnimation(pathBOnly);
// Map events directly onto persistent elements
pathAOnly.setOnMouseClicked(e -> { if (onAOnlyClicked.get() != null) onAOnlyClicked.get().handle(e); });
pathBoth.setOnMouseClicked(e -> { if (onBothClicked.get() != null) onBothClicked.get().handle(e); });
pathBOnly.setOnMouseClicked(e -> { if (onBOnlyClicked.get() != null) onBOnlyClicked.get().handle(e); });
// Add to child stack order once
getChildren().addAll(pathAOnly, pathBOnly, pathBoth);
}
/**
* JavaFX calls this method safely during resize passes.
* We modify properties of existing children rather than recreating nodes.
*/
protected void layoutChildren() {
super.layoutChildren();
double w = getWidth();
double h = getHeight();
if (w <= 0 || h <= 0) return;
// Calculate dynamic dimensions matching your scaling formula
double radius = w * 0.23;
double centerY = h * 0.5;
double cxL = (w * 0.5) - (radius * 0.55);
double cxR = (w * 0.5) + (radius * 0.55);
// Calculate exact mathematical arc intersection points
double dx = cxR - cxL;
double d = Math.sqrt(dx * dx);
double a = (radius * radius - radius * radius + d * d) / (2 * d);
double hPoint = Math.sqrt(radius * radius - a * a);
double ix = cxL + a;
double iy1 = centerY - hPoint;
double iy2 = centerY + hPoint;
// Generate perfect SVG paths smoothly handling window maximums/restores
String leftCrescent = String.format("M %f,%f A %f,%f 0 1,0 %f,%f A %f,%f 0 0,1 %f,%f Z",
ix, iy1, radius, radius, ix, iy2, radius, radius, ix, iy1);
String centerLens = String.format("M %f,%f A %f,%f 0 0,1 %f,%f A %f,%f 0 0,1 %f,%f Z",
ix, iy1, radius, radius, ix, iy2, radius, radius, ix, iy1);
String rightCrescent = String.format("M %f,%f A %f,%f 0 0,0 %f,%f A %f,%f 0 1,0 %f,%f Z",
ix, iy1, radius, radius, ix, iy2, radius, radius, ix, iy1);
// Update backing rendering paths natively without clear/add cycles
pathAOnly.setContent(leftCrescent);
pathBoth.setContent(centerLens);
pathBOnly.setContent(rightCrescent);
}
private void setupHoverAnimation(SVGPath shape) {
shape.setOnMouseEntered(e -> shape.setOpacity(0.65));
shape.setOnMouseExited(e -> shape.setOpacity(1.0));
}
public void setOnAOnlyClicked(EventHandler<? super MouseEvent> handler) { onAOnlyClicked.set(handler); }
public void setOnBothClicked(EventHandler<? super MouseEvent> handler) { onBothClicked.set(handler); }
public void setOnBOnlyClicked(EventHandler<? super MouseEvent> handler) { onBOnlyClicked.set(handler); }
}Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavaFxTest extends Application {
public void start(Stage primaryStage) {
OverlapGraphView overlapGraphView = new OverlapGraphView();
overlapGraphView.setOnAOnlyClicked(e -> {
System.out.println("Filter table by: segment A!");
});
overlapGraphView.setOnBothClicked(e -> {
System.out.println("Filter table by: segment Both!");
});
overlapGraphView.setOnBOnlyClicked(e -> {
System.out.println("Filter table by: segment B!");
});
overlapGraphView.setMaxSize(200, 200);
StackPane container = new StackPane(overlapGraphView);
Scene scene = new Scene(container, 320, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("UX Overlap Graph");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}