Wind Rose Charts in Python
A wind rose (also called a wind rose chart, wind rose plot, or wind rose diagram) summarizes how often observations arrive from each compass direction and how those observations are distributed across speed bands. XY bins the raw direction/speed pairs in Python and renders the result as stacked polar bars.
Use wind_rose() when you have one bearing and one magnitude per observation.
If the directional counts are already aggregated, use a
radial bar chart instead.
Jump to directional sectors, speed bands, or the input contract.
Create a Wind Rose
Directions are compass bearings in degrees: 0° is north and values increase clockwise. The helper applies that convention automatically:
Each stacked color band counts observations whose speed is above the previous edge and at or below the current edge. The legend labels show those inclusive upper edges.
Choose Directional Sectors
sectors= controls the number of equally sized angular bins and must be at
least 3. Each bin is centered on its compass bearing, so a value of exactly 0°
belongs to the sector centered on north. Bearings outside 0..360 wrap around
the circle.
More sectors reveal directional detail but need more observations to keep each bin stable. Common choices are 8, 12, 16, or 36 sectors, depending on sample size and the directional resolution of the source.
Configure Speed Bands
Pass increasing upper edges through speed_bins=:
The final edge should cover the fastest observation; values above it do not
belong to a displayed band. XY removes duplicate edges and orders the remaining
values. When speed_bins is omitted, it derives up to four readable bands from
the speed quartiles and rounds the top edge upward so every finite observation
is covered.
Set Sector Count and Band Edges Together
Lower the sector count when the sample is small or the source records only the eight principal bearings, and pass matching band edges so each petal stays thick enough to read:
Plot a Full Year of Observations
With thousands of records a high sector count resolves the prevailing wind, and
narrow speed bands separate calm air from gales — add xy.legend() to place the
band labels where you want them:
Follow the Input Contract
directionsandspeedsmust have the same length.- Each pair describes one observation.
- Non-finite pairs are dropped together.
- At least one finite pair must remain.
sectorsmust be 3 or greater.speed_binsmust contain at least one edge when supplied.
XY raises ValueError for mismatched arrays, an empty finite dataset, too few
sectors, or an empty band definition.
Read and Style the Result
The radial value is a count, not a speed. Each speed band becomes one stacked
bar series and takes the next chart palette color. Pass chart keyword props
such as title, width, height, padding, class_names, and styles through
wind_rose().
The helper authors a degree-based theta axis with north at zero and clockwise
rotation, plus an r axis labeled count. Build the equivalent sectors manually
with polar_bar_chart() when you need custom pre-binning, non-count radial
values, a different angular convention, or component children such as
xy.theme(), xy.legend(), and xy.modebar().
Interaction and Export
Wind roses support hover, fixed-minimum radial zoom, reset, and browser/static export through the shared polar renderer. Theta rotation, box zoom, selection, brushing, and crosshairs are not available.
See the polar overview for the full interaction, renderer, annotation, and large-data boundary.
Related Polar Charts
- Polar overview — numeric theta/r data and shared polar axes.
- Radial bar charts — pre-aggregated annular sectors.
- Pie and donut charts — share, progress-ring, revenue-mix, and gauge blocks.
- Radar charts — category profiles rather than directional frequencies.
API Reference
xy.wind_rose
A wind rose: directional frequency, stacked by speed band.
Props
| Prop | Type | Description |
|---|---|---|
directions | ArrayLike | Bearings in degrees, one per observation. |
speeds | ArrayLike | Speeds, one per observation. |
*children_in | Component | Extra components — an `xy.legend`, or an `xy.tooltip` to replace the default direction/count readout. |
sectors | int | Number of angular bins around the circle. |
speed_bins | Optional[Sequence[float]] | Upper edges of the speed bands. Defaults to four quartile bands derived from the data. Each band takes the next colour from the chart's palette cycle, as stacked series do everywhere else. |
title | Optional[str] | Title shown above the plot. |
width | int | str | Chart width in pixels or a CSS size such as ``"100%"``. |
height | int | str | Chart height in pixels or a CSS size such as ``"100%"``. |
padding | Union[float, Sequence[float], None] | Plot margins, as one value or a sequence of side values. Use zero for an edge-to-edge sparkline. |
data | TableLike | Chart-level data used by marks that omit their own ``data``. |
class_name | Optional[str] | CSS class applied to the chart container. |
class_names | Optional[dict[str, str]] | CSS classes keyed by stable chart DOM slot. |
style | Optional[dict[str, StyleValue]] | Inline style overrides for the chart container. |
styles | Optional[dict[str, dict[str, StyleValue]]] | Inline style mappings keyed by stable chart DOM slot. |
on_hover | Optional[Callable[[dict], None]] | Callback receiving hover event payloads. |
on_click | Optional[Callable[[dict], None]] | Callback receiving picked-mark click payloads. |
on_brush | Optional[Callable[[dict], None]] | Callback receiving brush event payloads. |
on_select | Optional[Callable[[Selection], None]] | Callback receiving data-space selections. |
on_view_change | Optional[Callable[[dict], None]] | Callback receiving viewport change payloads. |
hover | Optional[bool] | Whether pointer movement emits hover events. |
click | Optional[bool] | Whether picked marks emit click events. |
select | Optional[bool] | Whether shift-drag box selection is enabled. |
brush | Optional[bool] | Whether brush selection is enabled. |
crosshair | Optional[bool] | Whether plot-aligned hover guides are shown. |
navigation | Optional[bool] | Whether browser pan and zoom navigation is enabled. |
pan | Optional[bool] | Whether plain-drag panning is enabled. |
pan_axes | Optional[tuple[str, ...]] | Declared axis IDs translated by pan gestures. |
zoom | Optional[bool] | Whether viewport zoom is enabled. |
default_drag_action | Optional[DefaultDragAction] | Initial action performed by a plain plot drag. |
zoom_axes | Optional[tuple[str, ...]] | Declared axis IDs changed by zoom gestures and controls. |
zoom_limits | Optional[ZoomLimits] | Minimum and maximum magnification globally or by axis. |
wheel_zoom | Optional[bool] | Whether wheel and trackpad zoom is available. |
box_zoom | Optional[bool] | Whether box zoom is available as a drag action. |
zoom_buttons | Optional[bool] | Whether modebar Zoom In/Out commands are available. |
double_click_reset | Optional[bool] | Whether double-click restores ``reset_axes``. |
reset_axes | Optional[tuple[str, ...]] | Declared axis IDs restored by reset. |
link_group | Optional[str] | Identifier used to synchronize charts in the browser. |
link_axes | Optional[tuple[str, ...]] | Axes synchronized within the link group. |
coords | str | 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. |
FAQ
Are wind directions radians or degrees?
wind_rose() always accepts compass bearings in degrees. It applies north-zero,
clockwise theta settings automatically.
What happens when I omit speed_bins?
XY derives up to four bands from the finite speed quartiles and rounds the final edge upward to include the maximum observation.
Why are some observations missing?
Non-finite direction/speed pairs are dropped before the wind rose graph is
binned. With authored speed_bins, make sure the final upper edge covers the
fastest finite observation.