Polar chart
Compare directional observations and performance curves using their actual angular spacing.
On this page
Sailing performance
Illustrative observations · true wind speed 12 kn
True wind from the bow · mirrored port and starboard curves
- Target speed
- Observed speed
Code
import type { ReactElement } from "react";
import { PolarChart } from "@bosunkit/components/polar-chart";
import { ChartLegend } from "@bosunkit/components/chart-legend";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Illustrative sailing performance curves preserve true wind angle spacing and a missing observed speed.";
const angleLabels: Readonly<Record<number, string>> = {
"0": "0°",
"35": "35°",
"45": "45°",
"70": "70°",
"90": "90°",
"100": "100°",
"130": "130°",
"135": "135°",
"170": "170°",
"180": "180°",
"190": "190°",
"230": "230°",
"260": "260°",
"290": "290°",
"325": "325°",
"225": "225°",
"270": "270°",
"315": "315°",
};
const speedLabels: Readonly<Record<number, string>> = {
"0": "0 kn",
"2": "2 kn",
"3.8": "3.8 kn",
"4": "4 kn",
"4.2": "4.2 kn",
"4.5": "4.5 kn",
"4.9": "4.9 kn",
"5.2": "5.2 kn",
"5.4": "5.4 kn",
"5.5": "5.5 kn",
"5.6": "5.6 kn",
"6": "6 kn",
"6.4": "6.4 kn",
"8": "8 kn",
};
const formatAngle: (value: string | number) => string = (value: string | number): string =>
angleLabels[Number(value)] ?? "";
const formatSpeed: (value: number) => string = (value: number): string => speedLabels[value] ?? "";
const config = {
target: { label: "Target speed", formatValue: formatSpeed },
actual: { label: "Observed speed", formatValue: formatSpeed },
};
export default function SailingPerformance(): ReactElement {
return (
<ChartPanel
title="Sailing performance"
description="Illustrative observations · true wind speed 12 kn"
legend={<ChartLegend config={config} label="Sailing performance series" />}
>
<PolarChart
data={[
{ angle: 35, target: 4, actual: 3.8 },
{ angle: 45, target: 5.2, actual: 4.9 },
{ angle: 70, target: 6, actual: 5.5 },
{ angle: 100, target: 6.4, actual: null },
{ angle: 130, target: 5.6, actual: 5.4 },
{ angle: 170, target: 4.5, actual: 4.2 },
{ angle: 190, target: 4.5, actual: 4.2 },
{ angle: 230, target: 5.6, actual: 5.4 },
{ angle: 260, target: 6.4, actual: null },
{ angle: 290, target: 6, actual: 5.5 },
{ angle: 315, target: 5.2, actual: 4.9 },
{ angle: 325, target: 4, actual: 3.8 },
]}
categoryKey="angle"
config={config}
series={["target", "actual"]}
valueDomain={[0, 8]}
formatCategory={formatAngle}
formatAxisValue={formatSpeed}
label="Illustrative sailing speed by true wind angle"
angleReference="True wind from the bow · mirrored port and starboard curves"
categoryLabel="True wind angle"
dataTableLabel="View sailing observations"
missingValueText="Unavailable"
>
<text x={300} y={320} textAnchor="middle" fill="currentColor">
Bow
</text>
</PolarChart>
</ChartPanel>
);
}
Use polar plots for sailing performance and directional observations. Zero degrees is north in the drawing and angles increase clockwise; angleReference names the physical reference and from/toward convention. Prepare mirrored sailing curves in the caller. Radius values are nonnegative, zero is valid, and missing values break connected curves. connect={false} gives a point-only sky plot; the caller supplies the elevation-to-radius transform. closed closes only a complete curve. Numeric angular spacing is preserved by SVG geometry. Children are SVG annotations; direct Recharts composition belongs in ChartContainer.
Examples
Composition
Sailing performance
Illustrative observations · true wind speed 12 kn
True wind from the bow · mirrored port and starboard curves
- Target speed
- Observed speed
Code
import type { ReactElement } from "react";
import { PolarChart } from "@bosunkit/components/polar-chart";
import { ChartLegend } from "@bosunkit/components/chart-legend";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Illustrative sailing performance curves preserve true wind angle spacing and a missing observed speed.";
const angleLabels: Readonly<Record<number, string>> = {
"0": "0°",
"35": "35°",
"45": "45°",
"70": "70°",
"90": "90°",
"100": "100°",
"130": "130°",
"135": "135°",
"170": "170°",
"180": "180°",
"190": "190°",
"230": "230°",
"260": "260°",
"290": "290°",
"325": "325°",
"225": "225°",
"270": "270°",
"315": "315°",
};
const speedLabels: Readonly<Record<number, string>> = {
"0": "0 kn",
"2": "2 kn",
"3.8": "3.8 kn",
"4": "4 kn",
"4.2": "4.2 kn",
"4.5": "4.5 kn",
"4.9": "4.9 kn",
"5.2": "5.2 kn",
"5.4": "5.4 kn",
"5.5": "5.5 kn",
"5.6": "5.6 kn",
"6": "6 kn",
"6.4": "6.4 kn",
"8": "8 kn",
};
const formatAngle: (value: string | number) => string = (value: string | number): string =>
angleLabels[Number(value)] ?? "";
const formatSpeed: (value: number) => string = (value: number): string => speedLabels[value] ?? "";
const config = {
target: { label: "Target speed", formatValue: formatSpeed },
actual: { label: "Observed speed", formatValue: formatSpeed },
};
export default function SailingPerformance(): ReactElement {
return (
<ChartPanel
title="Sailing performance"
description="Illustrative observations · true wind speed 12 kn"
legend={<ChartLegend config={config} label="Sailing performance series" />}
>
<PolarChart
data={[
{ angle: 35, target: 4, actual: 3.8 },
{ angle: 45, target: 5.2, actual: 4.9 },
{ angle: 70, target: 6, actual: 5.5 },
{ angle: 100, target: 6.4, actual: null },
{ angle: 130, target: 5.6, actual: 5.4 },
{ angle: 170, target: 4.5, actual: 4.2 },
{ angle: 190, target: 4.5, actual: 4.2 },
{ angle: 230, target: 5.6, actual: 5.4 },
{ angle: 260, target: 6.4, actual: null },
{ angle: 290, target: 6, actual: 5.5 },
{ angle: 315, target: 5.2, actual: 4.9 },
{ angle: 325, target: 4, actual: 3.8 },
]}
categoryKey="angle"
config={config}
series={["target", "actual"]}
valueDomain={[0, 8]}
formatCategory={formatAngle}
formatAxisValue={formatSpeed}
label="Illustrative sailing speed by true wind angle"
angleReference="True wind from the bow · mirrored port and starboard curves"
categoryLabel="True wind angle"
dataTableLabel="View sailing observations"
missingValueText="Unavailable"
>
<text x={300} y={320} textAnchor="middle" fill="currentColor">
Bow
</text>
</PolarChart>
</ChartPanel>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| callout | ChartCallout | undefined | – | A visible summary bubble with caller-formatted value and context. |
| onCategoryDomainChange | ((range: readonly [number, number]) => void) | undefined | – | Receives a numeric viewport after drag-to-zoom or Shift-drag pan; requires categoryDomain. |
| maxGap | number | undefined | – | Maximum elapsed distance connected by a line; numeric categories only. |
| categoryDomain | readonly [number, number] | undefined | – | Controlled numeric/time viewport; emits no requests by itself. |
| syncId | string | undefined | – | Charts sharing this identifier synchronize inspection by category value or nearest time. |
| syncTolerance | number | undefined | – | Maximum distance for synchronized numeric categories; zero requires exact matches. |
| interpolation | "linear" | "stepAfter" | undefined | – | Linear segments or step changes; linear by default. Missing values remain gaps. |
| describeObservation | ((row: T) => string) | undefined | – | Caller-formatted source, processing stage or quality shown alongside every source-table row. |
| datarequired | readonly T[] | – | Rows in display order; numeric time categories must be increasing. |
| categoryKeyrequired | ChartCategoryKey<NoInfer<T>> | – | The row field naming a category or locating a sample on a numeric axis. |
| configrequired | ChartConfig<NoInfer<T>> | – | Definitions for numeric series, shared with legends and controls. |
| seriesrequired | readonly ChartValueKey<NoInfer<T>>[] | – | The visible numeric fields, in display order. Filtering preserves the complete registry's domain. |
| formatCategoryrequired | (value: string | number) => string | – | Writes category ticks, tooltip headings and the first table column. |
| formatAxisValuerequired | (value: number) => string | – | Writes value-axis ticks; separate from each series' observation formatter. |
| labelrequired | string | – | The drawing's accessible name, naming the quantity and time window. |
| categoryLabelrequired | string | – | The first table column's heading, translated by the caller. |
| dataTableLabelrequired | string | – | The disclosure button and source table's accessible name, translated by the caller. |
| missingValueTextrequired | string | – | Display text for missing values in the tooltip and source table. |
| height | string | undefined | "var(--bk-chart-height)" | The plot height as a CSS length; the chart height token by default. |
| valueDomain | readonly [number, number] | undefined | – | An explicit increasing value domain that clips outlying values; bar domains always include zero. |
| showGrid | boolean | undefined | true | Draw the grid; on by default. |
| showAxes | boolean | undefined | true | Draw axes; on by default. Values remain available in the table when off. |
| motion | MotionProp | undefined | false | Entrance and exit motion; off by default. Measurements never interpolate. |
| children | ReactNode | undefined | – | Recharts annotations appended inside the plot, such as ReferenceLine. Use ChartContainer for a custom root. |
| className | string | undefined | – | Classes added to the root. |
| testId | string | undefined | – | Becomes data-testid on the root, with --plot and --table parts. |
| ref | Ref<HTMLDivElement> | undefined | – | The chart root element. |
| angleReferencerequired | string | – | Caller display text naming the angle reference and direction convention. |
| connect | boolean | undefined | true | Connect supplied observations in their given order; missing values break the curve. |
| closed | boolean | undefined | false | Close complete curves; off by default and never closes across a missing observation. |
Sailing performance
Illustrative observations · true wind speed 12 kn
True wind from the bow · mirrored port and starboard curves
- Target speed
- Observed speed
Preserve gaps and supply the source or validity information needed to interpret the observations.
Related
- Instrument history: stream selection, aggregation and asynchronous windows.
- Chart panel: descriptions, controls and source information.
- Chart container: shared registry and direct Recharts composition.