Compass
The heading card with heading and course over ground arrows, wind and current arrows, and a rate-of-turn indicator.
On this page
Code
import {
AdviceType,
CogArrowStyle,
Compass,
CompassDirection,
CompassPriorityElement,
CompassReadoutSource,
HdgArrowStyle,
InstrumentState,
Priority,
RotPosition,
RotType,
VesselImage,
type AngleAdvice,
type CompassCenterReadout,
} from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"A passage under autopilot on a live feed: the heading wanders around the setpoint and into a caution sector, " +
"the rate of turn, course over ground, wind and current follow, and the setpoint is adjusted and confirmed " +
"every eight seconds, on a north-up, a heading-up and a course-up card.";
const CENTER_READOUTS: CompassCenterReadout[] = [
{ source: CompassReadoutSource.hdg },
{ source: CompassReadoutSource.cog },
{ source: CompassReadoutSource.rot, fractionDigits: 0 },
];
/** Port of the setpoint is open water; the starboard sector closes on the shoal off the headland. */
const ADVICES: AngleAdvice[] = [
{ minAngle: 196, maxAngle: 214, type: AdviceType.advice, hinted: true },
{ minAngle: 236, maxAngle: 262, type: AdviceType.caution, hinted: true },
];
const PANEL: CSSProperties = {
display: "flex",
flexWrap: "wrap",
alignItems: "flex-end",
justifyContent: "center",
gap: 28,
width: "100%",
maxWidth: 680,
};
const FIGURE: CSSProperties = { display: "flex", flexDirection: "column", alignItems: "center", gap: 8, margin: 0 };
const CAPTION: CSSProperties = { fontSize: 13, color: "var(--text-dim)", textAlign: "center", maxWidth: 220 };
/** The heading the feed reports at a given second: a slow swing with a faster yaw on top. */
function headingAt(second: number): number {
return 228 + 9 * Math.sin(second / 20) + 2 * Math.sin(second / 4);
}
export default function Showcase(): ReactElement {
const [second, setSecond]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
useEffect((): (() => void) => {
const interval: number = window.setInterval((): void => {
setSecond((previous: number): number => previous + 1);
}, 1000);
return (): void => {
window.clearInterval(interval);
};
}, []);
const heading: number = (headingAt(second) + 360) % 360;
const rateOfTurn: number = (headingAt(second + 0.5) - headingAt(second - 0.5)) * 60;
const courseOverGround: number = (heading + 4 + Math.sin(second / 5) + 360) % 360;
const windFrom: number = 300 + 6 * Math.sin(second / 11);
const windKnots: number = 18 + 3 * Math.sin(second / 4);
// The setpoint alternates between 225 and 232 degrees. In the two seconds before each change the helmsman is
// turning the knob, so the new value shows as a second marker; on release the marker slides across.
const phase: number = second % 8;
const confirmed: number = Math.floor(second / 8) % 2 === 0 ? 225 : 232;
const next: number = confirmed === 225 ? 232 : 225;
const adjusting: boolean = phase >= 6;
return (
<div style={PANEL}>
<figure style={FIGURE}>
<Compass
heading={heading}
courseOverGround={courseOverGround}
headingSetpoint={confirmed}
newHeadingSetpoint={adjusting ? next : undefined}
touching={adjusting}
autoAtHeadingSetpoint
autoAtHeadingSetpointDeadband={3}
headingSetpointAtZeroDeadband={0.5}
animateSetpoint
headingAdvices={ADVICES}
currentWindSpeedKnots={windKnots}
windFromDirection={windFrom}
currentSpeed={2}
currentFromDirection={160}
rateOfTurnDegreesPerMinute={rateOfTurn}
rotType={RotType.bar}
rotPosition={RotPosition.scale}
rotMaxValue={60}
rotArcExtent={45}
rotPortStarboard
rotAtZeroDeadband={0.5}
centerReadouts={CENTER_READOUTS}
hdgArrowStyle={HdgArrowStyle.vector}
cogArrowStyle={CogArrowStyle.velocityVector}
direction={CompassDirection.NorthUp}
priority={Priority.enhanced}
priorityElements={[
CompassPriorityElement.hdg,
CompassPriorityElement.cog,
CompassPriorityElement.rot,
CompassPriorityElement.wind,
]}
showLabels
faceDiameter={250}
/>
<figcaption style={CAPTION}>
Autopilot, north up: readouts, rate-of-turn bar by side, advice and caution sectors
</figcaption>
</figure>
<figure style={FIGURE}>
<Compass
heading={heading}
courseOverGround={courseOverGround}
headingSetpoint={confirmed}
newHeadingSetpoint={adjusting ? next : undefined}
touching={adjusting}
autoAtHeadingSetpoint
animateSetpoint
headingAdvices={ADVICES}
currentWindSpeedKnots={windKnots}
windFromDirection={windFrom}
currentSpeed={2}
currentFromDirection={160}
rateOfTurnDegreesPerMinute={rateOfTurn}
rotDotAnimationFactor={12}
rotType={RotType.dots}
rotPosition={RotPosition.innerCircle}
vesselImage={VesselImage.genericTop}
hdgArrowStyle={HdgArrowStyle.needle}
cogArrowStyle={CogArrowStyle.needle}
direction={CompassDirection.HeadingUp}
priority={Priority.regular}
showLabels
tickmarksInside
faceDiameter={170}
/>
<figcaption style={CAPTION}>Helm, heading up: the vessel, needles and circling rate-of-turn dots</figcaption>
</figure>
<figure style={FIGURE}>
<Compass
heading={heading}
courseOverGround={courseOverGround}
headingSetpoint={confirmed}
headingSetpointOverride
atHeadingSetpoint={false}
hdgArrowStyle={HdgArrowStyle.beamLine}
cogArrowStyle={CogArrowStyle.arrowHead}
direction={CompassDirection.CourseUp}
state={InstrumentState.loading}
faceDiameter={110}
/>
<figcaption style={CAPTION}>Chart table repeater, course up, while the heading sensor starts</figcaption>
</figure>
</div>
);
}
The compass is the instrument the watchkeeper steers by. A card turns under two arrows, one for the heading the bow
points along and one for the course over ground the vessel makes good, and a setpoint marker shows the heading that is
set. Around the ring it carries the wind arrow, the current arrow and a rate-of-turn indicator, so one instrument
answers where the bow points, where the vessel goes and what pushes it. direction sets the card to north up, heading
up or course up. It ports obc-compass; the overview lists the other instruments.
Examples
North up
Code
import { Compass, CompassDirection, VesselImage } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"The card with north at the top, the heading and course over ground arrows, the wind and current arrows, and the rate-of-turn dots.";
export default function NorthUp(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<Compass
heading={47}
courseOverGround={52}
currentWindSpeedKnots={14}
windFromDirection={310}
currentSpeed={1.2}
currentFromDirection={190}
rateOfTurnDegreesPerMinute={8}
direction={CompassDirection.NorthUp}
vesselImage={VesselImage.genericTop}
showLabels
/>
</div>
);
}
With readouts
Code
import { Compass, CompassDirection, CompassReadoutSource, type CompassCenterReadout } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"Heading, course over ground and rate of turn as readouts in the center, on a face pinned to 240 pixels.";
const CENTER_READOUTS: CompassCenterReadout[] = [
{ source: CompassReadoutSource.hdg },
{ source: CompassReadoutSource.cog },
{ source: CompassReadoutSource.rot },
];
export default function WithReadouts(): ReactElement {
return (
<Compass
heading={47}
courseOverGround={52}
rateOfTurnDegreesPerMinute={8}
direction={CompassDirection.NorthUp}
centerReadouts={CENTER_READOUTS}
showLabels
faceDiameter={240}
/>
);
}
Small face
Code
import { Compass } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"A compass on a face pinned to 180 pixels, which sizes itself and needs no surrounding box.";
export default function SmallFace(): ReactElement {
return <Compass heading={47} courseOverGround={52} rateOfTurnDegreesPerMinute={8} showLabels faceDiameter={180} />;
}
Reference and layers
Keep heading, course over ground and the setpoint in a declared reference, such as true or magnetic. The component receives angles; the application performs any conversion and labels the reference. Heading is where the bow points, while course over ground describes movement over the ground. Do not substitute one for the other when a source is lost or the vessel is nearly stationary.
Choose north-up, heading-up or course-up deliberately and show that choice near the instrument. Add wind, current and rate of turn only when their sources and references are known. A dense card needs enough physical space for its labels; use Heading when the extra layers do not support the task. Source age and integrity belong in the surrounding reading or instrument panel.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| heading | number | undefined | – | The current heading of the vessel in degrees; unset or not finite, no heading arrow and no vessel are drawn. |
| courseOverGround | number | undefined | – | The current course over ground in degrees; unset or not finite, no course arrow is drawn. |
| headingSetpoint | number | null | undefined | null | The set point for the heading in degrees. |
| newHeadingSetpoint | number | undefined | – | New setpoint being adjusted (renders a second, focus-state marker). |
| atHeadingSetpoint | boolean | undefined | false | Manual at-setpoint flag; used when autoAtHeadingSetpoint is false. |
| headingSetpointAtZeroDeadband | number | undefined | 0.5 | Zero-snap deadband for the setpoint marker, in degrees. |
| headingSetpointOverride | boolean | undefined | false | Derive the setpoint color from priority even when loading/off. |
| autoAtHeadingSetpoint | boolean | undefined | true | Enables automatic at-setpoint calculation. |
| autoAtHeadingSetpointDeadband | number | undefined | 2 | The deadband for auto at-setpoint detection in degrees. |
| animateSetpoint | boolean | undefined | false | Animate setpoint confirm transitions. |
| touching | boolean | undefined | false | Indicates the user is actively adjusting (focus rendering). |
| headingAdvices | AngleAdvice[] | undefined | [] | Caution/alert arcs; triggered state derives from the current heading. |
| currentWindSpeedKnots | number | null | undefined | null | The wind speed in knots. |
| windFromDirection | number | null | undefined | null | The direction the wind is coming from in degrees. |
| currentSpeed | number | null | undefined | null | The current speed, number of arrows. |
| currentFromDirection | number | null | undefined | null | The direction the current is coming from in degrees. |
| vesselImage | VesselImage | undefined | VesselImage.genericTop | The vessel silhouette centered on the compass, rotating with heading.
Hidden while centerReadouts is non-empty. |
| centerReadouts | CompassCenterReadout[] | undefined | [] | Center readouts replacing the vessel: the first entry renders on top, the
rest side by side below a horizontal divider. Values bind per entry
source (hdg → heading, cog → courseOverGround, rot →
rateOfTurnDegreesPerMinute, a dash when unset) and colors follow
priorityElements. While non-empty, the crosshair's center is cut out so
the readouts sit on a clean face. |
| hdgArrowStyle | HdgArrowStyle | undefined | HdgArrowStyle.arrowHead | HDG arrow style: arrowHead (default), needle, vector, or beamLine. |
| cogArrowStyle | CogArrowStyle | undefined | CogArrowStyle.arrowHead | COG arrow style: arrowHead (default), needle, vector, or velocityVector. |
| rateOfTurnDegreesPerMinute | number | undefined | – | Measured rate of turn in degrees per minute (positive = starboard, the
AIS / ITU-R M.1371 convention). Drives both the bar extent and (after
multiplication by rotDotAnimationFactor) the spinning dot animation. |
| rotDotAnimationFactor | number | undefined | 18 | Visual amplification applied only to the spinning dot animation (not to
the bar extent). Default 18 keeps the legacy visual feel
(≈1 rpm at 20°/min). |
| rotType | RotType | undefined | RotType.dots | ROT display mode: dots (spinning dots, default) or bar (arc from HDG toward COG). |
| rotPosition | RotPosition | undefined | RotPosition.innerCircle | ROT track position: innerCircle (default) or scale (outer ring). |
| rotMaxValue | number | undefined | 60 | Bar-extent reference value in **degrees per minute**. The bar fills the
full ±rotArcExtent arc when the measured ROT equals ±rotMaxValue.
Default 60 aligns with ES-TRIN 2025/1 Art. 3.02. |
| rotArcExtent | number | undefined | 60 | Arc, in degrees, that the rate-of-turn bar fills when the measured rate of turn equals rotMaxValue; the bar fills
±rotArcExtent for ±rotMaxValue. Default 60. Bar mode only. |
| rotPortStarboard | boolean | undefined | false | Colors the rate-of-turn dots or bar by the direction of the turn: starboard colors for a positive rate, port colors
for a negative one. Default false. |
| rotAtZeroDeadband | number | undefined | ROT_ZERO_DEADBAND_DEG | Smallest rate-of-turn bar, in degrees of arc, that is drawn as a bar; a shorter one is drawn as the zero marker.
Default 0.05. Bar mode only. |
| direction | CompassDirection | undefined | CompassDirection.NorthUp | Orientation mode: north-up (default), heading-up, or course-up. |
| state | InstrumentState | undefined | InstrumentState.active | Instrument state: active (default), loading or off. off draws the outer ring only, and loading and off
draw the heading setpoint marker disabled. |
| priority | Priority | undefined | Priority.regular | Priority.enhanced uses the blue palette for the elements in priorityElements. |
| priorityElements | CompassPriorityElement[] | undefined | [CompassPriorityElement.hdg] | Which elements the enhanced priority applies to (default: hdg). |
| showLabels | boolean | undefined | false | Show compass NSEW labels. The north arrow is always shown, independent of this flag. |
| tickmarksInside | boolean | undefined | false | When true, labels and north arrow are placed inside the outer ring. |
| faceDiameter | number | undefined | – | Outer-ring diameter in CSS pixels. When set, the instrument renders at a fixed intrinsic size derived from the ring, arc shape and label reserve — so instruments sharing the same value have identical ring circumference regardless of label width or arc extent. When unset (default), the instrument fills its container. |
| className | string | undefined | – | Class names added to the instrument's root element. |
Related
- Heading: the same card without the wind, current and rate-of-turn layers.
- Rate of turn: the turn indicator on its own.
- Wind: the wind dial on its own.
- Data integrity: the states a value can be in, and what the crew does.