Skip to content
design
Instruments

Heading

The heading and course over ground on a single card, with a heading setpoint and advice arcs.

On this page
ESW
30
HDG
DEG
24
COG
DEG
Helm: readouts, the enhanced palette, advice and caution sectors
ESWN
Cockpit repeater: the vessel, needles and inside tickmarks
Chart table: beam line, velocity vector, setpoint snapped to north
Hand steering up a buoyed channel on a live feed: the heading swings into a caution sector while the setpoint is adjusted, on a helm card with readouts, a cockpit repeater and a chart-table card.
Code
import {
  AdviceType,
  CogArrowStyle,
  CompassReadoutSource,
  HdgArrowStyle,
  Heading,
  HeadingPriorityElement,
  Priority,
  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 =
  "Hand steering up a buoyed channel on a live feed: the heading swings around the setpoint and into the caution " +
  "sector over the bank, and the setpoint is adjusted and confirmed every eight seconds, on three cards with " +
  "different arrows, centers and palettes.";

const CENTER_READOUTS: CompassCenterReadout[] = [
  { source: CompassReadoutSource.hdg },
  { source: CompassReadoutSource.cog },
];

/** The leading line through the channel, and the bank on its starboard side. */
const ADVICES: AngleAdvice[] = [
  { minAngle: 20, maxAngle: 34, type: AdviceType.advice, hinted: true },
  { minAngle: 40, maxAngle: 62, 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 30 + 10 * Math.sin(second / 18) + 2 * Math.sin(second / 3);
}

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;
  // The flood sets the vessel to port of her heading.
  const courseOverGround: number = (heading - 6 + 360) % 360;

  // The setpoint alternates between 27 and 33 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 ? 27 : 33;
  const next: number = confirmed === 27 ? 33 : 27;
  const adjusting: boolean = phase >= 6;

  return (
    <div style={PANEL}>
      <figure style={FIGURE}>
        <Heading
          heading={heading}
          courseOverGround={courseOverGround}
          headingSetpoint={confirmed}
          newHeadingSetpoint={adjusting ? next : undefined}
          touching={adjusting}
          autoAtHeadingSetpoint
          autoAtHeadingSetpointDeadband={3}
          animateSetpoint
          headingAdvices={ADVICES}
          centerReadouts={CENTER_READOUTS}
          hdgArrowStyle={HdgArrowStyle.arrowHead}
          cogArrowStyle={CogArrowStyle.vector}
          priority={Priority.enhanced}
          priorityElements={[HeadingPriorityElement.hdg, HeadingPriorityElement.cog]}
          showLabels
          faceDiameter={250}
        />
        <figcaption style={CAPTION}>Helm: readouts, the enhanced palette, advice and caution sectors</figcaption>
      </figure>

      <figure style={FIGURE}>
        <Heading
          heading={heading}
          courseOverGround={courseOverGround}
          headingSetpoint={confirmed}
          newHeadingSetpoint={adjusting ? next : undefined}
          touching={adjusting}
          autoAtHeadingSetpoint
          animateSetpoint
          headingAdvices={ADVICES}
          vesselImage={VesselImage.genericTop}
          hdgArrowStyle={HdgArrowStyle.needle}
          cogArrowStyle={CogArrowStyle.needle}
          showLabels
          tickmarksInside
          faceDiameter={170}
        />
        <figcaption style={CAPTION}>Cockpit repeater: the vessel, needles and inside tickmarks</figcaption>
      </figure>

      <figure style={FIGURE}>
        <Heading
          heading={heading}
          courseOverGround={courseOverGround}
          headingSetpoint={0.3}
          headingSetpointAtZeroDeadband={0.5}
          headingSetpointOverride
          atHeadingSetpoint={false}
          hdgArrowStyle={HdgArrowStyle.beamLine}
          cogArrowStyle={CogArrowStyle.velocityVector}
          faceDiameter={110}
        />
        <figcaption style={CAPTION}>Chart table: beam line, velocity vector, setpoint snapped to north</figcaption>
      </figure>
    </div>
  );
}

Heading is the lighter sibling of the compass. It shows the heading, the course over ground, the heading setpoint and advice arcs, with no wind, current or rate-of-turn layers. A vessel silhouette or a cluster of center readouts promotes the face to the double-band ring. It ports obc-heading; the overview lists the other instruments.

Examples

Heading

The heading and course over ground arrows on the double-band card, with the heading setpoint marker.
Code
import { Heading } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "The heading and course over ground arrows on the double-band card, with the heading setpoint marker.";

export default function HeadingExample(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <Heading heading={47} courseOverGround={52} headingSetpoint={60} />
    </div>
  );
}

With vessel

ESW
The vessel silhouette in the center, turning with the heading, and the N, E, S and W letters.
Code
import { Heading, VesselImage } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "The vessel silhouette in the center, turning with the heading, and the N, E, S and W letters.";

export default function WithVessel(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <Heading heading={47} courseOverGround={52} vesselImage={VesselImage.genericTop} showLabels />
    </div>
  );
}

With readouts

ESW
47
HDG
DEG
52
COG
DEG
‒
ROT
°/min
Heading, course over ground and rate of turn as readouts in the center, on a face pinned to 240 pixels.
Code
import { Heading, 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 <Heading heading={47} courseOverGround={52} centerReadouts={CENTER_READOUTS} showLabels faceDiameter={240} />;
}

Small face

ESW
A heading card on a face pinned to 180 pixels, with the heading setpoint marker.
Code
import { Heading } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A heading card on a face pinned to 180 pixels, with the heading setpoint marker.";

export default function SmallFace(): ReactElement {
  return <Heading heading={47} courseOverGround={52} headingSetpoint={60} showLabels faceDiameter={180} />;
}

Measured heading and commanded heading

Name the heading reference and keep course over ground distinct from the bow direction. Supply current heading, accepted setpoint and a draft setpoint as separate values. The focus marker previews an adjustment; it is not confirmation that an autopilot accepted a command.

At-setpoint styling compares supplied values or follows the caller's flag. It does not establish that steering control is engaged. Show control mode and source integrity nearby, and exercise the north crossing so a small angular difference cannot be read as almost a complete turn.

Props

PropTypeDefaultDescription
headingnumber | undefined0The current heading of the vessel in degrees.
courseOverGroundnumber | undefined0The current course over ground in degrees.
headingSetpointnumber | null | undefinednullThe set point for the heading in degrees.
newHeadingSetpointnumber | undefined–New setpoint being adjusted (renders a second, focus-state marker).
atHeadingSetpointboolean | undefinedfalseManual at-setpoint flag; used when autoAtHeadingSetpoint is false.
headingSetpointAtZeroDeadbandnumber | undefined0.5Zero-snap deadband for the setpoint marker, in degrees.
headingSetpointOverrideboolean | undefinedfalseDerive the setpoint color from priority even when loading/off.
autoAtHeadingSetpointboolean | undefinedtrueEnables automatic at-setpoint calculation.
autoAtHeadingSetpointDeadbandnumber | undefined2The deadband for auto at-setpoint detection in degrees.
animateSetpointboolean | undefinedfalseAnimate setpoint confirm transitions.
touchingboolean | undefinedfalseIndicates the user is actively adjusting (focus rendering).
headingAdvicesAngleAdvice[] | undefined[]Caution/alert arcs; triggered state derives from the current heading.
vesselImageVesselImage | undefined–The vessel silhouette shown in the center, rotating with heading. When unset (default) no vessel is shown; hidden while centerReadouts is non-empty.
centerReadoutsCompassCenterReadout[] | 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 has no input on this instrument and renders a dash) and colors follow priorityElements. While non-empty, the crosshair's center is cut out so the readouts sit on a clean face.
hdgArrowStyleHdgArrowStyle | undefinedHdgArrowStyle.needleHDG arrow style: needle (default), arrowHead, vector, or beamLine.
cogArrowStyleCogArrowStyle | undefinedCogArrowStyle.needleCOG arrow style: needle (default), arrowHead, vector, or velocityVector.
directionCompassDirection | undefinedCompassDirection.NorthUpOrientation mode: north-up (default), heading-up, or course-up.
priorityPriority | undefinedPriority.regularPriority.enhanced uses the blue palette for the elements in priorityElements.
priorityElementsHeadingPriorityElement[] | undefined[HeadingPriorityElement.hdg]Which elements the enhanced priority applies to (default: hdg).
showLabelsboolean | undefinedfalseShow compass NSEW labels. The north arrow is always shown, independent of this flag.
tickmarksInsideboolean | undefinedfalseWhen true, labels and north arrow are placed inside the outer ring.
faceDiameternumber | 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.
classNamestring | undefined–Class names added to the instrument's root element.
  • Compass: the same card with the wind, current and rate-of-turn layers.
  • Data integrity: the states a value can be in, and what the crew does.