Draw Semi Ring of Equal Parts in JavaFX

121 views Asked by At

I am working on a personal project and I want the user to be able to use a simple version of a radial menu. I have been trying to create a ring that is made up of N arcs (or another shape) I want it to look similar to a donut/ring. Where the number of pieces and the inner and outer radius are configurable using a variable.

I want the final thing to be a simple version of this: Helo Wars Radial Menu

I have something parially working but it looks very rough. This is the best I have so far. Below I attached my code and a screenshot of the result. I want every piece to be equal in size that all surround the inner circle

Current output

Also as a note, I have seen other posts that make use of increasing the width of an arc I would rather not use that because I was running into issues with a mouse over listener, where there "hit zone" was a little off.

package com.example.javafx21;


import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;

public class HelloApplication extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane root = new Pane();
        root.setPrefSize(300, 300);

        double centerX = 150;
        double centerY = 150;


        double innerRadius = 40;
        double outerRadius = 100;

        int numPieces = 8;

        for (int i = 0; i < numPieces; i++) {

            double angle = 360.0 / numPieces * i;

            Arc arc = new Arc();
            arc.setCenterX(centerX);
            arc.setCenterY(centerY);
            arc.setRadiusX(outerRadius);
            arc.setRadiusY(outerRadius);
            arc.setStartAngle(angle);
            arc.setLength((double) 360 / numPieces);
            arc.setType(ArcType.OPEN);
            arc.setStroke(Color.color(Math.random(), Math.random(), Math.random()));
            arc.setStrokeWidth(outerRadius - innerRadius);
            arc.setFill(null);

            root.getChildren().add(arc);
        }

        Scene scene = new Scene(root);

        primaryStage.setTitle("Adjustable Radius Semi-Rings");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

2

There are 2 answers

1
Birdasaur On

If it helps here is a full open source implementation you can learn and extend from:

https://github.com/Birdasaur/LitFX/blob/master/litfx-controls/src/main/java/lit/litfx/controls/menus/LitRadialMenu.java

The arc drawing calls are lower down in the Menu Item classes. This implementation is itself a significantly upgraded version of the JFXtras radial component which was originally made by Mr.Lonee.

2
SedJ601 On

Here is an example based on the code here. I edited INNER_RADIUS, START_ANGLE, END_ANGLE, and STEP_ANGLE. I also added a new variable, GAP. GAP is the space between each segment.

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

public class App extends Application {

    private static final double OUTER_RADIUS =300, INNER_RADIUS = 140, ORIGIN_X = 350, ORIGIN_Y = 350;
    private static final double START_ANGLE =0, END_ANGLE = 360, STEP_ANGLE = 36;
    private static final double GAP = .75;
    
    @Override
    public void start(Stage primaryStage) {

        Group group = new Group();

        for(double angle =  START_ANGLE; angle < END_ANGLE ; angle += STEP_ANGLE ){
            Point2D innerArcStart = getPoint(INNER_RADIUS, angle + GAP);
            Point2D innerArcEnd = getPoint(INNER_RADIUS, angle - GAP + STEP_ANGLE);
            Point2D outerArcStart = getPoint(OUTER_RADIUS, angle + GAP);
            Point2D outerArcEnd = getPoint(OUTER_RADIUS, angle - GAP + STEP_ANGLE);
            var path = getPath(innerArcStart, innerArcEnd, outerArcStart, outerArcEnd);
            
            Point2D selectedInnerArcStart = getPoint(OUTER_RADIUS + 14, angle + GAP);
            Point2D selectedInnerArcEnd = getPoint(OUTER_RADIUS + 14, angle - GAP + STEP_ANGLE);
            Point2D selectedOuterArcStart = getPoint(OUTER_RADIUS + 10, angle + GAP);
            Point2D selectedOuterArcEnd = getPoint(OUTER_RADIUS + 10, angle - GAP + STEP_ANGLE);
            var selectedPath = getSelectedPath(selectedInnerArcStart, selectedInnerArcEnd, selectedOuterArcStart, selectedOuterArcEnd, (Color)path.getFill());
            path.setOnMouseEntered((mouseEnteredEvent) -> {
                group.getChildren().add(selectedPath);
            });
            
            path.setOnMouseExited((mouseExitedEvent) -> {
                group.getChildren().remove(selectedPath);
            });            
            
            group.getChildren().add(path);
        }
        primaryStage.setScene(new Scene(new Pane(group), 700, 700));
        primaryStage.show();
    }

    private Point2D getPoint(double radius, double angle){
        double x = ORIGIN_X - radius * Math.cos(Math.toRadians(angle));
        double y = ORIGIN_Y - radius * Math.sin(Math.toRadians(angle));
        return new Point2D(x, y);
    }

    private Shape getPath(Point2D innerArcStart, Point2D innerArcEnd, Point2D outerArcStart, Point2D outerArcEnd){
        var path = new Path(
                new MoveTo(innerArcStart.getX(), innerArcStart.getY()),
                new LineTo(outerArcStart.getX(), outerArcStart.getY()), //left line
                new ArcTo(OUTER_RADIUS, OUTER_RADIUS, 0, outerArcEnd.getX(), outerArcEnd.getY(), false, true), //outer arc
                new LineTo(innerArcEnd.getX(),innerArcEnd.getY()), //right line
                new ArcTo(INNER_RADIUS, INNER_RADIUS, 0, innerArcStart.getX(), innerArcStart.getY(), false, false)
                );
        path.setFill(Color.color(Math.random(), Math.random(), Math.random()));
        return path;
    }

    private Shape getSelectedPath(Point2D innerArcStart, Point2D innerArcEnd, Point2D outerArcStart, Point2D outerArcEnd, Color color){
        var path = new Path(
                new MoveTo(innerArcStart.getX(), innerArcStart.getY()),
                new LineTo(outerArcStart.getX(), outerArcStart.getY()), //left line
                new ArcTo(OUTER_RADIUS, OUTER_RADIUS, 0, outerArcEnd.getX(), outerArcEnd.getY(), false, true), //outer arc
                new LineTo(innerArcEnd.getX(),innerArcEnd.getY()), //right line
                new ArcTo(INNER_RADIUS, INNER_RADIUS, 0, innerArcStart.getX(), innerArcStart.getY(), false, false)
                );
        path.setFill(color);
        return path;
    }
    
    public static void main(String args[]){
        launch(args);
    }
} 

Output

enter image description here