For AI agents: the complete XY documentation index is at llms.txt. Markdown versions are available by appending .md or sending Accept: text/markdown.
Chart Gallery

/

Polar Charts in Python

Polar Charts in Python

A polar chart (also called a polar plot or polar graph) places each observation by an angle (theta, or θ) and a distance from the center (radius, or r). It is a natural fit for cyclic measurements, directional observations, antenna patterns, radar comparisons, and wind distributions.

Jump to a polar line chart, the angular axis, or supported marks and limits.

XY uses the same composition model as its Cartesian charts. Put line, scatter, area, bar, column, heatmap, contour, or errorbar marks inside polar_chart(). Focused helpers build radar, radial bar, and wind rose charts on the same coordinate system.

Create a Polar Line Chart

The first mark channel becomes θ and the second becomes r. Angles use radians by default:

Without an authored sector, polar_chart() draws a full circular frame. Its angular axis defaults to 0..2π, and a linear radial axis starts at zero and ends at the largest radius. Pass domain= to r_axis() when the radial view must stay fixed.

Map a Polar Field

Heatmap cells follow rings and spokes rather than stretching a Cartesian image into a circle. Contours share that projection, so they can be layered over the field with one colorbar:

The polar heatmap path inverse-samples the source grid at the requested browser or export resolution. Pixels inside a hole or outside an authored sector stay transparent. Contours remain projected vector geometry.

Choose a Polar Chart Type

The overview owns numeric theta/r data and shared coordinate-system behavior. Use a focused page when the data already matches one of these higher-level compositions.

Compare Categories with a Radar Chart

Radar charts space named dimensions evenly around the frame and close filled or outlined profiles automatically.

Draw Radial Bars

Radial bar charts turn bars into annular sectors with scalar or per-item angular widths and configurable inner radii.

Compose a Pie or Donut

The pie and donut guide turns unequal-width sectors and base= into polished share, progress-ring, revenue-mix, and gauge blocks.

Summarize Wind with a Wind Rose

Wind rose charts bin raw compass bearings into directional sectors and stack their counts by speed band.

Configure the Angular Axis

theta_axis() accepts the ordinary x_axis() options for labels, ticks, formatting, and styling, plus five polar settings:

OptionValuesDefault
unit"radians" or "degrees""radians"
zero"E", "N", "W", "S", or a radian offset"E"
direction"counterclockwise" or "clockwise""counterclockwise"
sectorIncreasing (start, end) no wider than one turnFull turn
grid_shape"circular" or "linear""circular"

The compass combination xy.theta_axis(unit="degrees", zero="N", direction="clockwise") makes 0° point north, 90° east, 180° south, and 270° west.

A numeric zero is always an offset in radians counterclockwise from east, independent of the data unit. Exact tick_values take priority over automatic angular ticks. When tick_labels are omitted, authored fractional degree values retain their precision (for example, 22.5 renders as 22.5°).

sector=(start, end) clips marks and ticks to that angular interval and fits the chart to the visible arc's bounding box. On theta_axis(), domain= is a compatibility alias for sector=; pass one or the other, not both. Numeric theta keeps its independent full-turn data and tick range. Set grid_shape="linear" to join spoke intersections into polygonal radial rings.

Category strings also work as theta coordinates. They are spaced evenly around a full turn or across an authored sector, and their labels take priority over numeric angle formatting. radar_chart() remains the convenient composition when every series shares one category list and should close automatically.

Configure the Radial Axis

r_axis() accepts the same options as y_axis(). Use label= for the measured quantity, domain=(minimum, maximum) for a fixed radial range, and tick_values= when rings must land at exact values. Radial axes support type_="linear", "log", or "symlog".

The automatic linear range begins at zero so the center keeps its usual meaning; log autorange remains strictly positive. An explicit domain can choose a different inner value. hole= reserves a shared display-space inner-radius fraction from 0 up to (but not including) 1. origin= places the shared radial origin in data space, so an origin below the visible minimum creates an annulus. hole and origin are mutually exclusive, and a log-axis origin must be positive.

Filled areas and annular sectors are clipped to the visible radial interval. For example, a bar extending past the outer ring draws up to that ring instead of disappearing, and a filled area crossing the radial minimum is trimmed instead of reflecting through the center. A sector wholly outside the interval disappears. Scatter points and line vertices outside the interval are culled; an out-of-range vertex splits a line into visible runs instead of connecting through its mirrored polar position.

Combine a Sector, Hole, and Error Bars

Both angular (xerr) and radial (yerr) uncertainty project through the polar coordinate system. This example fits the plot to a 220° compass sector and clips the marks to a shared inner hole:

Hover and Zoom

Interactive polar charts deliberately expose a smaller gesture set than Cartesian charts:

  • Hover reports the nearest point or field cell: its series name, radial value, and any color or size encoding. The numeric angle is left out — the cursor is already on it — while an authored spoke label survives and xy.tooltip(labels={"x": ...}) opts the angle back in. A hole or excluded part of a sector is not hit-testable.
  • Wheel and modebar zoom scale the radial maximum while keeping the radial minimum fixed. Reset restores the original radial range.
  • Theta rotation/panning, interactive sector zoom, box zoom, rectangular or lasso selection, brushing, and crosshairs are not currently available.

Keeping the radial minimum fixed prevents an ordinary zoom from unexpectedly turning a disc into an annulus. See Interactions and selections for the general interaction configuration surface.

Lay Out and Annotate Polar Charts

Explicit padding=(top, right, bottom, left) is preserved by the polar layout. Increase the bottom or side value to reserve a stable band for a legend, caption, or other surrounding content; the disc stays centered in the remaining plot box.

Point-anchored text, label, marker, arrow, and callout annotations interpret data coordinates as (theta, r) consistently in the browser, SVG, and native raster output. Their dx and dy offsets remain screen-space pixels. Polar rules and bands remain deferred because they require spoke/ring and sector/annulus geometry instead of Cartesian lines and rectangles; using one on a polar chart raises at payload build instead of drawing a Cartesian approximation.

Use xy.pyplot

The Matplotlib-style compatibility layer routes a polar subplot through the same renderer:

Polar routing works through plt.subplot(projection="polar"), fig.add_subplot(..., projection="polar"), plt.axes(projection="polar"), and plt.subplots(subplot_kw={"projection": "polar"}). The polar=True alias is accepted by subplot(), add_subplot(), and axes(), or inside subplot_kw; it is not a direct plt.subplots(polar=True) argument. Ordinary plot, scatter, fill, bar, heatmap/image, contour, and errorbar calls share the core polar renderer. fill() maps its boundary to a radial area against r=0, which matches a full-turn filled profile but not every arbitrary closed Matplotlib polygon. The shim preserves theta zero, direction, offset, and authored theta grids. Degree-based set_thetamin()/set_thetamax() share view state with radian set_xlim(), while set_rorigin() authors the data-space radial origin. Their corresponding getters, radial limits, ticks, grids, categorical theta, and log/symlog radial scales use the same core axes.

The stateful plt.polar(), plt.thetagrids(), and plt.rgrids() convenience wrappers are not part of this increment; call the corresponding methods on the polar axes. Keep and reuse the returned axes handle: passing projection="polar" again while reactivating an existing plt.subplot() is not supported. See the Matplotlib compatibility guide for the full boundary.

Supported Marks and Current Limits

The supported polar primitives are line, scatter, area, bar, column, heatmap, contour, and errorbar. The radar, radial bar, and wind rose helpers compose those primitives; they do not add separate renderers.

Current limits:

  • Histograms, box plots, hexbin, density grids, generic segments, and meshes are rejected instead of being drawn with incorrect geometry. Polar errorbar and contour use narrowly allowlisted projected segments; they do not make every segment-backed mark legal.
  • Polar lines and filled-area boundaries connect observations with straight chords. Repeat the first observation at one full turn when manually closing a line; the radar helper handles closure itself.
  • Polar traces use direct rendering rather than Cartesian line decimation or scatter-density aggregation. line, scatter, and area are limited to 200,000 points per trace; a larger point trace raises ValueError. Heatmap/contour grids are not rejected merely because their cell count exceeds that point ceiling.
  • Polar rules and bands need dedicated spoke, ring, annulus, or sector geometry and raise instead of falling back to Cartesian geometry.
  • Polar LOD, facets/animation, interactive theta rotation/pan, sector zoom, and annulus/sector selection remain deferred.

Supported marks and point-anchored annotations use the same polar projection in the browser, SVG, PDF, and native raster exporters, so the chart can be displayed live or exported through the usual chart methods.

API Reference

xy.polar_chart

A polar chart: the same marks, rendered through polar coordinates.

Props

PropTypeDescription
*childrenComponent

Marks, axes, annotations, and chart chrome.

titleOptional[str]

Title shown above the plot.

widthint | str

Chart width in pixels or a CSS size such as ``"100%"``.

heightint | str

Chart height in pixels or a CSS size such as ``"100%"``.

paddingUnion[float, Sequence[float], None]

Plot margins, as one value or a sequence of side values. Use zero for an edge-to-edge sparkline.

dataTableLike

Chart-level data used by marks that omit their own ``data``.

class_nameOptional[str]

CSS class applied to the chart container.

class_namesOptional[dict[str, str]]

CSS classes keyed by stable chart DOM slot.

styleOptional[dict[str, StyleValue]]

Inline style overrides for the chart container.

stylesOptional[dict[str, dict[str, StyleValue]]]

Inline style mappings keyed by stable chart DOM slot.

on_hoverOptional[Callable[[dict], None]]

Callback receiving hover event payloads.

on_clickOptional[Callable[[dict], None]]

Callback receiving picked-mark click payloads.

on_brushOptional[Callable[[dict], None]]

Callback receiving brush event payloads.

on_selectOptional[Callable[[Selection], None]]

Callback receiving data-space selections.

on_view_changeOptional[Callable[[dict], None]]

Callback receiving viewport change payloads.

hoverOptional[bool]

Whether pointer movement emits hover events.

clickOptional[bool]

Whether picked marks emit click events.

selectOptional[bool]

Whether shift-drag box selection is enabled.

brushOptional[bool]

Whether brush selection is enabled.

crosshairOptional[bool]

Whether plot-aligned hover guides are shown.

navigationOptional[bool]

Whether browser pan and zoom navigation is enabled.

panOptional[bool]

Whether plain-drag panning is enabled.

pan_axesOptional[tuple[str, ...]]

Declared axis IDs translated by pan gestures.

zoomOptional[bool]

Whether viewport zoom is enabled.

default_drag_actionOptional[DefaultDragAction]

Initial action performed by a plain plot drag.

zoom_axesOptional[tuple[str, ...]]

Declared axis IDs changed by zoom gestures and controls.

zoom_limitsOptional[ZoomLimits]

Minimum and maximum magnification globally or by axis.

wheel_zoomOptional[bool]

Whether wheel and trackpad zoom is available.

box_zoomOptional[bool]

Whether box zoom is available as a drag action.

zoom_buttonsOptional[bool]

Whether modebar Zoom In/Out commands are available.

double_click_resetOptional[bool]

Whether double-click restores ``reset_axes``.

reset_axesOptional[tuple[str, ...]]

Declared axis IDs restored by reset.

link_groupOptional[str]

Identifier used to synchronize charts in the browser.

link_axesOptional[tuple[str, ...]]

Axes synchronized within the link group.

coordsstr

Coordinate system, ``"cartesian"`` (default) or ``"polar"``. Under ``"polar"`` each mark's first channel is the angle and its second is the radius. Prefer ``xy.polar_chart(...)``, which sets this for you.

xy.theta_axis

Configure the angular axis of an `xy.polar_chart`.

Props

PropTypeDescription
unitOptional[str]

Angular unit of the data, ``"radians"`` (default) or ``"degrees"``.

zeroUnion[str, float, None]

Direction that angle 0 points — ``"E"`` (default), ``"N"``, ``"W"``, ``"S"``, or an angle in radians counterclockwise from east.

directionOptional[str]

``"counterclockwise"`` (default) or ``"clockwise"``. Compass work usually wants ``zero="N"`` with ``"clockwise"``, which puts 90° at east and 180° at south.

sectorOptional[tuple[float, float]]

Visible angular interval in the declared ``unit``. The sweep must be increasing and no wider than one full turn.

grid_shapeOptional[str]

``"circular"`` (default) for arc rings or ``"linear"`` for polygonal rings joining the angular spokes.

idstr

Axis identifier referenced by marks.

labelOptional[str]

Axis label.

label_positionOptional[AxisLabelPosition]

Named or structured label placement.

label_offsetOptional[float]

Label offset in pixels.

label_angleOptional[float]

Label rotation in degrees.

type_Optional[str]

Scale type, such as ``linear``, ``time``, ``log``, or ``symlog``.

constantOptional[float]

Width of the linear region around zero for ``symlog``.

domainOptional[tuple[float, float]]

Explicit minimum and maximum scale values.

marginOptional[float]

Fractional padding around an automatic domain.

boundsUnion[tuple[float, float], Literal['data'], None]

Hard navigation limits, or ``"data"`` to use the data range. Pan and zoom are clamped within these limits; ``None`` leaves navigation unrestricted.

formatOptional[str]

Tick-label format string.

tick_countOptional[int]

Requested number of ticks.

tick_valuesUnion[Sequence[float], np.ndarray, None]

Explicit tick positions.

tick_labelsOptional[Sequence[str]]

Labels corresponding to explicit tick positions.

tick_label_angleOptional[float]

Tick-label rotation in degrees.

tick_label_strategyOptional[AxisTickLabelStrategy]

Collision-handling strategy for tick labels.

sideOptional[str]

Side of the plot where the axis is drawn.

tick_sidesOptional[Sequence[str]]

Plot sides where tick marks are drawn. Defaults to ``side``; supplying both draws mirrored ticks without moving the axis labels.

tick_label_sidesOptional[Sequence[str]]

Plot sides where tick labels are drawn. Defaults to ``side`` and remains independent of ``tick_sides``.

showOptional[bool]

Draw this axis at all. ``False`` hides its baseline, tick marks, tick labels, title, and grid lines in one switch; the four narrower switches below override it either way, so ``show=False, grid=True`` leaves only the grid.

lineOptional[bool]

Draw the axis baseline.

ticksOptional[bool]

Draw the tick marks.

gridOptional[bool]

Draw this axis's grid lines (the y axis owns the horizontal guides, the x axis the vertical ones).

textOptional[bool]

Draw this axis's text — its tick labels and its title. (Unlike ``tick_labels``, which supplies the label *strings*.)

styleOptional[dict[str, StyleValue]]

Axis style overrides. An explicit property here always wins over the switches above.

nonpositiveOptional[Literal['clip', 'mask']]

Log-axis handling for non-positive mark coordinates: ``"clip"`` or ``"mask"``.

xy.r_axis

Configure the radial axis of an `xy.polar_chart`.

Props

PropTypeDescription
holeOptional[float]

Display-space inner-radius fraction, from 0 (no hole) up to but excluding 1.

originOptional[float]

Data-space radial origin. An origin below the visible radial minimum creates an annulus. Mutually exclusive with ``hole``.

idstr

Axis identifier referenced by marks.

labelOptional[str]

Axis label.

label_positionOptional[AxisLabelPosition]

Named or structured label placement.

label_offsetOptional[float]

Label offset in pixels.

label_angleOptional[float]

Label rotation in degrees.

type_Optional[str]

Scale type, such as ``linear``, ``time``, ``log``, or ``symlog``.

constantOptional[float]

Width of the linear region around zero for ``symlog``.

domainOptional[tuple[float, float]]

Explicit minimum and maximum scale values.

marginOptional[float]

Fractional padding around an automatic domain.

boundsUnion[tuple[float, float], Literal['data'], None]

Hard navigation limits, or ``"data"`` to use the data range. Pan and zoom are clamped within these limits; ``None`` leaves navigation unrestricted.

reversebool

Whether to reverse the scale direction.

formatOptional[str]

Tick-label format string.

tick_countOptional[int]

Requested number of ticks.

tick_valuesUnion[Sequence[float], np.ndarray, None]

Explicit tick positions.

tick_labelsOptional[Sequence[str]]

Labels corresponding to explicit tick positions.

tick_label_angleOptional[float]

Tick-label rotation in degrees.

tick_label_strategyOptional[AxisTickLabelStrategy]

Collision-handling strategy for tick labels.

sideOptional[str]

Side of the plot where the axis is drawn.

tick_sidesOptional[Sequence[str]]

Plot sides where tick marks are drawn. Defaults to ``side``; supplying both draws mirrored ticks without moving the axis labels.

tick_label_sidesOptional[Sequence[str]]

Plot sides where tick labels are drawn. Defaults to ``side`` and remains independent of ``tick_sides``.

showOptional[bool]

Draw this axis at all. ``False`` hides its baseline, tick marks, tick labels, title, and grid lines in one switch; the four narrower switches below override it either way, so ``show=False, grid=True`` leaves only the grid.

lineOptional[bool]

Draw the axis baseline.

ticksOptional[bool]

Draw the tick marks.

gridOptional[bool]

Draw this axis's grid lines (the y axis owns the horizontal guides, the x axis the vertical ones).

textOptional[bool]

Draw this axis's text — its tick labels and its title. (Unlike ``tick_labels``, which supplies the label *strings*.)

styleOptional[dict[str, StyleValue]]

Axis style overrides. An explicit property here always wins over the switches above.

nonpositiveOptional[Literal['clip', 'mask']]

Log-axis handling for non-positive mark coordinates: ``"clip"`` or ``"mask"``.

FAQ

How do I create a polar chart in Python?

Put a supported mark such as xy.line(theta, radius), xy.heatmap(z, x=theta, y=radius), or xy.errorbar(theta, radius, yerr=...) inside xy.polar_chart(...). Angles are radians by default.

How do I create a radar chart?

See the radar chart guide for filled and outlined profiles, the category/value contract, and shared-scale configuration.

How do I create a wind rose?

See the wind rose guide for directional bins, speed bands, input validation, and compass conventions.

How do I create a pie or donut chart?

See Pie and Donut Charts for four live unequal-width sector compositions with center metrics and Reflex legends.

How do I use degrees instead of radians?

Add xy.theta_axis(unit="degrees"). The angle data and generated tick labels will both use degrees.

How do I make zero degrees point north?

Use xy.theta_axis(unit="degrees", zero="N", direction="clockwise") for the standard compass convention. The wind rose helper applies it automatically.

Can I migrate a Matplotlib polar plot?

Yes. Create the axes with plt.subplot(projection="polar") or plt.subplots(subplot_kw={"projection": "polar"}). Lines, scatter, radial-to-zero fills, bars, heatmaps/images, contours, and error bars route through the polar renderer. Theta zero/direction/offset and min/max, authored theta grids, radial origin, radial limits/ticks/grids, categorical theta, and log/symlog radius are supported. Arbitrary closed-polygon fills remain a documented approximation.

Built with Reflex