ggplot2 coord_polar() in R: Build Polar Coordinate Charts
ggplot2 coord_polar() in R bends the x or y axis of a cartesian plot into a circle, turning bar charts into pie slices, rose plots, and circular bar charts without writing any custom geometry.
coord_polar() # full circle, x mapped to angle coord_polar(theta = "y") # map y to angle (pie chart) coord_polar(theta = "x", start = 0) # rotate the 12 o-clock start coord_polar(theta = "x", direction = -1) # reverse, counter-clockwise coord_polar(theta = "y", clip = "off") # let labels render outside coord_polar() + theme_void() # strip axes for a clean pie geom_bar() + coord_polar() # bar to wind rose geom_col(width = 1) + coord_polar("y") # stacked bar to pie
Need explanation? Read on for examples and pitfalls.
What coord_polar() does in one sentence
coord_polar() is a coordinate transformation, not a geom. It re-maps either the x-axis or the y-axis onto an angular dimension, so a horizontal bar drawn at x = 3 sits at 3 radians around the origin instead of along a straight line. Because the geom data does not change, the same geom_bar() call produces a column under default cartesian coords and a pie slice under coord_polar(theta = "y"). That separation between geometry and coordinates is the engine behind every pie, rose, and circular bar built in ggplot2.
Syntax
The signature has four arguments and two sensible defaults.
theta: maps"x"(rose / circular bar) or"y"(pie) onto the angle.start: rotation offset in radians, applied after the angle mapping.pi / 2puts the first slice at 3 o-clock.direction:1runs slices clockwise,-1counter-clockwise.clip: set to"off"whenever labels need room to render outside the disc.
coord_polar() can do. Reach for coord_polar() for stock pies and rose plots; reach for coord_radial() when you need a donut or a half circle.Five common patterns
Pattern 1: pie chart from a single stacked bar. Build one column with geom_col(), then bend it.
The empty x = "" collapses every row into one column, so the heights stack and the angles map to cumulative share.
Pattern 2: rose plot from frequency counts. Map theta = "x" and let geom_bar() count categories.
Pattern 3: circular bar chart with named items. Same as pattern 2 but rows are individuals, not counts.
Pattern 4: rotate the start angle. Pies look more natural when slice 1 begins at the top (the default in most BI tools).
The direction = -1 matches how Excel and PowerBI draw pies.
Pattern 5: half-pie gauge. Pad the data with an empty value that occupies the unwanted half of the circle.
coord_polar(). As of ggplot2 3.5.0, coord_radial() exposes a real inner.radius argument, so the donut is one line instead of a layered workaround.coord_polar() vs coord_radial() and base R pie()
Pick the function by feature, not by familiarity.
| Feature | coord_polar() | coord_radial() | base pie() |
|---|---|---|---|
| Pie chart | yes | yes | yes |
| Donut (inner radius) | no | yes | no |
| Partial arc / gauge | hack only | yes | no |
| Works inside a ggplot pipeline | yes | yes | no |
| Available pre-ggplot2 3.5.0 | yes | no | yes (base) |
geom_text() label sitting at x = 5 rotates to 5 radians around the origin. Once you internalize that the angle is just the x or y mapping after warping, rose plots and gauges stop feeling like specialty charts.Common pitfalls
Pitfall 1: forgetting width = 1 on geom_col(). The default width = 0.9 leaves white gaps between pie slices.
Set width = 1 and the slices touch cleanly.
Pitfall 2: confusing theta = "x" with theta = "y". Use theta = "y" for pies (a single stacked bar). Use theta = "x" for rose plots (multiple bars around the circle). If a pie renders as a thin ring, the wrong axis is mapped.
Pitfall 3: labels clipped at the panel edge. coord_polar() defaults to clip = "on", which cuts off long labels. Set clip = "off" and pad the plot with theme(plot.margin = margin(20, 20, 20, 20)) so labels have room.
Try it yourself
Try it: Build a pie chart from the diamonds dataset showing the share of each cut grade. Save the plot to ex_pie.
Click to reveal solution
Explanation: count() produces one row per cut grade with column n. The empty x = "" collapses bars into a single column, theta = "y" maps n to angle, and theme_void() strips the cartesian axes.
Related ggplot2 functions
coord_radial(): modern replacement with inner radius and partial arcs.coord_flip(): swaps x and y while keeping cartesian, not polar.coord_fixed(): enforces a fixed aspect ratio for accurate maps and scatter plots.geom_bar(): counts rows by category, the base for rose plots.geom_col(): uses precomputed values, the base for pies and gauges.
FAQ
Why does my pie chart render as a flat horizontal bar?
You probably passed theta = "x" instead of theta = "y". With theta = "x" and a single bar (x = ""), there is no angular variation to draw, so coord_polar collapses the bar into a thin ring. Switch to theta = "y" and the heights of the stacked bar become slice angles. The visual cue: if your pie looks like a doughnut squashed into a pancake, the axis is wrong.
Can I add data labels to a pie made with coord_polar()?
Yes. Compute label positions in the data (cumulative angle midpoints), then add geom_text(aes(label = value), position = position_stack(vjust = 0.5)) before coord_polar(). The labels inherit the polar transformation along with the bars, so they sit on each slice automatically.
Is coord_polar() deprecated in favor of coord_radial()?
No. coord_polar() still works in current ggplot2 and is the right pick for stock pies and rose plots. coord_radial() (ggplot2 3.5.0+) is a superset for donuts, partial arcs, and richer axis customization, but it is overkill for a plain pie.
Why are my bar widths uneven after coord_polar()?
You almost certainly forgot width = 1 on geom_col() or geom_bar(). The default 0.9 leaves gaps between bars; in polar coordinates those gaps render as visible white wedges around the disc. Setting width = 1 makes adjacent bars meet exactly.
Can I draw a true half-circle with coord_polar()?
Not directly. coord_polar() always draws a full 2 pi sweep. The common workaround pads the data with an empty value that occupies the unwanted half (see pattern 5 above). For a real half circle without the padding trick, switch to coord_radial(start = -pi / 2, end = pi / 2).
External reference: ggplot2 coord_polar() docs.