© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
K. Sharan, P. SpäthLearn JavaFX 17https://doi.org/10.1007/978-1-4842-7848-2_14

14. Understanding 2D Shapes

Kishori Sharan1   and Peter Späth2
(1)
Montgomery, AL, USA
(2)
Leipzig, Sachsen, Germany
 
In this chapter, you will learn:
  • What 2D shapes are and how they are represented in JavaFX

  • How to draw 2D shapes

  • How to draw complex shapes using the Path class

  • How to draw shapes using the Scalable Vector Graphics (SVG)

  • How to combine shapes to build another shape

  • How to use strokes for a shape

  • How to style shapes using Cascading Style Sheets (CSS)

The examples of this chapter lie in the com.jdojo.shape package. In order for them to work, you must add a corresponding line to the module-info.java file:
...
opens com.jdojo.shape to javafx.graphics, javafx.base;
...

What Are 2D Shapes?

Any shape that can be drawn in a two-dimensional plane is called a 2D shape. JavaFX offers a variety of nodes to draw different types of shapes (lines, circles, rectangles, etc.). You can add shapes to a scene graph.

Shapes can be two-dimensional or three-dimensional. In this chapter, I will discuss 2D shapes. Chapter 16 discusses 3D shapes.

All shape classes are in the javafx.scene.shape package . Classes representing 2D shapes are inherited from the abstract Shape class as shown in Figure 14-1.
Figure 14-1

A class diagram for classes representing 2D shapes

A shape has a size and a position, which are defined by their properties. For example, the width and height properties define the size of a rectangle, the radius property defines the size of a circle, the x and y properties define the position of the upper-left corner of a rectangle, the centerX and centerY properties define the center of a circle, etc.

Shapes are not resized by their parents during layout. The size of a shape changes only when its size-related properties are changed. You may find a phrase like “JavaFX shapes are nonresizable.” It means shapes are nonresizable by their parent during layout. They can be resized only by changing their properties.

Shapes have an interior and a stroke . The properties for defining the interior and stroke of a shape are declared in the Shape class. The fill property specifies the color to fill the interior of the shape. The default fill is Color.BLACK. The stroke property specifies the color for the outline stroke, which is null by default, except for Line, Polyline, and Path, which have Color.BLACK as the default stroke. The strokeWidth property specifies the width of the outline, which is 1.0px by default. The Shape class contains other stroke-related properties that I will discuss in the section “Understanding the Stroke of a Shape.”

The Shape class contains a smooth property , which is true by default. Its true value indicates that an antialiasing hint should be used to render the shape. If it is set to false, the antialiasing hint will not be used, which may result in the edges of shapes being not crisp.

The program in Listing 14-1 creates two circles. The first circle has a light gray fill and no stroke, which is the default. The second circle has a yellow fill and a 2.0px wide black stroke. Figure 14-2 shows the two circles.
// ShapeTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ShapeTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // Create a circle with a light gray fill and no stroke
            Circle c1 = new Circle(40, 40, 40);
            c1.setFill(Color.LIGHTGRAY);
            // Create a circle with an yellow fill and a black stroke
            // of 2.0px
            Circle c2 = new Circle(40, 40, 40);
            c2.setFill(Color.YELLOW);
            c2.setStroke(Color.BLACK);
            c2.setStrokeWidth(2.0);
            HBox root = new HBox(c1, c2);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Shapes");
            stage.show();
        }
}
Listing 14-1

Using fill and stroke Properties of the Shape Class

Figure 14-2

Two circles with different fills and strokes

Drawing 2D Shapes

The following sections describe in detail how to use the JavaFX classes representing 2D shapes to draw those shapes.

Drawing Lines

An instance of the Line class represents a line node. A Line has no interior. By default, its fill property is set to null. Setting fill has no effects. The default stroke is Color.BLACK, and the default strokeWidth is 1.0. The Line class contains four double properties:
  • startX

  • startY

  • endX

  • endY

The Line represents a line segment between (startX, startY) and (endX, endY) points. The Line class has a no-args constructor, which defaults all its four properties to zero resulting in a line from (0, 0) to (0, 0), which represents a point. Another constructor takes values for startX, startY, endX, and endY. After you create a Line, you can change its location and length by changing any of the four properties.

The program in Listing 14-2 creates some Lines and sets their stroke and strokeWidth properties. The first Line will appear as a point. Figure 14-3 shows the line.
// LineTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class LineTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // It will be just a point at (0, 0)
            Line line1 = new Line();
            Line line2 = new Line(0, 0, 50, 0);
            line2.setStrokeWidth(1.0);
            Line line3 = new Line(0, 50, 50, 0);
            line3.setStrokeWidth(2.0);
            line3.setStroke(Color.RED);
            Line line4 = new Line(0, 0, 50, 50);
            line4.setStrokeWidth(5.0);
            line4.setStroke(Color.BLUE);
            HBox root = new HBox(line1, line2, line3, line4);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Lines");
            stage.show();
        }
}
Listing 14-2

Using the Line Class to Create Line Nodes

Figure 14-3

Using line nodes

Drawing Rectangles

An instance of the Rectangle class represents a rectangle node. The class uses six properties to define the rectangle:
  • x

  • y

  • width

  • height

  • arcWidth

  • arcHeight

The x and y properties are the x and y coordinates of the upper-left corner of the rectangle in the local coordinate system of the node. The width and height properties are the width and height of the rectangle, respectively. Specify the same width and height to draw a square.

By default, the corners of a rectangle are sharp. A rectangle can have rounded corners by specifying the arcWidth and arcHeight properties. You can think of one of the quadrants of an ellipse positioned at the four corners to make them round. The arcWidth and arcHeight properties are the horizontal and vertical diameters of the ellipse. By default, their values are zero, which makes a rectangle have sharp corners. Figure 14-4 shows two rectangles—one with sharp corners and one with rounded corners. The ellipse is shown to illustrate the relationship between the arcWidth and arcHeight properties for a rounded rectangle.
Figure 14-4

Rectangles with sharp and rounded corners

The Rectangle class contains several constructors. They take various properties as arguments. The default values for x, y, width, height, arcWidth, and arcHeight properties are zero. The constructors are
  • Rectangle()

  • Rectangle(double width, double height)

  • Rectangle(double x, double y, double width, double height)

  • Rectangle(double width, double height, Paint fill)

You will not see effects of specifying the values for the x and y properties for a Rectangle when you add it to most of the layout panes as they place their children at (0, 0). A Pane uses these properties. The program in Listing 14-3 adds two rectangles to a Pane. The first rectangle uses the default values of zero for the x and y properties. The second rectangle specifies 120 for the x property and 20 for the y property. Figure 14-5 shows the positions of the two rectangles inside the Pane. Notice that the upper-left corner of the second rectangle (on the right) is at (120, 20).
// RectangleTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class RectangleTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // x=0, y=0, width=100, height=50, fill=LIGHTGRAY, stroke=null
            Rectangle rect1 = new Rectangle(100, 50, Color.LIGHTGRAY);
            // x=120, y=20, width=100, height=50, fill=WHITE, stroke=BLACK
            Rectangle rect2 = new Rectangle(120, 20, 100, 50);
            rect2.setFill(Color.WHITE);
            rect2.setStroke(Color.BLACK);
            rect2.setArcWidth(10);
            rect2.setArcHeight(10);
            Pane root = new Pane();
            root.getChildren().addAll(rect1, rect2);
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Rectangles");
            stage.show();
        }
}
Listing 14-3

Using the Rectangle Class to Create Rectangle Nodes

Figure 14-5

Rectangles inside a Pane, which uses the x and y properties to position them

Drawing Circles

An instance of the Circle class represents a circle node. The class uses three properties to define the circle:
  • centerX

  • centerY

  • radius

The centerX and centerY properties are the x and y coordinates of the center of the circle in the local coordinate system of the node. The radius property is the radius of the circle. The default values for these properties are zero.

The Circle class contains several constructors:
  • Circle()

  • Circle(double radius)

  • Circle(double centerX, double centerY, double radius)

  • Circle(double centerX, double centerY, double radius, Paint fill)

  • Circle(double radius, Paint fill)

The program in Listing 14-4 adds two circles to an HBox. Notice that the HBox does not use centerX and centerY properties of the circles. Add them to a Pane to see the effects. Figure 14-6 shows the two circles.
// CircleTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class CircleTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // centerX=0, centerY=0, radius=40, fill=LIGHTGRAY,
            // stroke=null
            Circle c1 = new Circle(0, 0, 40);
            c1.setFill(Color.LIGHTGRAY);
            // centerX=10, centerY=10, radius=40. fill=YELLOW,
            // stroke=BLACK
            Circle c2 = new Circle(10, 10, 40, Color.YELLOW);
            c2.setStroke(Color.BLACK);
            c2.setStrokeWidth(2.0);
            HBox root = new HBox(c1, c2);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Circle");
            stage.show();
        }
}
Listing 14-4

Using the Circle Class to Create Circle Nodes

Figure 14-6

Using circle nodes

Drawing Ellipses

An instance of the Ellipse class represents an ellipse node. The class uses four properties to define the ellipse:
  • centerX

  • centerY

  • radiusX

  • radiusY

The centerX and centerY properties are the x and y coordinates of the center of the circle in the local coordinate system of the node. The radiusX and radiusY are the radii of the ellipse in the horizontal and vertical directions. The default values for these properties are zero. A circle is a special case of an ellipse when radiusX and radiusY are the same.

The Ellipse class contains several constructors:
  • Ellipse()

  • Ellipse(double radiusX, double radiusY)

  • Ellipse(double centerX, double centerY, double radiusX, double radiusY)

The program in Listing 14-5 creates three instances of the Ellipse class. The third instance draws a circle as the program sets the same value for the radiusX and radiusY properties. Figure 14-7 shows the three ellipses.
// EllipseTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
public class EllipseTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            Ellipse e1 = new Ellipse(50, 30);
            e1.setFill(Color.LIGHTGRAY);
            Ellipse e2 = new Ellipse(60, 30);
            e2.setFill(Color.YELLOW);
            e2.setStroke(Color.BLACK);
            e2.setStrokeWidth(2.0);
            // Draw a circle using the Ellipse class (radiusX=radiusY=30)
            Ellipse e3 = new Ellipse(30, 30);
            e3.setFill(Color.YELLOW);
            e3.setStroke(Color.BLACK);
            e3.setStrokeWidth(2.0);
            HBox root = new HBox(e1, e2, e3);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Ellipses");
            stage.show();
        }
}
Listing 14-5

Using the Ellipse Class to Create Ellipse Nodes

Figure 14-7

Using ellipse nodes

Drawing Polygons

An instance of the Polygon class represents a polygon node. The class does not define any public properties. It lets you draw a polygon using an array of (x, y) coordinates defining the vertices of the polygon. Using the Polygon class, you can draw any type of geometric shape that is created using connected lines (triangles, pentagons, hexagons, parallelograms, etc.).

The Polygon class contains two constructors:
  • Polygon()

  • Polygon(double... points)

The no-args constructor creates an empty polygon. You need add the (x, y) coordinates of the vertices of the shape. The polygon will draw a line from the first vertex to the second vertex, from the second to the third, and so on. Finally, the shape is closed by drawing a line from the last vertex to the first vertex.

The Polygon class stores the coordinates of the vertices in an ObservableList<Double>. You can get the reference of the observable list using the getPoints() method. Notice that it stores the coordinates in a list of Double, which is simply a number. It is your job to pass the numbers in pairs, so they can be used as (x, y) coordinates of vertices. If you pass an odd number of numbers, no shape is created. The following snippet of code creates two triangles—one passes the coordinates of the vertices in the constructor, and another adds them to the observable list later. Both triangles are geometrically the same:
// Create an empty triangle and add vertices later
Polygon triangle1 = new Polygon();
triangle1.getPoints().addAll(50.0, 0.0,
                      0.0, 100.0,
                      100.0, 100.0);
// Create a triangle with vertices
Polygon triangle2 = new Polygon(50.0, 0.0,
                        0.0, 100.0,
                        100.0, 100.0);
The program in Listing 14-6 creates a triangle , a parallelogram, and a hexagon using the Polygon class as shown in Figure 14-8.
// PolygonTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class PolygonTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            Polygon triangle1 = new Polygon();
            triangle1.getPoints().addAll(50.0, 0.0,
                0.0, 50.0,
                100.0, 50.0);
            triangle1.setFill(Color.WHITE);
            triangle1.setStroke(Color.RED);
            Polygon parallelogram = new Polygon();
            parallelogram.getPoints().addAll(
               30.0, 0.0,
               130.0, 0.0,
               100.00, 50.0,
               0.0, 50.0);
            parallelogram.setFill(Color.YELLOW);
            parallelogram.setStroke(Color.BLACK);
            Polygon hexagon = new Polygon(
                100.0, 0.0,
                120.0, 20.0,
                120.0, 40.0,
                100.0, 60.0,
                80.0, 40.0,
                80.0, 20.0);
            hexagon.setFill(Color.WHITE);
            hexagon.setStroke(Color.BLACK);
            HBox root = new HBox(triangle1, parallelogram, hexagon);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Polygons");
            stage.show();
        }
}
Listing 14-6

Using the Polygon Class to Create a Triangle, a Parallelogram, and a Hexagon

Figure 14-8

Using polygon nodes

Drawing Polylines

A polyline is similar to a polygon, except that it does not draw a line between the last and first points. That is, a polyline is an open polygon. However, the fill color is used to fill the entire shape as if the shape was closed.

An instance of the Polyline class represents a polyline node. The class does not define any public properties. It lets you draw a polyline using an array of (x, y) coordinates defining the vertices of the polyline. Using the Polyline class, you can draw any type of geometric shape that is created using connected lines (triangles, pentagons, hexagons, parallelograms, etc.).

The Polyline class contains two constructors:
  • Polyline()

  • Polyline(double... points)

The no-args constructor creates an empty polyline. You need add (x, y) coordinates of the vertices of the shape. The polygon will draw a line from the first vertex to the second vertex, from the second to the third, and so on. Unlike a Polygon, the shape is not closed automatically. If you want to close the shape, you need to add the coordinates of the first vertex as the last pair of numbers.

If you want to add coordinates of vertices later, add them to the ObservableList<Double> returned by the getPoints() method of the Polyline class. The following snippet of code creates two triangles with the same geometrical properties using different methods. Notice that the first and the last pairs of numbers are the same in order to close the triangle:
// Create an empty triangle and add vertices later
Polygon triangle1 = new Polygon();
triangle1.getPoints().addAll(
    50.0, 0.0,
    0.0, 100.0,
    100.0, 100.0,
    50.0, 0.0);
// Create a triangle with vertices
Polygon triangle2 = new Polygon(
    50.0, 0.0,
    0.0, 100.0,
    100.0, 100.0,
    50.0, 0.0);
The program in Listing 14-7 creates a triangle, an open parallelogram, and a hexagon using the Polyline class as shown in Figure 14-9.
// PolylineTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polyline;
import javafx.stage.Stage;
public class PolylineTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            Polyline triangle1 = new Polyline();
            triangle1.getPoints().addAll(
                50.0, 0.0,
                0.0, 50.0,
                100.0, 50.0,
                50.0, 0.0);
            triangle1.setFill(Color.WHITE);
            triangle1.setStroke(Color.RED);
            // Create an open parallelogram
            Polyline parallelogram = new Polyline();
            parallelogram.getPoints().addAll(
                30.0, 0.0,
                130.0, 0.0,
                100.00, 50.0,
                0.0, 50.0);
            parallelogram.setFill(Color.YELLOW);
            parallelogram.setStroke(Color.BLACK);
            Polyline hexagon = new Polyline(
               100.0, 0.0,
               120.0, 20.0,
               120.0, 40.0,
               100.0, 60.0,
               80.0, 40.0,
               80.0, 20.0,
               100.0, 0.0);
            hexagon.setFill(Color.WHITE);
            hexagon.setStroke(Color.BLACK);
            HBox root = new HBox(triangle1, parallelogram, hexagon);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Polylines");
            stage.show();
        }
}
Listing 14-7

Using the Polyline Class to Create a Triangle, an Open Parallelogram, and a Hexagon

Figure 14-9

Using polyline nodes

Drawing Arcs

An instance of the Arc class represents a sector of an ellipse. The class uses seven properties to define the ellipse:
  • centerX

  • centerY

  • radiusX

  • radiusY

  • startAngle

  • length

  • type

The first four properties define an ellipse. Please refer to the section “Drawing Ellipses” for how to define an ellipse. The last three properties define a sector of the ellipse that is the Arc node. The startAngle property specifies the start angle of the section in degrees measured counterclockwise from the positive x-axis. It defines the beginning of the arc. The length is an angle in degrees measured counterclockwise from the start angle to define the end of the sector. If the length property is set to 360, the Arc is a full ellipse. Figure 14-10 illustrates the properties.
Figure 14-10

Properties defining an Arc

The type property specifies the way the Arc is closed. It is one of the constants, OPEN, CHORD, and ROUND, defined in the ArcType enum :
  • The ArcType.OPEN does not close the arc.

  • The ArcType.CHORD closes the arc by joining the starting and ending points by a straight line.

  • The ArcType.ROUND closes the arc by joining the starting and ending points to the center of the ellipse.

Figure 14-11 shows the three closure types for an arc. The default type for an Arc is ArcType.OPEN. If you do not apply a stroke to an Arc, both ArcType.OPEN and ArcType.CHORD look the same.
Figure 14-11

Closure types of an arc

The Arc class contains two constructors:
  • Arc()

  • Arc(double centerX, double centerY, double radiusX, double radiusY, double startAngle, double length)

The program in Listing 14-8 shows how to create Arc nodes . The resulting window is shown in Figure 14-12.
// ArcTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class ArcTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // An OPEN arc with a fill
            Arc arc1 = new Arc(0, 0, 50, 100, 0, 90);
            arc1.setFill(Color.LIGHTGRAY);
            // An OPEN arc with no fill and a stroke
            Arc arc2 = new Arc(0, 0, 50, 100, 0, 90);
            arc2.setFill(Color.TRANSPARENT);
            arc2.setStroke(Color.BLACK);
            // A CHORD arc with no fill and a stroke
            Arc arc3 = new Arc(0, 0, 50, 100, 0, 90);
            arc3.setFill(Color.TRANSPARENT);
            arc3.setStroke(Color.BLACK);
            arc3.setType(ArcType.CHORD);
            // A ROUND arc with no fill and a stroke
            Arc arc4 = new Arc(0, 0, 50, 100, 0, 90);
            arc4.setFill(Color.TRANSPARENT);
            arc4.setStroke(Color.BLACK);
            arc4.setType(ArcType.ROUND);
            // A ROUND arc with a gray fill and a stroke
            Arc arc5 = new Arc(0, 0, 50, 100, 0, 90);
            arc5.setFill(Color.GRAY);
            arc5.setStroke(Color.BLACK);
            arc5.setType(ArcType.ROUND);
            HBox root = new HBox(arc1, arc2, arc3, arc4, arc5);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Arcs");
            stage.show();
        }
}
Listing 14-8

Using the Arc Class to Create Arcs, Which Are Sectors of Ellipses

Figure 14-12

Using Arc nodes

Drawing Quadratic Curves

Bezier curves are used in computer graphics to draw smooth curves. An instance of the QuadCurve class represents a quadratic Bezier curve segment intersecting two specified points using a specified Bezier control point. The QuadCurve class contains six properties to specify the three points:
  • startX

  • startY

  • controlX

  • controlY

  • endX

  • endY

The QuadCurve class contains two constructors:
  • QuadCurve()

  • QuadCurve(double startX, double startY, double controlX, double controlY, double endX, double endY)

The program in Listing 14-9 draws the same quadratic Bezier curve twice—once with a stroke and a transparent fill and once with no stroke and a light gray fill. Figure 14-13 shows the two curves.
// QuadCurveTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.QuadCurve;
import javafx.stage.Stage;
public class QuadCurveTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            QuadCurve qc1 = new QuadCurve(0, 100, 20, 0, 150, 100);
            qc1.setFill(Color.TRANSPARENT);
            qc1.setStroke(Color.BLACK);
            QuadCurve qc2 = new QuadCurve(0, 100, 20, 0, 150, 100);
            qc2.setFill(Color.LIGHTGRAY);
            HBox root = new HBox(qc1, qc2);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using QuadCurves");
            stage.show();
        }
}
Listing 14-9

Using the QuadCurve Class to Draw Quadratic BezierCurve

Figure 14-13

Using quadratic Bezier curves

Drawing Cubic Curves

An instance of the CubicCurve class represents a cubic Bezier curve segment intersecting two specified points using two specified Bezier control points. Please refer to the Wikipedia article at http://en.wikipedia.org/wiki/Bezier_curves for a detailed explanation and demonstration of Bezier curves. The CubicCurve class contains eight properties to specify the four points:
  • startX

  • startY

  • controlX1

  • controlY1

  • controlX2

  • controlY2

  • endX

  • endY

The CubicCurve class contains two constructors:
  • CubicCurve()

  • CubicCurve(double startX, double startY, double controlX1, double controlY1, double controlX2, double controlY2, double endX, double endY)

The program in Listing 14-10 draws the same cubic Bezier curve twice—once with a stroke and a transparent fill and once with no stroke and a light gray fill. Figure 14-14 shows the two curves.
// CubicCurveTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurve;
import javafx.stage.Stage;
public class CubicCurveTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            CubicCurve cc1 = new CubicCurve(0, 50, 20, 0, 50, 80, 50, 0);
            cc1.setFill(Color.TRANSPARENT);
            cc1.setStroke(Color.BLACK);
            CubicCurve cc2 = new CubicCurve(0, 50, 20, 0, 50, 80, 50, 0);
            cc2.setFill(Color.LIGHTGRAY);
            HBox root = new HBox(cc1, cc2);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using CubicCurves");
            stage.show();
        }
}
Listing 14-10

Using the CubicCurve Class to Draw a Cubic Bezier Curve

Figure 14-14

Using cubic Bezier curves

Building Complex Shapes Using the Path Class

I discussed several shape classes in the previous sections. They are used to draw simple shapes. It is not convenient to use them for complex shapes. You can draw complex shapes using the Path class. An instance of the Path class defines the path (outline) of a shape. A path consists of one or more subpaths. A subpath consists of one or more path elements. Each subpath has a starting point and an ending point.

A path element is an instance of the PathElement abstract class. The following subclasses of the PathElement class exist to represent a specific type of path elements:
  • MoveTo

  • LineTo

  • HLineTo

  • VLineTo

  • ArcTo

  • QuadCurveTo

  • CubicCurveTo

  • ClosePath

Before you see an example, let us outline the process of creating a shape using the Path class . The process is similar to drawing a shape on a paper with a pencil. First, you place the pencil on the paper. You can restate it, “You move the pencil to a point on the paper.” Regardless of what shape you want to draw, moving the pencil to a point must be the first step. Now, you start moving your pencil to draw a path element (e.g., a horizontal line). The starting point of the current path element is the same as the ending point of the previous path element. Keep drawing as many path elements as needed (e.g., a vertical line, an arc, and a quadratic Bezier curve). At the end, you can end the last path element at the same point where you started or somewhere else.

The coordinates defining a PathElement can be absolute or relative. By default, coordinates are absolute. It is specified by the absolute property of the PathElement class. If it is true, which is the default, the coordinates are absolute. If it is false, the coordinates are relative. The absolute coordinates are measured relative to the local coordinate system of the node. Relative coordinates are measured treating the ending point of the previous PathElement as the origin.

The Path class contains three constructors:
  • Path()

  • Path(Collection<? extends PathElement> elements)

  • Path(PathElement... elements)

The no-args constructor creates an empty shape. The other two constructors take a list of path elements as arguments. A Path stores path elements in an ObservableList<PathElement>. You can get the reference of the list using the getElements() method. You can modify the list of path elements to modify the shape. The following snippet of code shows two ways of creating shapes using the Path class :
// Pass the path elements to the constructor
Path shape1 = new Path(pathElement1, pathElement2, pathElement3);
// Create an empty path and add path elements to the elements list
Path shape2 = new Path();
shape2.getElements().addAll(pathElement1, pathElement2, pathElement3);
Tip

An instance of the PathElement may be added as a path element to Path objects simultaneously. A Path uses the same fill and stroke for all its path elements.

The MoveTo Path Element

A MoveTo path element is used to make the specified x and y coordinates as the current point. It has the effect of lifting and placing the pencil at the specified point on the paper. The first path element of a Path object must be a MoveTo element, and it must not use relative coordinates. The MoveTo class defines two double properties that are the x and y coordinates of the point:
  • x

  • y

The MoveTo class contains two constructors. The no-args constructor sets the current point to (0.0, 0.0). The other constructor takes the x and y coordinates of the current point as arguments:
// Create a MoveTo path element to move the current point to (0.0, 0.0)
MoveTo mt1 = new MoveTo();
// Create a MoveTo path element to move the current point to (10.0, 10.0)
MoveTo mt2 = new MoveTo(10.0, 10.0);
Tip

A path must start with a MoveTo path element. You can have multiple MoveTo path elements in a path. A subsequent MoveTo element denotes the starting point of a new subpath.

The LineTo Path Element

A LineTo path element draws a straight line from the current point to the specified point. It contains two double properties that are the x and y coordinates of the end of the line:
  • x

  • y

The LineTo class contains two constructors. The no-args constructor sets the end of the line to (0.0, 0.0). The other constructor takes the x and y coordinates of the end of the line as arguments:
// Create a LineTo path element with its end at (0.0, 0.0)
LineTo lt1 = new LineTo();
// Create a LineTo path element with its end at (10.0, 10.0)
LineTo lt2 = new LineTo(10.0, 10.0);
With the knowledge of the MoveTo and LineTo path elements, you can construct shapes that are made of lines only. The following snippet of code creates a triangle as shown in Figure 14-15. The figure shows the triangle and its path elements. The arrows show the flow of the drawing. Notice that the drawing starts at (0.0) using the first MoveTo path element.
Path triangle = new Path(
    new MoveTo(0, 0),
    new LineTo(0, 50),
    new LineTo(50, 50),
    new LineTo(0, 0));
Figure 14-15

Creating a triangle using the MoveTo and LineTo path elements

The ClosePath path element closes a path by drawing a straight line from the current point to the starting point of the path. If multiple MoveTo path elements exist in a path, a ClosePath draws a straight line from the current point to the point identified by the last MoveTo. You can rewrite the path for the previous triangle example using a ClosePath:
Path triangle = new Path(
    new MoveTo(0, 0),
   new LineTo(0, 50),
   new LineTo(50, 50),
   new ClosePath());
The program in Listing 14-11 creates two Path nodes : one triangle and one with two inverted triangles to give it a look of a star as shown in Figure 14-16. In the second shape, each triangle is created as a subpath—each subpath starting with a MoveTo element. Notice the two uses of the ClosePath elements. Each ClosePath closes its subpath.
// PathTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class PathTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
           Path triangle = new Path(
              new MoveTo(0, 0),
              new LineTo(0, 50),
              new LineTo(50, 50),
              new ClosePath());
            Path star = new Path();
            star.getElements().addAll(
               new MoveTo(30, 0),
               new LineTo(0, 30),
               new LineTo(60, 30),
               new ClosePath(),/* new LineTo(30, 0), */
               new MoveTo(0, 10),
               new LineTo(60, 10),
               new LineTo(30, 40),
               new ClosePath() /*new LineTo(0, 10)*/);
            HBox root = new HBox(triangle, star);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Paths");
            stage.show();
        }
}
Listing 14-11

Using the Path Class to Create a Triangle and a Star

Figure 14-16

Shapes based on path elements

The HLineTo and VLineTo Path Elements

The HLineTo path element draws a horizontal line from the current point to the specified x coordinate. The y coordinate of the ending point of the line is the same as the y coordinate of the current point. The x property of the HLineTo class specifies the x coordinate of the ending point:
// Create an horizontal line from the current point (x, y) to (50, y)
HLineTo hlt = new HLineTo(50);
The VLineTo path element draws a vertical line from the current point to the specified y coordinate. The x coordinate of the ending point of the line is the same as the x coordinate of the current point. The y property of the VLineTo class specifies the y coordinate of the ending point:
// Create a vertical line from the current point (x, y) to (x, 50)
VLineTo vlt = new VLineTo(50);
Tip

The LineTo path element is the generic version of HLineTo and VLineTo.

The following snippet of code creates the same triangle as discussed in the previous section. This time, you use HLineTo and VLineTo path elements to draw the base and height sides of the triangle instead of the LineTo path elements:
Path triangle = new Path(
   new MoveTo(0, 0),
   new VLineTo(50),
   new HLineTo(50),
   new ClosePath());

The ArcTo Path Element

An ArcTo path element defines a segment of ellipse connecting the current point and the specified point. It contains the following properties:
  • radiusX

  • radiusY

  • x

  • y

  • XAxisRotation

  • largeArcFlag

  • sweepFlag

The radiusX and radiusY properties specify the horizontal and vertical radii of the ellipse. The x and y properties specify the x and y coordinates of the ending point of the arc. Note that the starting point of the arc is the current point of the path.

The XAxisRotation property specifies the rotation of the x-axis of the ellipse in degrees. Note that the rotation is for the x-axis of the ellipse from which the arc is obtained, not the x-axis of the coordinate system of the node. A positive value rotates the x-axis counterclockwise.

The largeArcFlag and sweepFlag properties are Boolean type, and by default, they are set to false. Their uses need a detailed explanation. Two ellipses can pass through two given points as shown in Figure 14-17 giving us four arcs to connect the two points.
Figure 14-17

Effects of the largeArcFlag and sweepFlag properties on an ArcTo path element

Figure 14-17 shows starting and ending points labeled Start and End, respectively. Two points on an ellipse can be traversed through the larger arc or smaller arc. If the largeArcFlag is true, the larger arc is used. Otherwise, the smaller arc is used.

When it is decided that the larger or smaller arc is used, you still have two choices: which ellipse of the two possible ellipses will be used? This is determined by the sweepFlag property. Try drawing the arc from the starting point to the ending point using two selected arcs—the two larger arcs or the two smaller arcs. For one arc, the traversal will be clockwise and for the other counterclockwise. If the sweepFlag is true, the ellipse with the clockwise traversal is used. If the sweepFlag is false, the ellipse with the counterclockwise traversal is used. Table 14-1 shows which type of arc from which ellipse will be used based on the two properties.
Table 14-1

Choosing the Arc Segment and the Ellipse Based on the largeArcFlag and sweepFlag Properties

largeArcFlag

sweepFlag

Arc Type

Ellipse

true

true

Larger

Ellipse-2

true

false

Larger

Ellipse-1

false

true

Smaller

Ellipse-1

false

false

Smaller

Ellipse-2

The program in Listing 14-12 uses an ArcTo path element to build a Path object . The program lets the user change properties of the ArcTo path element. Run the program and change largeArcFlag, sweepFlag, and other properties to see how they affect the ArcTo path element.
// ArcToTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.HLineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.VLineTo;
import javafx.stage.Stage;
public class ArcToTest extends Application {
        private ArcTo arcTo;
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // Create the ArcTo path element
            arcTo = new ArcTo();
            // Use the arcTo element to build a Path
            Path path = new Path(
               new MoveTo(0, 0),
               new VLineTo(100),
               new HLineTo(100),
               new VLineTo(50),
               arcTo);
            BorderPane root = new BorderPane();
            root.setTop(this.getTopPane());
            root.setCenter(path);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using ArcTo Path Elements");
            stage.show();
        }
        private GridPane getTopPane() {
            CheckBox largeArcFlagCbx = new CheckBox("largeArcFlag");
            CheckBox sweepFlagCbx = new CheckBox("sweepFlag");
            Slider xRotationSlider = new Slider(0, 360, 0);
            xRotationSlider.setPrefWidth(300);
            xRotationSlider.setBlockIncrement(30);
            xRotationSlider.setShowTickMarks(true);
            xRotationSlider.setShowTickLabels(true);
            Slider radiusXSlider = new Slider(100, 300, 100);
            radiusXSlider.setBlockIncrement(10);
            radiusXSlider.setShowTickMarks(true);
            radiusXSlider.setShowTickLabels(true);
            Slider radiusYSlider = new Slider(100, 300, 100);
            radiusYSlider.setBlockIncrement(10);
            radiusYSlider.setShowTickMarks(true);
            radiusYSlider.setShowTickLabels(true);
            // Bind ArcTo properties to the control data
            arcTo.largeArcFlagProperty().bind(
                    largeArcFlagCbx.selectedProperty());
            arcTo.sweepFlagProperty().bind(
                    sweepFlagCbx.selectedProperty());
            arcTo.XaxisRotationProperty().bind(
                    xRotationSlider.valueProperty());
            arcTo.radiusXProperty().bind(
                    radiusXSlider.valueProperty());
            arcTo.radiusYProperty().bind(
                    radiusYSlider.valueProperty());
            GridPane pane = new GridPane();
            pane.setHgap(5);
            pane.setVgap(10);
            pane.addRow(0, largeArcFlagCbx, sweepFlagCbx);
            pane.addRow(1, new Label("XAxisRotation"), xRotationSlider);
            pane.addRow(2, new Label("radiusX"), radiusXSlider);
            pane.addRow(3, new Label("radiusY"), radiusYSlider);
            return pane;
        }
}
Listing 14-12

Using ArcTo Path Elements

The QuadCurveTo Path Element

An instance of the QuadCurveTo class draws a quadratic Bezier curve from the current point to the specified ending point (x, y) using the specified control point (controlX, controlY). It contains four properties to specify the ending and control points.
  • x

  • y

  • controlX

  • controlY

The x and y properties specify the x and y coordinates of the ending point. The controlX and controlY properties specify the x and y coordinates of the control point.

The QuadCurveTo class contains two constructors:
  • QuadCurveTo()

  • QuadCurveTo(double controlX, double controlY, double x, double y)

The following snippet of code uses a QuadCurveTo with the (10, 100) control point and (0, 0) ending point. Figure 14-18 shows the resulting path.
Path path = new Path(
   new MoveTo(0, 0),
   new VLineTo(100),
   new HLineTo(100),
   new VLineTo(50),
   new QuadCurveTo(10, 100, 0, 0));
Figure 14-18

Using a QuadCurveTo path element

The CubicCurveTo Path Element

An instance of the CubicCurveTo class draws a cubic Bezier curve from the current point to the specified ending point (x, y) using the specified control points (controlX1, controlY1) and (controlX2, controlY2). It contains six properties to specify the ending and control points:
  • x

  • y

  • controlX1

  • controlY1

  • controlX2

  • controlY2

The x and y properties specify the x and y coordinates of the ending point. The controlX1 and controlY1 properties specify the x and y coordinates of the first control point. The controlX2 and controlY2 properties specify the x and y coordinates of the second control point.

The CubicCurveTo class contains two constructors:
  • CubicCurveTo()

  • CubicCurveTo(double controlX1, double controlY1, double controlX2, double controlY2, double x, double y)

The following snippet of code uses a CubicCurveTo with the (10, 100) and (40, 80) as control points and (0, 0) as the ending point. Figure 14-19 shows the resulting path.
Path path = new Path(
   new MoveTo(0, 0),
   new VLineTo(100),
   new HLineTo(100),
   new VLineTo(50),
   new CubicCurveTo(10, 100, 40, 80, 0, 0));
Figure 14-19

Using a QuadCurveTo path element

The ClosePath Path Element

The ClosePath path element closes the current subpath. Note that a Path may consist of multiple subpaths, and, therefore, it is possible to have multiple ClosePath elements in a Path. A ClosePath element draws a straight line from the current point to the initial point of the current subpath and ends the subpath. A ClosePath element may be followed by a MoveTo element, and in that case, the MoveTo element is the starting point of the next subpath. If a ClosePath element is followed by a path element other than a MoveTo element, the next subpath starts at the starting point of the subpath that was closed by the ClosePath element.

The following snippet of code creates a Path object, which uses two subpaths. Each subpath draws a triangle. The subpaths are closed using ClosePath elements. Figure 14-20 shows the resulting shape.
Path p1 = new Path(
   new MoveTo(50, 0),
   new LineTo(0, 50),
   new LineTo(100, 50),
   new ClosePath(),
   new MoveTo(90, 15),
   new LineTo(40, 65),
   new LineTo(140, 65),
   new ClosePath());
p1.setFill(Color.LIGHTGRAY);
Figure 14-20

A shape using two subpaths and a ClosePath element

The Fill Rule for a Path

A Path can be used to draw very complex shapes. Sometimes, it is hard to determine whether a point is inside or outside the shape. The Path class contains a fillRule property that is used to determine whether a point is inside a shape. Its value could be one of the constants of the FillRule enum: NON_ZERO and EVEN_ODD. If a point is inside the shape, it will be rendered using the fill color. Figure 14-21 shows two triangles created by a Path and a point in the area common to both triangles. I will discuss whether the point is considered inside the shape.
Figure 14-21

A shape made of two triangular subpaths

The direction of the stroke is the vital factor in determining whether a point is inside a shape. The shape in Figure 14-21 can be drawn using strokes in different directions. Figure 14-22 shows two of them. In Shape-1, both triangles use counterclockwise strokes. In Shape-2, one triangle uses a counterclockwise stroke, and another uses a clockwise stroke.
Figure 14-22

A shape made of two triangular subpaths using different stroke directions

The fill rule of a Path draws rays from the point to infinity, so they can intersect all path segments. In the NON_ZERO fill rule, if the number of path segments intersected by rays is equal in counterclockwise and clockwise directions, the point is outside the shape. Otherwise, the point is inside the shape. You can understand this rule by using a counter, which starts with zero. Add one to the counter for every ray intersecting a path segment in the counterclockwise direction. Subtract one from the counter for every ray intersecting a path segment in the clockwise direction. At the end, if the counter is nonzero, the point is inside; otherwise, the point is outside. Figure 14-23 shows the same two paths made of two triangular subpaths with their counter values when the NON_ZERO fill rule is applied. The rays drawn from the point are shown in dashed lines. The point in the first shape scores six (a nonzero value), and it is inside the path. The point in the second shape scores zero, and it is outside the path.
Figure 14-23

Applying the NON_ZERO fill rule to two triangular subpaths

Like the NON_ZERO fill rule, the EVEN_ODD fill rule also draws rays from a point in all directions extending to infinity, so all path segments are intersected. It counts the number of intersections between the rays and the path segments. If the number is odd, the point is inside the path. Otherwise, the point is outside the path. If you set the fillRule property to EVEN_ODD for the two shapes shown in Figure 14-23, the point is outside the path for both shapes because the number of intersections between rays and path segments is six (an even number) in both cases. The default value for the fillRule property of a Path is FillRule.NON_ZERO.

The program in Listing 14-13 is an implementation of the examples discussed in this section. It draws four paths: the first two (counting from the left) with NON_ZERO fill rules and the last two with EVEN_ODD fill rules. Figure 14-24 shows the paths. The first and third paths use a counterclockwise stroke for drawing both triangular subpaths. The second and fourth paths are drawn using a counterclockwise stroke for one triangle and a clockwise stroke for another.
// PathFillRule.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.stage.Stage;
public class PathFillRule extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            // Both triangles use a counterclockwise stroke
            PathElement[] pathElements1 = {
               new MoveTo(50, 0),
               new LineTo(0, 50),
               new LineTo(100, 50),
               new LineTo(50, 0),
               new MoveTo(90, 15),
               new LineTo(40, 65),
               new LineTo(140, 65),
               new LineTo(90, 15)};
            // One triangle uses a clockwise stroke and
            // another uses a counterclockwise stroke
            PathElement[] pathElements2 = {
               new MoveTo(50, 0),
               new LineTo(0, 50),
               new LineTo(100, 50),
               new LineTo(50, 0),
               new MoveTo(90, 15),
               new LineTo(140, 65),
               new LineTo(40, 65),
               new LineTo(90, 15)};
            /* Using the NON-ZERO fill rule by default */
            Path p1 = new Path(pathElements1);
            p1.setFill(Color.LIGHTGRAY);
            Path p2 = new Path(pathElements2);
            p2.setFill(Color.LIGHTGRAY);
            /* Using the EVEN_ODD fill rule */
            Path p3 = new Path(pathElements1);
            p3.setFill(Color.LIGHTGRAY);
            p3.setFillRule(FillRule.EVEN_ODD);
            Path p4 = new Path(pathElements2);
            p4.setFill(Color.LIGHTGRAY);
            p4.setFillRule(FillRule.EVEN_ODD);
            HBox root = new HBox(p1, p2, p3, p4);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Fill Rules for Paths");
            stage.show();
        }
}
Listing 14-13

Using Fill Rules for Paths

Figure 14-24

Paths using different fill rules

Drawing Scalable Vector Graphics

An instance of the SVGPath class draws a shape from path data in an encoded string. You can find the SVG specification at www.w3.org/TR/SVG. You can find the detailed rules of constructing the path data in string format at www.w3.org/TR/SVG/paths.html. JavaFX partially supports SVG specification.

The SVGPath class contains a no-args constructor to create its object:
// Create a SVGPath object
SVGPath sp = new SVGPath();
The SVGPath class contains two properties:
  • content

  • fillRule

The content property defines the encoded string for the SVG path. The fillRule property specifies the fill rule for the interior of the shape, which could be FillRule.NON_ZERO or FillRule.EVEN_ODD. The default value for the fillRule property is FillRule.NON_ZERO. Please refer to the section “The Fill Rule for a Path” for more details on fill rules. Fill rules for a Path and a SVGPath work the same.

The following snippet of code sets “M50, 0 L0, 50 L100, 50 Z” encoded string as the content for a SVGPath object to draw a triangle as shown in Figure 14-25:
SVGPath sp2 = new SVGPath();
sp2.setContent("M50, 0 L0, 50 L100, 50 Z");
sp2.setFill(Color.LIGHTGRAY);
sp2.setStroke(Color.BLACK);
Figure 14-25

A triangle using a SVGPath

The content of a SVGPath is an encoded string following some rules:
  • The string consists of a series of commands.

  • Each command name is exactly one letter long.

  • A command is followed by its parameters.

  • Parameter values for a command are separated by a comma or a space. For example, “M50, 0 L0, 50 L100, 50 Z” and “M50 0 L0 50 L100 50 Z” represent the same path. For readability, you will use a comma to separate two values.

  • You do not need to add spaces before or after the command character. For example, “M50 0 L0 50 L100 50 Z” can be rewritten as “M50 0L0 50L100 50Z”.

Let us consider the SVG content used in the previous example:
M50, 0 L0, 50 L100, 50 Z
The content consists of four commands:
  • M50, 0

  • L0, 50

  • L100, 50

  • Z

Comparing the SVG path commands with the Path API, the first command is “MoveTo (50, 0)”; the second command is “LineTo(0, 50)”; the third command is “LineTo(100, 50)”; and the fourth command is “ClosePath”.

Tip

The command name in SVGPath content is the first letter of the classes representing path elements in a Path object. For example, an absolute MoveTo in the Path API becomes M in SVGPath content, an absolute LineTo becomes L, and so on.

The parameters for the commands are coordinates, which can be absolute or relative. When the command name is in uppercase (e.g., M), its parameters are considered absolute. When the command name is in lowercase (e.g., m), its parameters are considered relative. The “closepath” command is Z or z. Because the “closepath” command does not take any parameters, both uppercase and lowercase versions behave the same.

Consider the content of two SVG paths :
  • M50, 0 L0, 50 L100, 50 Z

  • M50, 0 l0, 50 l100, 50 Z

The first path uses absolute coordinates. The second path uses absolute and relative coordinates. Like a Path, a SVGPath must start with a “moveTo” command, which must use absolute coordinates. If a SVGPath starts with a relative “moveTo” command (e.g., "m 50, 0"), its parameters are treated as absolute coordinates. In the foregoing SVG paths, you can start the string with "m50, 0", and the result will be the same.

The previous two SVG paths will draw two different triangles, as shown in Figure 14-26, even though both use the same parameters. The first path draws the triangle on the left, and the second one draws the triangle on the right. The commands in the second path are interpreted as follows:
  • Move to (50, 0).

  • Draw a line from the current point (50, 0) to (50, 50). The ending point (50, 50) is derived by adding the x and y coordinates of the current point to the relative “lineto” command (l) parameters. The ending point becomes (50, 50).

  • Draw a line from the current point (50, 50) to (150, 100). Again, the coordinates of the ending point are derived by adding the x and y coordinates of the current point (50, 50) to the command parameter “l100, 50” (the first character in “l100, 50” is the lowercase L, not the digit 1).

  • Then close the path (Z).

Figure 14-26

Using absolute and relative coordinates in SVG paths

Table 14-2 lists the commands used in the content of the SVGPath objects. It also lists the equivalent classes used in the Path API. The table lists the command, which uses absolute coordinates. The relative versions of the commands use lowercase letters. The plus sign (+) in the parameter column indicates that multiple parameters may be used.
Table 14-2

List of SVG Path Commands

Command

Parameter

Command Name

Path API Class

M

(x, y)+

moveto

MoveTo

L

(x, y)+

lineto

LineTo

H

x+

lineto

HLineTo

V

y+

lineto

VLineTo

A

(rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y)+

arcto

ArcTo

Q

(x1, y1, x, y)+

Quadratic Bezier curveto

QuadCurveTo

T

(x, y)+

Shorthand/smooth quadratic Bezier curveto

QuadCurveTo

C

(x1, y1, x2, y2, x, y)+

curveto

CubicCurveTo

S

(x2, y2, x, y)+

Shorthand/smooth curveto

CubicCurveTo

Z

None

closePath

ClosePath

The “moveTo” Command

The “moveTo” command M starts a new subpath at the specified (x, y) coordinates. It may be followed by one or multiple pairs of coordinates. The first pair of coordinates is considered the x and y coordinates of the point, which the command will make the current point. Each additional pair is treated as a parameter for a “lineto” command. If the “moveTo” command is relative, the “lineto” command will be relative. If the “moveTo” command is absolute, the “lineto” command will be absolute. For example, the following two SVG paths are the same:
M50, 0 L0, 50 L100, 50 Z
M50, 0, 0, 50, 100, 50 Z

The “lineto” Commands

There are three “lineto” commands: L, H, and V. They are used to draw straight lines.

The command L is used to draw a straight line from the current point to the specified (x, y) point. If you specify multiple pairs of (x, y) coordinates, it draws a polyline. The final pair of the (x, y) coordinate becomes the new current point. The following SVG paths will draw the same triangle. The first one uses two L commands, and the second one uses only one:
  • M50, 0 L0, 50 L100, 50 L50, 0

  • M50, 0 L0, 50, 100, 50, 50, 0

The H and V commands are used to draw horizontal and vertical lines from the current point. The command H draws a horizontal line from the current point (cx, cy) to (x, cy). The command V draws a vertical line from the current point (cx, cy) to (cx, y). You can pass multiple parameters to them. The final parameter value defines the current point. For example, “M0, 0H200, 100 V50Z” will draw a line from (0, 0) to (200, 0), from (200, 0) to (100, 0). The second command will make (100, 0) as the current point. The third command will draw a vertical line from (100, 0) to (100, 50). The z command will draw a line from (100, 50) to (0, 0). The following snippet of code draws a SVG path as shown in Figure 14-27:
SVGPath p1 = new SVGPath();
p1.setContent("M0, 0H-50, 50, 0 V-50, 50, 0, -25 L25, 0");
p1.setFill(Color.LIGHTGRAY);
p1.setStroke(Color.BLACK);
Figure 14-27

Using multiple parameters to “lineto” commands

The “arcto” Command

The “arcto” command A draws an elliptical arc from the current point to the specified (x, y) point. It uses rx and ry as the radii along x-axis and y-axis. The x-axis-rotation is a rotation angle in degrees for the x-axis of the ellipse. The large-arc-flag and sweep-flag are the flags used to select one arc out of four possible arcs. Use 0 and 1 for flag values, where 1 means true and 0 means false. Please refer to the section “The ArcTo Path Element” for a detailed explanation of all its parameters. You can pass multiple arcs parameters, and in that case, the ending point of an arc becomes the current point for the subsequent arc. The following snippet of code draws two SVG paths with arcs. The first path uses one parameter for the “arcTo” command, and the second path uses two parameters. Figure 14-28 shows the paths.
SVGPath p1 = new SVGPath();
// rx=150, ry=50, x-axis-rotation=0, large-arc-flag=0,
// sweep-flag 0, x=-50, y=50
p1.setContent("M0, 0 A150, 50, 0, 0, 0, -50, 50 Z");
p1.setFill(Color.LIGHTGRAY);
p1.setStroke(Color.BLACK);
// Use multiple arcs in one "arcTo" command
SVGPath p2 = new SVGPath();
// rx1=150, ry1=50, x-axis-rotation1=0, large-arc-flag1=0,
// sweep-flag1=0, x1=-50, y1=50
// rx2=150, ry2=10, x-axis-rotation2=0, large-arc-flag2=0,
// sweep-flag2=0, x2=10, y2=10
p2.setContent("M0, 0 A150 50 0 0 0 -50 50, 150 10 0 0 0 10 10 Z");
p2.setFill(Color.LIGHTGRAY);
p2.setStroke(Color.BLACK);
Figure 14-28

Using “arcTo” commands to draw elliptical arc paths

The “Quadratic Bezier curveto” Command

Both commands Q and T are used to draw quadratic Bezier curves.

The command Q draws a quadratic Bezier curve from the current point to the specified (x, y) point using the specified (x1, y1) as the control point.

The command T draws a quadratic Bezier curve from the current point to the specified (x, y) point using a control point that is the reflection of the control point on the previous command. The current point is used as the control point if there was no previous command or the previous command was not Q, q, T, or t.

The command Q takes the control point as parameters, whereas the command T assumes the control point. The following snippet of code uses the commands Q and T to draw quadratic Bezier curves as shown in Figure 14-29:
SVGPath p1 = new SVGPath();
p1.setContent("M0, 50 Q50, 0, 100, 50");
p1.setFill(Color.LIGHTGRAY);
p1.setStroke(Color.BLACK);
SVGPath p2 = new SVGPath();
p2.setContent("M0, 50 Q50, 0, 100, 50 T200, 50");
p2.setFill(Color.LIGHTGRAY);
p2.setStroke(Color.BLACK);
Figure 14-29

Using Q and T commands to draw quadratic Bezier curves

The “Cubic Bezier curveto” Command

The commands C and S are used to draw cubic Bezier curves.

The command C draws a cubic Bezier curve from the current point to the specified point (x, y) using the specified control points (x1, y1) and (x2, y2).

The command S draws a cubic Bezier curve from the current point to the specified point (x, y). It assumes the first control point to be the reflection of the second control point on the previous command. The current point is used as the first control point if there was no previous command or the previous command was not C, c, S, or s. The specified point (x2, y2) is the second control point. Multiple sets of coordinates draw a polybezier.

The following snippet of code uses the commands C and S to draw cubic Bezier curves as shown in Figure 14-30. The second path uses the command S to use the reflection of the second control point of the previous command C as its first control point:
SVGPath p1 = new SVGPath();
p1.setContent("M0, 0 C0, -100, 100, 100, 100, 0");
p1.setFill(Color.LIGHTGRAY);
p1.setStroke(Color.BLACK);
SVGPath p2 = new SVGPath();
p2.setContent("M0, 0 C0, -100, 100, 100, 100, 0 S200 100 200, 0");
p2.setFill(Color.LIGHTGRAY);
p2.setStroke(Color.BLACK);
Figure 14-30

Using C and S commands to draw cubic Bezier curves

The “closepath” Command

The “closepath” commands Z and z draw a straight line from the current point to the starting point of the current subpath and end the subpath. Both uppercase and lowercase versions of the command work the same.

Combining Shapes

The Shape class provides three static methods that let you perform union, intersection, and subtraction of shapes:
  • union(Shape shape1, Shape shape2)

  • intersect(Shape shape1, Shape shape2)

  • subtract(Shape shape1, Shape shape2)

The methods return a new Shape instance. They operate on the areas of the input shapes. If a shape does not have a fill and a stroke, its area is zero. The new shape has a stroke and a fill. The union() method combines the areas of two shapes. The intersect() method uses the common areas between the shapes to create the new shape. The subtract() method creates a new shape by subtracting the specified second shape from the first shape.

The program in Listing 14-14 combines two circles using the union, intersection, and subtraction operations. Figure 14-31 shows the resulting shapes.
// CombiningShapesTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class CombiningShapesTest extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            Circle c1 = new Circle (0, 0, 20);
            Circle c2 = new Circle (15, 0, 20);
            Shape union = Shape.union(c1, c2);
            union.setStroke(Color.BLACK);
            union.setFill(Color.LIGHTGRAY);
            Shape intersection = Shape.intersect(c1, c2);
            intersection.setStroke(Color.BLACK);
            intersection.setFill(Color.LIGHTGRAY);
            Shape subtraction = Shape.subtract(c1, c2);
            subtraction.setStroke(Color.BLACK);
            subtraction.setFill(Color.LIGHTGRAY);
            HBox root = new HBox(union, intersection, subtraction);
            root.setSpacing(20);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Combining Shapes");
            stage.show();
        }
}
Listing 14-14

Combining Shapes to Create New Shapes

Figure 14-31

Shapes created by combining two circles

Understanding the Stroke of a Shape

Stroking is the process of painting the outline of a shape. Sometimes, the outline of a shape is also known as stroke. The Shape class contains several properties to define the appearance of the stroke of a shape:
  • stroke

  • strokeWidth

  • strokeType

  • strokeLineCap

  • strokeLineJoin

  • strokeMiterLimit

  • strokeDashOffset

The stroke property specifies the color of the stroke. The default stroke is set to null for all shapes except Line, Path, and Polyline, which have Color.BLACK as their default stroke.

The strokeWidth property specifies the width of the stroke. It is 1.0px by default.

The stroke is painted along the boundary of a shape. The strokeType property specifies the distribution of the width of the stroke on the boundary. Its value is one of the three constants, CENTERED, INSIDE, and OUTSIDE, of the StrokeType enum. The default value is CENTERED. The CENTERED stroke type draws a half of the stroke width outside and half inside the boundary. The INSIDE stroke type draws the stroke inside the boundary. The OUTSIDE stroke draws the stroke outside the boundary. The stroke width of a shape is included in its layout bounds.

The program in Listing 14-15 creates four rectangles as shown in Figure 14-32. All rectangles have the same width and height (50px and 50px). The first rectangle, counting from the left, has no stroke, and it has layout bounds of 50px X 50px. The second rectangle uses a stroke of width 4px and an INSIDE stroke type. The INSIDE stroke type is drawn inside the width and height boundary, and the rectangle has the layout bounds of 50px X 50px. The third rectangle uses a stroke width 4px and a CENTERED stroke type, which is the default. The stroke is drawn 2px inside the boundary and 2px outside the boundary. The 2px outside stroke is added to the dimensions of all four making the layout bounds to 54px X 54px. The fourth rectangle uses a 4px stroke width and an OUTSIDE stroke type. The entire stroke width falls outside the width and height of the rectangle making the layouts to 58px X 58px.
// StrokeTypeTest.java
package com.jdojo.shape;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class StrokeTypeTest extends Application {
        public static void main(String[] args) {
            Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
            Rectangle r1 = new Rectangle(50, 50);
            r1.setFill(Color.LIGHTGRAY);
            Rectangle r2 = new Rectangle(50, 50);
            r2.setFill(Color.LIGHTGRAY);
            r2.setStroke(Color.BLACK);
            r2.setStrokeWidth(4);
            r2.setStrokeType(StrokeType.INSIDE);
            Rectangle r3 = new Rectangle(50, 50);
            r3.setFill(Color.LIGHTGRAY);
            r3.setStroke(Color.BLACK);
            r3.setStrokeWidth(4);
            Rectangle r4 = new Rectangle(50, 50);
            r4.setFill(Color.LIGHTGRAY);
            r4.setStroke(Color.BLACK);
            r4.setStrokeWidth(4);
            r4.setStrokeType(StrokeType.OUTSIDE);
            HBox root = new HBox(r1, r2, r3, r4);
            root.setAlignment(Pos.CENTER);
            root.setSpacing(10);
            root.setStyle("""
               -fx-padding: 10;
               -fx-border-style: solid inside;
               -fx-border-width: 2;
               -fx-border-insets: 5;
               -fx-border-radius: 5;
               -fx-border-color: blue;""");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.setTitle("Using Different Stroke Types for Shapes");
            stage.show();
        }
}
Listing 14-15

Effects of Applying Different Stroke Types on a Rectangle

Figure 14-32

Rectangles using different types of strokes

The strokeLineCap property specifies the ending decoration of a stroke for unclosed subpaths and dash segments. Its value is one of the constants of the StrokeLineCap enum: BUTT, SQUARE, and ROUND. The default is BUTT. The BUTT line cap adds no decoration to the end of a subpath; the stroke starts and ends exactly at the starting and ending points. The SQUARE line cap extends the end by half the stroke width. The ROUND line cap adds a round cap to the end. The round cap uses a radius equal to half the stroke width. Figure 14-33 shows three lines, which are unclosed subpaths. All lines are 100px wide using 10px stroke width. The figure shows the strokeLineCap they use. The width of the layout bounds of the line using the BUTT line cap remains 100px. However, for other two lines, the width of the layout bounds increases to 110px—increasing by 10px at both ends.
Figure 14-33

Different line cap styles for strokes

Note that the strokeLineCap properties are applied to the ends of a line segment of unclosed subpaths. Figure 14-34 shows three triangles created by unclosed subpaths. They use different stroke line caps. The SVG path data “M50, 0L0, 50 M0, 50 L100, 50 M100, 50 L50, 0” was used to draw the triangles. The fill was set to null and the stroke width to 10px.
Figure 14-34

Triangles using unclosed subpaths using different stroke line caps

The strokeLineJoin property specifies how two successive path elements of a subpath are joined. Its value is one of the constants of the StrokeLineJoin enum: BEVEL, MITER, and ROUND. The default is MITER. The BEVEL line join connects the outer corners of path elements by a straight line. The MITER line join extends the outer edges of two path elements until they meet. The ROUND line join connects two path elements by rounding their corners by half the stroke width. Figure 14-35 shows three triangles created with the SVG path data “M50, 0L0, 50 L100, 50 Z”. The fill color is null, and the stroke width is 10px. The triangles use different line joins as shown in the figure.
Figure 14-35

Triangles using different stroke line join types

A MITER line join joins two path elements by extending their outer edges. If the path elements meet at a smaller angle, the length of the join may become very big. You can limit the length of the join using the strokeMiterLimit property . It specifies the ratio of the miter length and the stroke width. The miter length is the distance between the most inside point and the most outside point of the join. If the two path elements cannot meet by extending their outer edges within this limit, a BEVEL join is used instead. The default value is 10.0. That is, by default, the miter length may be up to ten times the stroke width.

The following snippet of code creates two triangles as shown in Figure 14-36. Both use a MITER line join by default. The first triangle uses 2.0 as the miter limit. The second triangle uses the default miter limit, which is 10.0. The stroke width is 10px. The first triangle tries to join the corners by extending two lines up to 20px, which is computed by multiplying the 10px stroke width by the miter limit of 2.0. The corners cannot be joined using the MITER join within 20px, so a BEVEL join is used.
SVGPath t1 = new SVGPath();
t1.setContent("M50, 0L0, 50 L100, 50 Z");
t1.setStrokeWidth(10);
t1.setFill(null);
t1.setStroke(Color.BLACK);
t1.setStrokeMiterLimit(2.0);
SVGPath t2 = new SVGPath();
t2.setContent("M50, 0L0, 50 L100, 50 Z");
t2.setStrokeWidth(10);
t2.setFill(null);
t2.setStroke(Color.BLACK);
Figure 14-36

Triangles using different stroke miter limits

By default, the stroke draws a solid outline. You can also have a dashed outline. You need to provide a dashing pattern and a dash offset. The dashing pattern is an array of double that is stored in an ObservableList<Double>. You can get the reference of the list using the getStrokeDashArray() method of the Shape class. The elements of the list specify a pattern of dashes and gaps. The first element is the dash length, the second gap, the third dash length, the fourth gap, and so on. The dashing pattern is repeated to draw the outline. The strokeDashOffset property specifies the offset in the dashing pattern where the stroke begins.

The following snippet of code creates two instances of Polygon as shown in Figure 14-37. Both use the same dashing patterns but a different dash offset. The first one uses the dash offset of 0.0, which is the default. The stroke of the first rectangle starts with a 15.0px dash, which is the first element of the dashing pattern, which can be seen in the dashed line drawn from the (0, 0) to (100, 0). The second Polygon uses a dash offset of 20.0, which means the stroke will start 20.0px inside the dashing pattern. The first two elements 15.0 and 3.0 are inside the dash offset 20.0. Therefore, the stroke for the second Polygon starts at the third element, which is a 5.0px dash.
Polygon p1 = new Polygon(0, 0, 100, 0, 100, 50, 0, 50, 0, 0);
p1.setFill(null);
p1.setStroke(Color.BLACK);
p1.getStrokeDashArray().addAll(15.0, 5.0, 5.0, 5.0);
Polygon p2 = new Polygon(0, 0, 100, 0, 100, 50, 0, 50, 0, 0);
p2.setFill(null);
p2.setStroke(Color.BLACK);
p2.getStrokeDashArray().addAll(15.0, 5.0, 5.0, 5.0);
p2.setStrokeDashOffset(20.0);
Figure 14-37

Two polygons using dashing patterns for their outline

Styling Shapes with CSS

All shapes do not have a default style class name. If you want to apply styles to shapes using CSS, you need to add style class names to them. All shapes can use the following CSS properties:
  • -fx-fill

  • -fx-smooth

  • -fx-stroke

  • -fx-stroke-type

  • -fx-stroke-dash-array

  • -fx-stroke-dash-offset

  • -fx-stroke-line-cap

  • -fx-stroke-line-join

  • -fx-stroke-miter-limit

  • -fx-stroke-width

All CSS properties correspond to the properties in the Shape class, which I have discussed at length in the previous section. Rectangle supports two additional CSS properties to specify arc width and height for rounded rectangles:
  • -fx-arc-height

  • -fx-arc-width

The following snippet of code creates a Rectangle and adds rectangle as its style class name:
Rectangle r1 = new Rectangle(200, 50);
r1.getStyleClass().add("rectangle");
The following style will produce a rectangle as shown in Figure 14-38:
.rectangle {
        -fx-fill: lightgray;
        -fx-stroke: black;
        -fx-stroke-width: 4;
        -fx-stroke-dash-array: 15 5 5 10;
        -fx-stroke-dash-offset: 20;
        -fx-stroke-line-cap: round;
        -fx-stroke-line-join: bevel;
}
Figure 14-38

Applying CSS styles to a rectangle

Summary

Any shape that can be drawn in a two-dimensional plane is called a 2D shape. JavaFX offers various nodes to draw different types of shapes (lines, circles, rectangles, etc.). You can add shapes to a scene graph. All shape classes are in the javafx.scene.shape package. Classes representing 2D shapes are inherited from the abstract Shape class. A shape can have a stroke that defines the outline of the shape. A shape may have a fill.

An instance of the Line class represents a line node. A Line has no interior. By default, its fill property is set to null. Setting fill has no effect. The default stroke is Color.BLACK, and the default strokeWidth is 1.0.

An instance of the Rectangle class represents a rectangle node. The class uses six properties to define the rectangle: x, y, width, height, arcWidth, and arcHeight. The x and y properties are the x and y coordinates of the upper-left corner of the rectangle in the local coordinate system of the node. The width and height properties are the width and height of the rectangle, respectively. Specify the same width and height to draw a square. By default, the corners of a rectangle are sharp. A rectangle can have rounded corners by specifying the arcWidth and arcHeight properties.

An instance of the Circle class represents a circle node. The class uses three properties to define the circle: centerX, centerY, and radius. The centerX and centerY properties are the x and y coordinates of the center of the circle in the local coordinate system of the node. The radius property is the radius of the circle. The default values for these properties are zero.

An instance of the Ellipse class represents an ellipse node. The class uses four properties to define the ellipse: centerX, centerY, radiusX, radiusY. The centerX and centerY properties are the x and y coordinates of the center of the circle in the local coordinate system of the node. The radiusX and radiusY are the radii of the ellipse in the horizontal and vertical directions. The default values for these properties are zero. A circle is a special case of an ellipse when radiusX and radiusY are the same.

An instance of the Polygon class represents a polygon node. The class does not define any public properties. It lets you draw a polygon using an array of (x, y) coordinates defining the vertices of the polygon. Using the Polygon class, you can draw any type of geometric shape that is created using connected lines (triangles, pentagon, hexagon, parallelogram, etc.).

A polyline is similar to a polygon, except that it does not draw a line between the last and first points. That is, a polyline is an open polygon. However, the fill color is used to fill the entire shape as if the shape was closed. An instance of the Polyline class represents a polyline node.

An instance of the Arc class represents a sector of an ellipse. The class uses seven properties to define the ellipse: centerX, centerY, radiusX, radiusY, startAngle, length, and type. The first four properties define an ellipse. The last three properties define a sector of the ellipse that is the Arc node. The startAngle property specifies the start angle of the section in degrees measured counterclockwise from the positive x-axis. It defines the beginning of the arc. The length is an angle in degrees measured counterclockwise from the start angle to define the end of the sector. If the length property is set to 360, the Arc is a full ellipse.

Bezier curves are used in computer graphics to draw smooth curves. An instance of the QuadCurve class represents a quadratic Bezier curve segment intersecting two specified points using a specified Bezier control point.

An instance of the CubicCurve class represents a cubic Bezier curve segment intersecting two specified points using two specified Bezier control points.

You can draw complex shapes using the Path class. An instance of the Path class defines the path (outline) of a shape. A path consists of one or more subpaths. A subpath consists of one or more path elements. Each subpath has a starting point and an ending point. A path element is an instance of the PathElement abstract class. Several subclasses of the PathElement class exist to represent a specific type of path elements; those classes are MoveTo, LineTo, HLineTo, VLineTo, ArcTo, QuadCurveTo, CubicCurveTo, and ClosePath.

JavaFX partially supports SVG specification. An instance of the SVGPath class draws a shape from path data in an encoded string.

JavaFX lets you create a shape by combining multiple shapes. The Shape class provides three static methods named union(), intersect(), and subtract() that let you perform union, intersection, and subtraction of two shapes that are passed as the arguments to these methods. The methods return a new Shape instance. They operate on the areas of the input shapes. If a shape does not have a fill and a stroke, its area is zero. The new shape has a stroke and a fill. The union() method combines the areas of two shapes. The intersect() method uses the common areas between the shapes to create the new shape. The subtract() method creates a new shape by subtracting the specified second shape from the first shape.

Stroking is the process of painting the outline of a shape. Sometimes, the outline of a shape is also known as stroke. The Shape class contains several properties such as stroke, strokeWidth, and so on to define the appearance of the stroke of a shape.

JavaFX lets you style 2D shapes with CSS.

The next chapter will discuss how to handle text drawing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset