In this document, we’ll learn how to draw an NBA half-court
using R and ggplot2.
This exercise combines data visualisation and creativity — showing how
R’s plotting system can be used to draw detailed shapes, lines, and
features, not just charts.
The example below recreates a Detroit Pistons “The Palace” half-court design using geometric layers and precise coordinate placement.
Before creating the court, we need to load the ggplot2
library, which allows us to draw and layer shapes.
library(ggplot2)
To begin drawing our NBA half-court, we first need to set up the base dimensions — this defines the coordinate space where all the court elements (lines, arcs, and key) will be added.
We’ll use the ggplot() function as our plotting base,
and coord_fixed() to ensure that one unit on the x-axis
equals one unit on the y-axis. This keeps the court proportions
realistic.
# Create empty court with fixed dimensions
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) + # Set court width and height
theme_void() + # Remove gridlines and axes
labs(title = "NBA Half Court Dimensions") +
theme(
plot.title = element_text(hjust = 0.5, size = 18, face = "bold")
)
Now that we’ve defined our court dimensions, the next step is to draw the outer boundary lines that form the edges of the half-court.
These are created using geom_segment(), which draws
straight lines between two coordinate points — an (x, y)
start and an (xend, yend) end.
We’ll use the Pistons’ team colour blue
(#1D428A) for these lines.
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary Lines
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) + # Left line
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) + # Top line
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) + # Right line
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) + # Bottom line
theme_void() +
labs(title = "Detroit Pistons – The Palace") +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
Next, we’ll draw the key, also known as the
paint area, which sits directly under the basket.
This is the rectangular zone where players position themselves for
rebounds and free throws.
We’ll again use the geom_segment() function to draw the
sides and top of the key.
For styling, we’ll switch to the Pistons’ red colour
(#C8102E) to highlight this section against the
blue border.
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary (Royal Blue)
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) +
# Key (Paint Area – Red)
geom_segment(aes(x = 17, y = 0.175, xend = 17, yend = 19), colour = "#C8102E", linewidth = 1.1) + # Left edge
geom_segment(aes(x = 33, y = 0.175, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) + # Right edge
geom_segment(aes(x = 17, y = 19, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) + # Top of key
theme_void() +
labs(title = "Detroit Pistons – The Palace") +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
The next component of the half-court is the Free Throw Line and the Free Throw Circle — the curved area players stand around when shooting free throws.
We’ll draw the line and arc using geom_segment() and
geom_curve().
The dashed curve represents the outer circular guideline, and the solid
curve represents the actual free throw arc.
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) +
# Key (Paint Area)
geom_segment(aes(x = 17, y = 0.175, xend = 17, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 33, y = 0.175, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 17, y = 19, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
# Free Throw Line and Circle
geom_curve(aes(x = 25 - 6, y = 19, xend = 25 + 6, yend = 19),
colour = "#1D428A", linewidth = 1, linetype = "dashed", curvature = 0.8) + # Outer dashed arc
geom_curve(aes(x = 25 + 6, y = 19, xend = 25 - 6, yend = 19),
colour = "#1D428A", linewidth = 1.2, curvature = 0.8) + # Inner solid arc # Inner arc
theme_void() +
labs(title = "Detroit Pistons – The Palace") +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
The three-point line is one of the most recognisable
features of a basketball court.
It consists of two straight lines extending up from the baseline and a
large curved arc that connects them.
We’ll use geom_segment() for the straight edges in each
corner, and geom_curve() for the main three-point arc.
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) +
# Key and Free Throw Circle
geom_segment(aes(x = 17, y = 0.175, xend = 17, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 33, y = 0.175, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 17, y = 19, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_curve(aes(x = 25 - 6, y = 19, xend = 25 + 6, yend = 19),
colour = "#1D428A", linewidth = 1, linetype = "dashed", curvature = 0.80) +
geom_curve(aes(x = 25 + 6, y = 19, xend = 25 - 6, yend = 19),
colour = "#1D428A", linewidth = 1.2, curvature = 0.80) +
# Three-Point Line
geom_segment(aes(x = 3, y = 0, xend = 3, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Left corner
geom_segment(aes(x = 47, y = 0, xend = 47, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Right corner
geom_curve(aes(x = 3, y = 14, xend = 47, yend = 14),
colour = "#1D428A", linewidth = 1.1, curvature = -0.55) + # Arc
theme_void() +
labs(title = "Detroit Pistons – The Palace") +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
Now we’ll add the features directly under the hoop — the
backboard, the rim, and the
restricted area.
These are central to the scoring zone and help bring the court to life
visually.
We’ll use: - geom_segment() for the
backboard,
- geom_point() for the rim, and
- geom_curve() for the restricted area
(the curved line under the hoop).
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) +
# Key and Free Throw Circle
geom_segment(aes(x = 17, y = 0.175, xend = 17, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 33, y = 0.175, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 17, y = 19, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_curve(aes(x = 25 - 6, y = 19, xend = 25 + 6, yend = 19),
colour = "#1D428A", linewidth = 1, linetype = "dashed", curvature = 0.80) +
geom_curve(aes(x = 25 + 6, y = 19, xend = 25 - 6, yend = 19),
colour = "#1D428A", linewidth = 1.2, curvature = 0.80) +
# Three-Point Line
geom_segment(aes(x = 3, y = 0, xend = 3, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Left corner
geom_segment(aes(x = 47, y = 0, xend = 47, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Right corner
geom_curve(aes(x = 3, y = 14, xend = 47, yend = 14),
colour = "#1D428A", linewidth = 1.1, curvature = -0.55) + # Arc
# Backboard
geom_segment(aes(x = 25 - 3, y = 4, xend = 25 + 3, yend = 4),
colour = "black", linewidth = 1.5) +
# Rim
geom_point(aes(x = 25, y = 4.5), colour = "orange", size = 8) +
geom_point(aes(x = 25, y = 4.5), colour = "#F5DEB3", size = 5.5) +
# Restricted Area
geom_curve(aes(x = 25 - 4, y = 4.5, xend = 25 + 4, yend = 4.5),
colour = "#C8102E", linewidth = 1.2, curvature = -1) +
theme_void() +
labs(title = "Detroit Pistons – The Palace") +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
The last visual element to complete our NBA half-court is the
Half-Court Circle — the large arc seen at centre
court.
This helps balance the layout and signals where jump balls occur at the
start of the game.
We’ll use geom_curve() again to draw two arcs:
- one large outer arc, and
- one smaller inner arc, to give a clean, layered effect.
We’ll also finalise the overall styling using
theme_void() and element_rect() to set the
background colour.
court <- ggplot() +
coord_fixed(xlim = c(0, 50), ylim = c(0, 47)) +
# Outer Boundary
geom_segment(aes(x = 0, y = 0, xend = 0, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 0, y = 47, xend = 50, yend = 47), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 47, xend = 50, yend = 0), colour = "#1D428A", linewidth = 1.2) +
geom_segment(aes(x = 50, y = 0, xend = 0, yend = 0), colour = "#1D428A", linewidth = 1.2) +
# Key and Free Throw Circle
geom_segment(aes(x = 17, y = 0.175, xend = 17, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 33, y = 0.175, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_segment(aes(x = 17, y = 19, xend = 33, yend = 19), colour = "#C8102E", linewidth = 1.1) +
geom_curve(aes(x = 25 - 6, y = 19, xend = 25 + 6, yend = 19),
colour = "#1D428A", linewidth = 1, linetype = "dashed", curvature = 0.80) +
geom_curve(aes(x = 25 + 6, y = 19, xend = 25 - 6, yend = 19),
colour = "#1D428A", linewidth = 1.2, curvature = 0.80) +
# Three-Point Line
geom_segment(aes(x = 3, y = 0, xend = 3, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Left corner
geom_segment(aes(x = 47, y = 0, xend = 47, yend = 14), colour = "#1D428A", linewidth = 1.1) + # Right corner
geom_curve(aes(x = 3, y = 14, xend = 47, yend = 14),
colour = "#1D428A", linewidth = 1.1, curvature = -0.55) + # Arc
# Backboard
geom_segment(aes(x = 25 - 3, y = 4, xend = 25 + 3, yend = 4),
colour = "black", linewidth = 1.5) +
# Rim
geom_point(aes(x = 25, y = 4.5), colour = "orange", size = 8) +
geom_point(aes(x = 25, y = 4.5), colour = "#F5DEB3", size = 5.5) +
# Restricted Area
geom_curve(aes(x = 25 - 4, y = 4.5, xend = 25 + 4, yend = 4.5),
colour = "#C8102E", linewidth = 1.2, curvature = -1) +
#Final Touches
labs(title = "Detroit Pistons – The Palace") +
theme_void() +
theme(
plot.title = element_text(hjust = 0.5, size = 18, face = "bold", colour = "#C8102E"),
plot.background = element_rect(fill = "#F5DEB3", colour = NA)
)
court
In this project, we learned how to create a fully customised
NBA half-court using R and the ggplot2
package.
By layering geometric shapes such as geom_segment(),
geom_curve(), and geom_point(), we were able
to draw realistic features including the key,
three-point line, rim, and
half-court circle.
This exercise demonstrates how R’s visualisation tools extend beyond traditional charts and graphs. Allowing us to create precise, scalable, and data-driven graphics entirely through code.
Now that the court layout is complete, this base can serve as a powerful visual foundation for further basketball analytics work.
For example, we could: - Overlay shot location data
to create a heatmap of player performance,
- Highlight specific zones such as mid-range or
three-point areas, or
- Animate player movements over time for advanced
visual storytelling.
Overall, building this court reinforces key ggplot2
concepts like layering, coordinate scaling, and shape customisation —
while combining creativity and data visualisation into one powerful
workflow.