9

I would like to know how to draw a semi circle in JavaFX. I tried to use Shape and QuadCurve but I couldn't make a perfect semicircle.

Here is a picture of what I'm trying to draw :

enter image description here

Daniel Compton
  • 13,878
  • 4
  • 40
  • 60
AwaX
  • 448
  • 3
  • 8
  • 19

4 Answers4

14

The picture you linked is actually a semi-ring. You can get it in JavaFX by drawing nested 2 arcs and some lines. But my preferred way is to use the Path.

public class SemiDemo extends Application {

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();
        root.getChildren().add(drawSemiRing(120, 120, 100, 50, Color.LIGHTGREEN, Color.DARKGREEN));
        root.getChildren().add(drawSemiRing(350, 350, 200, 30, Color.LIGHTSKYBLUE, Color.DARKBLUE));

        Scene scene = new Scene(root, 300, 250);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Path drawSemiRing(double centerX, double centerY, double radius, double innerRadius, Color bgColor, Color strkColor) {
        Path path = new Path();
        path.setFill(bgColor);
        path.setStroke(strkColor);
        path.setFillRule(FillRule.EVEN_ODD);

        MoveTo moveTo = new MoveTo();
        moveTo.setX(centerX + innerRadius);
        moveTo.setY(centerY);

        ArcTo arcToInner = new ArcTo();
        arcToInner.setX(centerX - innerRadius);
        arcToInner.setY(centerY);
        arcToInner.setRadiusX(innerRadius);
        arcToInner.setRadiusY(innerRadius);

        MoveTo moveTo2 = new MoveTo();
        moveTo2.setX(centerX + innerRadius);
        moveTo2.setY(centerY);

        HLineTo hLineToRightLeg = new HLineTo();
        hLineToRightLeg.setX(centerX + radius);

        ArcTo arcTo = new ArcTo();
        arcTo.setX(centerX - radius);
        arcTo.setY(centerY);
        arcTo.setRadiusX(radius);
        arcTo.setRadiusY(radius);

        HLineTo hLineToLeftLeg = new HLineTo();
        hLineToLeftLeg.setX(centerX - innerRadius);

        path.getElements().add(moveTo);
        path.getElements().add(arcToInner);
        path.getElements().add(moveTo2);
        path.getElements().add(hLineToRightLeg);
        path.getElements().add(arcTo);
        path.getElements().add(hLineToLeftLeg);

        return path;
    }

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

Refer to Shape API of JavaFX for more info about the shapes used in the code.
Screenshot:

enter image description here

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
  • Year, perfect ! :D It's exactly what I need ^^ Thank you very much ! – AwaX Jul 30 '12 at 12:14
  • Do you know if there is a simple way to modify the rotate point of a path ? – AwaX Jul 31 '12 at 11:59
  • No no ^^ I mean the point where the rotation is applied, because by default for a path the rotate point is the first point drawn on the scene, so if by chance you know a simple way to change that it'd be great :) – AwaX Jul 31 '12 at 12:32
  • @AwaX. Please start new a Q&A entry. This way you will be explaining the problem more accurately and having more answers for it quicker. – Uluk Biy Jul 31 '12 at 12:43
  • Hey @AwaX. Try this path.getTransforms().add(new Rotate(45, centerX + radius, centerY)); where 2nd 3rd params are pivotX pivotY coordinates respectively. – Uluk Biy Jul 31 '12 at 13:32
  • for 180 rotate : arcToInner.setSweepFlag(true); – Jay Thakkar May 19 '14 at 09:13
  • 1
    @Pappu, use directly 2 circles. Subtract the one from another. – Uluk Biy Oct 30 '15 at 05:47
4

Suggestions:

  • If you don't need a full outlining path, you can just use an Arc.
  • If you don't need the arc filled and just want to trace the outline path of the arc, then set the fill of the arc to null.
  • If you want the outline path of the arc thick, then set the stroke parameters on the arc.
  • If you need the a thick arc which is also outlined, then it is best to define a full arc as in Uluk's answer.

Sample code:

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;

public class SemiCircleSample extends Application {
  @Override public void start(Stage stage) {
    Arc arc = new Arc(50, 50, 25, 25, 0, 180);
    arc.setType(ArcType.OPEN);
    arc.setStrokeWidth(10);
    arc.setStroke(Color.CORAL);
    arc.setStrokeType(StrokeType.INSIDE);
    arc.setFill(null);

    stage.setScene(new Scene(new Group(arc), 100, 80));
    stage.show();
  }

  public static void main(String[] args) { launch(args); }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Thank you but it's a full semi circle, what I want to draw is a semi circle empty in the center. I included an example in my previous post showing exactly what I'm trying to draw. – AwaX Jul 30 '12 at 11:39
  • Updated answer to demonstrate drawing an arc with a stroke specified and no fill (which will generate a half ring image). – jewelsea Jul 30 '12 at 17:10
  • Thank you very much for you update, your solution is more convenient but you can't fill the arc like you said, so I think I'll take the other solution. – AwaX Jul 31 '12 at 11:57
2

As an experiment, I tried to do the same thing on a Canvas. This is what I came up with, making use of a RadialGradient and the function GraphicsContext.fillArc:

/**
 *
 * @param x Coordinate x of the centre of the arc
 * @param y Coordinate y of the centre of the arc
 * @param outer Outer radius of the arc
 * @param innerPercentage Inner radius of the arc, from 0 to 1 (as percentage)
 * @param arcStartAngle Start angle of the arc, in degrees
 * @param arcExtent Extent of the arc, in degrees
 */
private void drawSemiCircle(float x, float y, float outer, float innerPercentage, float arcStartAngle, float arcExtent) {
    RadialGradient rg = new RadialGradient(
            0,
            0,
            x,
            y,
            outer,
            false,
            CycleMethod.NO_CYCLE,
            new Stop((innerPercentage + (.0 * innerPercentage)), Color.TRANSPARENT),
            new Stop((innerPercentage + (.1 * innerPercentage)), Color.RED),
            new Stop((innerPercentage + (.6 * innerPercentage)), Color.YELLOW),
            new Stop((innerPercentage + (1 * innerPercentage)), Color.GREEN)
    );
    gc.setFill(rg);
    gc.fillArc(
            x - outer,
            y - outer,
            outer * 2,
            outer * 2,
            arcStartAngle,
            arcExtent,
            ArcType.ROUND
    );
}

Key points here are the arc type as ArcType.ROUND and the use of Color.TRANSPARENT as the first color.

Then it can be used something along the line:

drawSemiCircle(100, 100, 100, .5f, -45, 270);

It's not a perfect solution but it worked for me.

Leonel B.
  • 185
  • 3
  • 5
0

Path.arcTo() the parameter SweepAngle refers to the rotation degree, if sweepAngle is positive the arc is clockwise, if sweepAngle is negative the arc is counterclockwise.

This code is used in my production environment, it draws a semi-circle ring using a bitmap image, the path goes clockwise on the outer radius, and counter-clockwise on the inner radius:

drawpercent = 0.85; //this draws a semi ring to 85% you can change it using your code.
DegreesStart = -90;
DegreesRotation = 180;

radiusPathRectF = new android.graphics.RectF((float)CentreX - (float)Radius, (float)CentreY - (float)Radius,  (float)CentreX + (float)Radius, (float)CentreY + (float)Radius);
innerradiusPathRectF = new android.graphics.RectF((float)CentreX - (float)InnerRadius, (float)CentreY - (float)InnerRadius, (float)CentreX + (float)InnerRadius, (float)CentreY + (float)InnerRadius);

Path p = new Path(); //TODO put this outside your draw() function,  you should never have a "new" keyword inside a fast loop.

                degrees = (360 + (DegreesStart)) % 360;
                radians = (360 - degrees + 90) * Math.PI / 180.0;
                //radians = Math.toRadians(DegreesStart);
                int XstartOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
                int YstartOuter = (int)Math.round((Math.sin(-radians)* Radius + CentreY));
                int XstartInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
                int YstartInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));

                degrees = (360 + (DegreesStart + drawpercent * DegreesRotation)) % 360;
                //radians = degrees * Math.PI / 180.0;
                radians = (360 - degrees + 90) * Math.PI / 180.0;
                //radians = Math.toRadians(DegreesStart + drawpercent * DegreesRotation);
                int XendOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
                int YendOuter = (int)Math.round((Math.sin(-radians) * Radius + CentreY));
                int XendInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
                int YendInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));

                //draw a path outlining the semi-circle ring.
                p.moveTo(XstartInner, YstartInner);
                p.lineTo(XstartOuter, YstartOuter);
                p.arcTo(radiusPathRectF, (float)DegreesStart - (float)90, (float)drawpercent * (float)DegreesRotation);
                p.lineTo(XendInner, YendInner);
                p.arcTo(innerradiusPathRectF, (float)degrees - (float)90, -1 * (float)drawpercent * (float)DegreesRotation);
                p.close();

                g.clipPath(p);

                g.drawBitmap(bitmapCircularBarImage, bitmapRect0, bitmapRectXY, paint);
hamish
  • 1,141
  • 1
  • 12
  • 21