Shape Intersection, Subtraction, and Union
- Skill Level Intermediate
- Supported Versions JavaFX 1.3
- Key Features Shapes, Intersection, Subtraction, Union
- Last Updated April 2010
Learn how to create new shapes by using shape intersection, subtraction, and union in the JavaFX Script programming language.
The JavaFX API Desktop Profile has classes to create common shapes: rectangles, circles, ellipses, polygons, and shapes with complex paths. But you can create many other shapes, including semicircles, crescents, and curves, by performing union, intersection, and subtraction operations on two or more shapes. This article describes union, intersection, and subtraction operations in JavaFX and shows some simple and more advanced examples of each type of operation.
Constructive Area Geometry Operations
Constructive area geometry operations perform Boolean operations on two or more existing shapes. These Boolean shape operations are implemented as node types in the JavaFX ShapeIntersect and ShapeSubtract classes. The following Boolean shape operations are implemented in JavaFX, assuming two shapes, Shape 1 and Shape 2:
- Intersection, or Boolean AND
This operation calculates the geometric outline that contains the area common to both Shape 1 and Shape 2. Use the (ShapeIntersectclass.) - Subtraction, or Boolean NOT
This operation calculates the geometric outline of the area in Shape 1 minus the area in Shape 2. (Use theShapeSubtractclass.) - Union, or Boolean OR
This operation calculates the geometric outline that contains the area of both Shape 1 and Shape 2. (Assign both Shape 1 and Shape 2 to the sequence variableaof either theShapeIntersectorShapeSubtractclass, which creates a union.)
The ShapeIntersect and ShapeSubtract class have the following common properties:
ShapeIntersectandShapeSubtractare in the Desktop profile of the API, which means they cannot be used for mobile deployment.ShapeIntersectandShapeSubtracthave two variables,aandb, which contain the member shapes to be intersected or subtracted. Bothaandbare sequence variables, which means they can contain more than one member shape. Sequences within a variable are combined in Union operations before being operated against the other variable.- All nongeometric variables of member shapes are ignored. Variables such as
fillandstrokeshould be set on theShapeSubtractorShapeIntersectobject instance. ShapeIntersectandShapeSubtractdo not respond to binding changes in variables of the component shapes. For example, the width of the shapes defined in theaandbvariables of theShapeSubtractinstance cannot be bound to other variables such as scene width or scene height. This means that animations that involve dynamic changes to one of the member width or height variables are not possible.ShapeIntersectandShapeSubtractdo not recognize transformations applied to member shapes.Lineinstances do not work as member shapes ofShapeIntersectandShapeSubtract. However,PolylineandPathinstances work as long as they construct a shape with an internal geometric area.- If you want to rotate
ShapeIntersectorShapeSubtractobjects, you might need to use theNodeclass transforms variable with aRotateclass instance instead of therotatevariable. Therotatevariable calculates the center point of the object automatically, and this calculation can be unpredictable forShapeIntersectorShapeSubtractobjects. By using aRotateclass instance, you can specify the pivot point. For more information about how to do transformations, see the related links at the end of this article.
Simple Examples of Intersection, Subtraction, and Union
The following example shows a classic Venn diagram. Because the three circles overlap, they are a good example to demonstrate intersection, subtraction, and union.
Figure 1: Classic Venn Diagram
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
Stage {
title: "Venn Diagram"
scene: Scene {
width: 90
height: 150
content: [
//Circle 1
Circle {
centerX: 60
centerY: 60
radius: 30
fill: Color.YELLOW
opacity: 0.5
}
//Circle 2
Circle {
centerX: 45
centerY: 85.98
radius: 30
fill: Color.INDIANRED
opacity: 0.5
}
//Circle 3
Circle {
centerX: 75
centerY: 85.98
radius: 30
fill: Color.BLUE
opacity: 0.5
}
]
}
}
Shape Intersection
The ShapeIntersect class intersects the unions of each sequence in variable a and b, as in (A1 OR A2 OR A3) AND (B1 OR B2 OR B3).
If Circle 1 in the Venn diagram is assigned to sequence a, and Circles 2 and 3 in the Venn diagram are assigned to sequence b, the following shape is produced, which consists of the Circle 1 intersected with the union of Circles 2 and 3. The code example adds a linear gradient plus a reflection effect.
Figure 2: Venn Two-Way Intersection
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ShapeIntersect;
import javafx.scene.effect.Reflection;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
def shapeintersect = ShapeIntersect {
a:
Circle {
centerX: 60
centerY: 60
radius: 30
fill: Color.YELLOW
opacity: 0.5
}
b: [
Circle {
centerX: 45
centerY: 85.98
radius: 30
fill: Color.INDIANRED
opacity: 0.5
},
Circle {
centerX: 75
centerY: 85.98
radius: 30
fill: Color.BLUE
opacity: 0.5
}
]
fill: LinearGradient {
startX: 0.5, startY: 0.0, endX: 1.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.CRIMSON }
Stop { offset: 1.0 color: Color.DARKRED }
]
}
effect: Reflection {fraction: 0.6 }
}
Stage {
title: "Intersect"
scene: Scene {
width: 90
height: 150
content: shapeintersect
}
}
You can obtain the intersection of only two shapes at a time, so obtaining the intersection of all three circles requires two ShapeIntersect object instances. The first ShapeIntersect instance takes the intersection of any two of the circles, and the second ShapeIntersect instance takes the result of the first intersection and intersects it with the third circle.
Figure 3: Venn Three-Way Intersection
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ShapeIntersect;
def shapeintersect1 = ShapeIntersect {
a: Circle {
centerX: 60
centerY: 60
radius: 30
fill: Color.YELLOW
opacity: 0.5
}
b: Circle {
centerX: 45
centerY: 85.98
radius: 30
fill: Color.INDIANRED
opacity: 0.5
}
}
def shapeintersect2 = ShapeIntersect {
a: shapeintersect1
b: Circle {
centerX: 75
centerY: 85.98
radius: 30
fill: Color.BLUE
opacity: 0.5
}
}
Stage {
title: "Venn Diagram"
scene: Scene {
width: 90
height: 150
content: shapeintersect2
}
}
Shape Subtraction
In terms of Boolean logic, shape subtraction equates to a NOT b. With the ShapeSubtract class, the union of all shapes in variable b is subtracted from the union of all shapes in variable a. The following images show some of the possible outcomes of the Venn diagram, depending on whether they are assigned to variable a or variable b.
Figure 4: Examples of Shape Subtraction
The following code example corresponds to the image on the right.
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
Stage {
title: "Subtraction"
scene: Scene {
width: 90
height: 150
content: [
ShapeSubtract {
a: Circle {
centerX: 60
centerY: 60
radius: 30
fill: Color.YELLOW
opacity: 0.5
}
b: [ Circle {
centerX: 45
centerY: 85.98
radius: 30
fill: Color.INDIANRED
opacity: 0.5
},
Circle {
centerX: 75
centerY: 85.98
radius: 30
fill: Color.BLUE
opacity: 0.5
}
]
fill: LinearGradient {
startX: 0.0, startY: 0.0, endX: 1.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.WHITE }
Stop { offset: 1.0 color: Color.GRAY }
]
}
stroke: Color.BLACK
effect: DropShadow {offsetX: 2 offsetY: 4}
}
]
}
}
Shape Union
In the JavaFX Script programming language, shape union can be accomplished by listing the shapes in variable a of either the ShapeIntersect or ShapeSubtract class. Without variable b, only the union of shapes in variable a will occur, with no intersection or subtraction.
The following code creates the union of the three circles in the Venn diagram. A drop shadow effect plus a linear gradient and stroke are added to the ShapeSubtract object instance.
Figure 5: Union of Venn Diagram Circles
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
Stage {
title: "Union"
scene: Scene {
width: 90
height: 150
content: [
ShapeSubtract {
a: [
Circle {
centerX: 60
centerY: 60
radius: 30
}
Circle {
centerX: 45
centerY: 85.98
radius: 30
}
Circle {
centerX: 75
centerY: 85.98
radius: 30
}
]
fill: LinearGradient {
startX: 0.0, startY: 0.0, endX: 1.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.WHITE }
Stop { offset: 1.0 color: Color.GRAY }
]
}
stroke: Color.BLACK
effect: DropShadow {offsetX: 2 offsetY: 4}
}
]
}
}
Advanced Topics
The following topics demonstrate some effects and UI controls that you can construct in the JavaFX Script programming language by using shape union, subtraction, and intersection.
Tool Tip or Caption Box
The following example demonstrates a tool tip or caption box, created by taking the union of a rounded rectangle plus two lines created with the Polyline class. By using variables for the rectangle's position and radius, the definition text and caption box pointer can be placed to move with the rectangle if its location changes.
When the application runs, the caption box becomes visible when the mouse cursor is hovered over the word. To enable the caption box to appear when the mouse cursor is anywhere over the word, including the white space inside of and between letters, the following must happen:
- A group must be created that includes the text and a transparent rectangle.
- The mouse event must be defined for the group.
Figure 6: Tool Tip or Caption Box
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.input.MouseEvent;
import javafx.scene.Group;
import javafx.stage.Stage;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Polyline;
def scenewidth = 300;
def sceneheight = 200;
def rectanglex = 100;
def rectangley = 90;
def rectanglewidth = 180;
def rectangleheight = 80;
var visible:Boolean = false; //controls whether definition is visible
// Definition term
def term: Text = Text {
content: "in·ter·sperse"
font: Font {
name: "Times New Roman"
size: 20 }
fill: Color.DARKGREEN
x: 20
y: 50
}
//Transparent rectangle over text to make clicking easier
def test = Rectangle {
x: 20
y: 30
width: 150
height: 30
fill: Color.GRAY
opacity: 0.0
}
// Group with term and transparent rectangle and mouse event.
def termgroup = Group {
content: [
test, term
]
onMouseEntered: function( e: MouseEvent ):Void {
visible = true;
}
onMouseExited: function( e: MouseEvent ):Void {
visible = false;
}
}
// Caption box shape
def captionbox = ShapeSubtract {
a: [ Rectangle {
x: rectanglex
y: rectangley
width: rectanglewidth
height: rectangleheight
arcHeight: 14
arcWidth: 14
},
Polyline {
points: [
rectanglex, rectangley,
rectanglex - 40, rectangley - 30,
rectanglex + 20, rectangley + 50
]
}
]
fill: null
stroke: Color.LIGHTGREY
strokeWidth: 2
//visible: bind visible
}
// Definition text
def definition = Text {
content: "to diversify or adorn with things set or scattered at intervals"
wrappingWidth: rectanglewidth - 40
font: Font {
name: "Times New Roman"
size: 12
}
fill: Color.GREY
x: rectanglex + 20
y: rectangley + 20
}
def captiongroup: Group = Group {
content: [ captionbox, definition ]
visible: bind visible
}
Stage {
title: "Caption Box"
scene: Scene {
width: scenewidth
height: sceneheight
content: [ termgroup, captiongroup ]
}
}
Citation for definition: "intersperse." Webster's Third New International Dictionary, Unabridged. 2002. [http://unabridged.merriam-webster.com], accessed November 11, 2009).
Next Button Created by Intersection, Subtraction, and Union
Triangles are created with the Polygon class, but you can make your triangle a little more unusual by adding one rounded edge. In this example, the triangle is embedded in a rounded rectangle with the text "Next" subtracted from it to achieve a cutout effect.
Figure 7: Next Button as It Appears on Windows and Mac OS X
The steps to create this button are shown in the following subtasks.
Task 1: Create the Triangle
Triangles are typically created by using an instance of the Polygon class. The coordinates for this triangle were taken from Building GUI Applications With JavaFX Lesson 4: Creating Graphical Objects, and the fill color was changed. The exact placement in the scene doesn't matter right now, because the final object can be placed elsewhere. The fill color doesn't matter either, because it will be ignored when the shape is combined with the curve.
Figure 8: Triangle for Next Button
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.Scene;
import javafx.stage.Stage;
Stage {
title: "Triangle"
scene: Scene {
width: 200
height: 120
content: [
Polygon {
points: [162.0, 61.0, 130.0, 43.0, 130.0, 79.0]
fill: Color.TURQUOISE
}
]
}
}
Task 2: Create the Curve Using Shape Intersection
The curve at the end of the triangle is created by taking the intersection of a circle and the mirror image of the triangle. Use the following series of steps:
- Create a mirror shape of the original triangle, as shown in (a).
- Create a circle whose center point is the right tip of the original triangle, and whose radius is the same as the distance from the tip to the base of the original triangle, as shown in (b).
- Use the ShapeIntersect class to create an intersection between the mirrored (gray) triangle and the black circle, as shown in (c) in Figure 9 and Code Example 8.
Figure 9: Triangle Intersected With Circle
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.ShapeIntersect;
//Create the curve by intersecting a circle
// with a mirror image of the triangle
def curve = ShapeIntersect {
a: Circle {
centerX: 162
centerY: 61
radius: 36
}
b: Polygon {
points: [98.0, 61.0, 130.0, 43.0, 130.0, 79.0]
}
}
Stage {
title: "Rounded portion of triangle"
scene: Scene {
width: 200
height: 120
content: [
curve
]
}
}
Task 3: Combine the Intersected Curve With the Original Triangle
To produce the rounded triangle, take the union of the curve shape and the original triangle. As described previously, shape union is accomplished by using the a sequence variable of the ShapeSubtract class. The following image and code show the rounded rectangle, to which a fill color, reduced opacity, and a drop shadow have been applied.
Figure 10: Rounded Triangle
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.ShapeIntersect;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.Color;
import javafx.scene.shape.ShapeSubtract;
//Create the curve by intersecting a circle
// with a mirror image of the triangle
def curve = ShapeIntersect {
a: Circle {
centerX: 162
centerY: 61
radius: 36
}
b: Polygon {
points: [98.0, 61.0, 130.0, 43.0, 130.0, 79.0]
}
}
//Create a union of the curve plus the triangle
def arrow = ShapeSubtract {
a: [Polygon {
points: [162.0, 61.0, 130.0, 43.0, 130.0, 79.0]
},
curve
]
// Set fill and effects on the subtracted shape
fill: Color.web("#1F6592")
//Decrease the opacity and add a drop shadow
opacity: 0.7
effect: DropShadow {offsetY: 2}
// Reposition the triangle in the center of the scene
}
Stage {
title: "Rounded Rectangle"
scene: Scene {
width: 200
height: 200
content: [
arrow
]
}
}
Task 4: Give the Arrow a 3-D Appearance
A common design trick to give the object more of a 3-D appearance is to create a darker-colored curve with low opacity on the lower half of the shape. In this case, the darker curve is created by defining a circle that overlaps the triangle in a small portion of its arc, as shown in (a) in Figure 11. The intersection of the rounded rectangle and the circle is taken, leaving the small portion of the arc shown in (b).
Figure 11: Creating the Dark Underside of the Rounded Triangle
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ShapeIntersect;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.effect.DropShadow;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.ShapeSubtract;
//Create the curve by intersecting a circle
// with a mirror image of the triangle
def curve = ShapeIntersect {
a: Circle {
centerX: 162
centerY: 61
radius: 36
}
b: Polygon {
points: [98.0, 61.0, 130.0, 43.0, 130.0, 79.0]
}
}
//Create a union of the curve plus the triangle
def arrow = ShapeSubtract {
a: [Polygon {
points: [162.0, 61.0, 130.0, 43.0, 130.0, 79.0]
},
curve
]
// Set fill and effects on the subtracted shape
fill: Color.web("#1F6592")
//Decrease the opacity and add a drop shadow
opacity: 0.7
effect: DropShadow {offsetY: 2}
// Reposition the triangle in the center of the scene
}
// Create the shadow on the lower half of the arrow
def shadow = ShapeIntersect {
a: arrow,
b: Circle {
centerX: 160
centerY: 98
radius: 40
}
fill: Color.web("#245D82")
opacity: 0.5
}
Stage {
title: "Curved Arrow With Shadow"
scene: Scene {
width: 200
height: 120
content: [
arrow, shadow
]
}
}
Task 5: Create the Enclosing Rectangle, Text Cutout, and Background
Text can be subtracted from other shapes to create a cutout effect. In this case, a gray rectangle is created, the text is subtracted, and a drop shadow effect is applied to the subtracted shape. Because the drop shadow applies to the cutout text as well as to the outer edges of the gray rectangle, a rectangle of the same size but darker in color is inserted underneath. This manipulation reduces the drop shadow contrast in the cutout text, particularly when run on Windows.
Note: A type of intersection in which the upper layer is transparent and enables the lower layer to show through is called clipping. Clipping applies to all nodes, not just geometric area shapes. For more information, see How do I clip shapes?
Figure 12: Creating the Rectangle With Cutout Text
import javafx.scene.paint.Color;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.effect.DropShadow;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
import javafx.scene.text.FontWeight;
// Create a background rectangle to darken the subtracted text
def background = Rectangle {
x: 20
y: 25
width: 160
height: 75
arcHeight: 20
arcWidth: 20
fill: Color.GREY
}
// Create the rounded rectangle with text subtracted
def textbox = ShapeSubtract {
a: Rectangle {
x: 20
y: 25
width: 160
height: 75
arcHeight: 20
arcWidth: 20
}
b: Text {
content: "Next"
textOrigin: TextOrigin.TOP
font: Font.font("Verdana", FontWeight.EXTRA_BOLD, 30)
x: 35
y: 48
}
// Set fill and effects on the subtracted shape
fill: Color.DARKGRAY
effect: DropShadow {offsetY: 2}
}
Stage {
title: "Next button"
scene: Scene {
width: 200
height: 120
content: [
background, textbox
]
}
}
Task 6: Group, Resize, and Move the Objects
To make the button smaller or larger, combine the objects into a Group node, then use the scaleX and scaleY variables to resize the group. In this case, the object has been reduced to half its original size.
Use the layoutX and layoutY variables to move the button into position in the scene.
Figure 13 and Code Example 12 are the entire application to create the Next button, including all of the previous steps.
Figure 13: Next Button at 50% Size and Moved to New Position
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.ShapeIntersect;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.effect.DropShadow;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
import javafx.scene.text.FontWeight;
import javafx.scene.Group;
//Create the curve by intersecting a circle
// with a mirror image of the triangle
def curve = ShapeIntersect {
a: Circle {
centerX: 162
centerY: 61
radius: 36
}
b: Polygon {
points: [98.0, 61.0, 130.0, 43.0, 130.0, 79.0]
}
}
//Create a union of the curve plus the triangle
def arrow = ShapeSubtract {
a: [Polygon {
points: [162.0, 61.0, 130.0, 43.0, 130.0, 79.0]
},
curve
]
// Set fill and effects on the subtracted shape
fill: Color.web("#1F6592")
//Decrease the opacity and add a drop shadow
opacity: 0.7
effect: DropShadow {offsetY: 2}
// Reposition the triangle in the center of the scene
}
// Create the shadow on the lower half of the arrow
def shadow = ShapeIntersect {
a: arrow,
b: Circle {
centerX: 160
centerY: 98
radius: 40
}
fill: Color.web("#245D82")
opacity: 0.5
}
// Create a background rectangle to darken the subtracted text
def background = Rectangle {
x: 20
y: 25
width: 160
height: 75
arcHeight: 20
arcWidth: 20
fill: Color.GREY
}
// Create the rounded rectangle with text subtracted
def textbox = ShapeSubtract {
a: Rectangle {
x: 20
y: 25
width: 160
height: 75
arcHeight: 20
arcWidth: 20
}
b: Text {
content: "Next"
textOrigin: TextOrigin.TOP
font: Font.font("Verdana", FontWeight.EXTRA_BOLD, 30)
x: 35
y: 48
}
// Set fill and effects on the subtracted shape
fill: Color.DARKGRAY
effect: DropShadow {offsetY: 2 }
}
//Group all of the objects, size to 50% and move
def button = Group {
content: [background, textbox, arrow, shadow]
scaleX: 0.5
scaleY: 0.5
layoutX: -20
layoutY: -20
}
Stage {
title: "Next button"
scene: Scene {
width: 200
height: 120
content: [
button
]
}
}
Related Links
- Jigsaw Created With Boolean Shape operations
This sample demonstrations the construction of puzzle-piece shapes by using theShapeSubtractclass. - How do I change an object's size?
This how-to topic shows several ways to resize nodes in the GUI. - Enhance Your Application by Applying Transformations
This article provides an introduction to transformations, and compares several ways to implement them.
Nancy Hildebrandt
Technical Writer, Oracle Corporation
Vaibhav Choudhary
Member of Technical Staff, Sun Microsystems