Skip to content
design
Instruments

Rate of turn

The rate of turn in degrees per minute, as circling dots, a track bar and a readout.

On this page
0.0
ROT
°/min
Track bar by side, inner dots and the readout
Bar arc on the scale, enhanced, zero below 1 degree
Dots by side, spun faster
Tacking up the channel on a live feed: a track bar colored by side with a readout, a bar arc on the scale, and circling dots.
Code
import { Priority, RateOfTurn, RotPosition, RotType, WatchCircleType } from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "Tacking up the channel on a live feed: the turn rate swings to starboard, settles, and swings to port, shown as " +
  "a track bar colored by side with a readout, as a bar arc on the scale in the enhanced palette, and as circling " +
  "dots.";

const PANEL: CSSProperties = {
  display: "flex",
  flexWrap: "wrap",
  alignItems: "flex-end",
  justifyContent: "center",
  gap: 28,
  width: "100%",
  maxWidth: 660,
};

const FIGURE: CSSProperties = { display: "flex", flexDirection: "column", alignItems: "center", gap: 6, margin: 0 };
const CAPTION: CSSProperties = { fontSize: 13, color: "var(--text-dim)", textAlign: "center", maxWidth: 220 };

/** Bar arc, in degrees, that a full 60 degrees per minute fills on the scale view. */
const BAR_ARC_AT_FULL_RATE: number = 45;

/**
 * Rate of turn in degrees per minute at a time in seconds: a tack to starboard, a steady leg, a tack to port and a
 * steady leg, every forty seconds.
 */
function rateAt(time: number): number {
  const phase: number = time % 40;
  if (phase < 8) {
    return 48 * Math.sin((Math.PI * phase) / 8);
  }
  if (phase >= 20 && phase < 28) {
    return -42 * Math.sin((Math.PI * (phase - 20)) / 8);
  }
  return 1.5 * Math.sin(time * 1.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 rate: number = rateAt(second);

  return (
    <div style={PANEL}>
      <figure style={FIGURE}>
        <div style={{ width: 240, height: 240 }}>
          <RateOfTurn
            rateOfTurnDegreesPerMinute={rate}
            hasTrackBar
            rotMaxValue={60}
            rotArcExtent={60}
            rotPortStarboard
            rotType={RotType.dots}
            rotPosition={RotPosition.innerCircle}
            watchCircleType={WatchCircleType.triple}
            hasReadout
            label="ROT"
            unit="°/min"
            fractionDigits={1}
          />
        </div>
        <figcaption style={CAPTION}>Track bar by side, inner dots and the readout</figcaption>
      </figure>

      <figure style={FIGURE}>
        <div style={{ width: 170, height: 170 }}>
          <RateOfTurn
            rateOfTurnDegreesPerMinute={rate}
            rotType={RotType.bar}
            rotPosition={RotPosition.scale}
            barStartAngle={0}
            barEndAngle={(rate / 60) * BAR_ARC_AT_FULL_RATE}
            rotAtZeroDeadband={1}
            priority={Priority.enhanced}
            watchCircleType={WatchCircleType.double}
          />
        </div>
        <figcaption style={CAPTION}>Bar arc on the scale, enhanced, zero below 1 degree</figcaption>
      </figure>

      <figure style={FIGURE}>
        <div style={{ width: 130, height: 130 }}>
          <RateOfTurn rateOfTurnDegreesPerMinute={rate} rotDotAnimationFactor={30} rotPortStarboard />
        </div>
        <figcaption style={CAPTION}>Dots by side, spun faster</figcaption>
      </figure>
    </div>
  );
}

The rate-of-turn instrument shows how fast the vessel swings, in degrees per minute, positive to starboard. Dots circle at a speed that follows the rate. A track bar adds the amount of the turn as an arc, and rotPortStarboard colors it by side. It ports obc-rate-of-turn; the overview lists the other instruments.

Examples

Rate of turn

Dots that circle starboard at a speed set by a turn of 15 degrees per minute.
Code
import { RateOfTurn } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "Dots that circle starboard at a speed set by a turn of 15 degrees per minute.";

export default function RateOfTurnExample(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <RateOfTurn rateOfTurnDegreesPerMinute={15} />
    </div>
  );
}

Port bar

A turn of 24 degrees per minute to port as a track bar in the port color.
Code
import { RateOfTurn, WatchCircleType } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A turn of 24 degrees per minute to port as a track bar in the port color.";

export default function PortBar(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <RateOfTurn
        rateOfTurnDegreesPerMinute={-24}
        hasTrackBar
        rotPortStarboard
        watchCircleType={WatchCircleType.double}
      />
    </div>
  );
}

Enhanced bar

A turn of 36 degrees per minute to starboard as a track bar in the enhanced palette.
Code
import { Priority, RateOfTurn, WatchCircleType } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "A turn of 36 degrees per minute to starboard as a track bar in the enhanced palette.";

export default function EnhancedBar(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <RateOfTurn
        rateOfTurnDegreesPerMinute={36}
        hasTrackBar
        priority={Priority.enhanced}
        watchCircleType={WatchCircleType.double}
      />
    </div>
  );
}

With readout

15
ROT
DEG/min
The turn as a number in the center, labeled ROT, beside the circling dots.
Code
import { RateOfTurn } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "The turn as a number in the center, labeled ROT, beside the circling dots.";

export default function WithReadout(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <RateOfTurn rateOfTurnDegreesPerMinute={15} hasReadout />
    </div>
  );
}

Rate, direction and age

Supply degrees per minute with starboard positive. A heading difference is not a rate until the sampling interval and angular wrap have been accounted for in the data layer. Keep the current numeric value available so motion is not the only way to read the instrument.

The moving dots visualize the supplied rate; they do not establish that updates are arriving. Surface stale or invalid measurements beside the instrument and check the reduced-motion presentation. A report should identify the recorded interval and remain understandable from a static capture.

Props

PropTypeDefaultDescription
rateOfTurnDegreesPerMinutenumber | undefined–Measured rate of turn in degrees per minute (the maritime/AIS convention). Sign controls direction (positive = starboard/clockwise).
rotDotAnimationFactornumber | undefined18Visual amplification applied to the spinning dot animation. Default 18 keeps the legacy visual feel (≈1 rpm at 20°/min).
rotTypeRotType | undefinedRotType.dotsROT display: dots (spinning dots, default) or bar (arc bar with clipped dots).
rotPositionRotPosition | undefinedRotPosition.scaleTrack position: scale (outer ring, default here) or innerCircle.
priorityPriority | undefinedPriority.regularColor priority: regular (gray palette, default) or enhanced (blue palette) for the dots, the bars and the track bar.
barStartAnglenumber | undefined0Start angle of the static bar arc (0° = 12 o'clock, clockwise). Bar mode only.
barEndAnglenumber | undefined30End angle of the static bar arc. Bar mode only.
watchCircleTypeWatchCircleType | undefinedWatchCircleType.singleRing style matched to the surrounding instrument (e.g. triple in compass contexts).
rotPortStarboardboolean | undefinedfalseColors the dots and bars by the direction of the turn: starboard colors for a positive rate, port colors for a negative one. Default false.
rotAtZeroDeadbandnumber | undefinedROT_ZERO_DEADBAND_DEGSmallest 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.
hasTrackBarboolean | undefinedfalseWhen true, shows a bar in the ring band from the twelve o'clock position to the measured rate of turn, with a needle marker at its end and sector tickmarks. Pair with watchCircleType="double" for the banded face — or "triple" when rotPosition is innerCircle, so the dots keep their inner track. Driven by rateOfTurnDegreesPerMinute only.
rotMaxValuenumber | undefined60Bar-extent reference value in **degrees per minute**: the track bar reaches ±rotArcExtent when the measured ROT equals ±rotMaxValue. Default 60 aligns with ES-TRIN 2025/1 Art. 3.02.
rotArcExtentnumber | undefined60Arc extent of the track bar in degrees per direction. Default 60.
hasReadoutboolean | undefinedfalseWhen true, shows a centered readout with the measured rate of turn. Shows a dash while rateOfTurnDegreesPerMinute is unset.
labelstring | undefined"ROT"Readout label. Default ROT.
unitstring | undefined"DEG/min"Readout unit. Default DEG/min.
fractionDigitsnumber | undefined0Number of fraction digits shown in the readout. Default 0.
classNamestring | undefined–Class names added to the instrument's root element.
  • Compass: the same indicator on the heading card.
  • Rudder: the angle that causes the turn.
  • Data integrity: the states a value can be in, and what the crew does.