Skip to content
design
Instruments

Pitch

The pitch (trim) of the vessel in degrees on a side arc, with an average band and caution zones.

On this page
Both arcs, the vessel and the ten-second band
A 40 degree arc, zoomed to fill the box
-1.0
Pitch
DEG
The same pitch as a number
Motoring into a head sea on a live feed: the bow passes the caution threshold, on both arcs, zoomed onto a narrow arc and as a number.
Code
import { ObcPitchType, Pitch, Priority, VesselImage } from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "Motoring into a head sea on a live feed: the bow lifts and drops past the 8 degree caution threshold, the band " +
  "of the last ten seconds follows, and three views show it on both arcs, zoomed onto a narrow arc, and as a number.";

const SAMPLE_SECONDS: number = 0.5;
const TREND_SECONDS: number = 10;
const CAUTION_DEGREES: number = 8;

const PANEL: CSSProperties = {
  display: "flex",
  flexWrap: "wrap",
  alignItems: "flex-end",
  justifyContent: "center",
  gap: 24,
  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: 200 };

/** Pitch in degrees at a time in seconds, positive bow up: the head sea's period under a building envelope. */
function pitchAt(time: number): number {
  const envelope: number = 0.75 + 0.35 * Math.sin(time / 11);
  return -1 + envelope * (7 * Math.sin((2 * Math.PI * time) / 5.5) + 2 * Math.sin((2 * Math.PI * time) / 3.1));
}

/** The lowest and highest pitch over the trend window ending at `time`. */
function bandAt(time: number): { low: number; high: number } {
  let low: number = Number.POSITIVE_INFINITY;
  let high: number = Number.NEGATIVE_INFINITY;
  for (let past: number = 0; past <= TREND_SECONDS; past += SAMPLE_SECONDS) {
    const value: number = pitchAt(time - past);
    low = Math.min(low, value);
    high = Math.max(high, value);
  }
  return { low, high };
}

export default function Showcase(): ReactElement {
  const [sample, setSample]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);

  useEffect((): (() => void) => {
    const interval: number = window.setInterval((): void => {
      setSample((previous: number): number => previous + 1);
    }, SAMPLE_SECONDS * 1000);
    return (): void => {
      window.clearInterval(interval);
    };
  }, []);

  const time: number = sample * SAMPLE_SECONDS;
  const pitch: number = pitchAt(time);
  const { low, high }: { low: number; high: number } = bandAt(time);
  const beyondCaution: boolean = Math.max(Math.abs(low), Math.abs(high)) > CAUTION_DEGREES;

  return (
    <div style={PANEL}>
      <figure style={FIGURE}>
        <div style={{ width: 260, height: 260 }}>
          <Pitch
            pitch={pitch}
            minAvgPitch={low}
            maxAvgPitch={high}
            vesselImageSide={VesselImage.genericSide}
            maxPitchAdvice={CAUTION_DEGREES}
            triggerPitchAdvice={beyondCaution}
            type={ObcPitchType.dualScale}
            priority={Priority.enhanced}
          />
        </div>
        <figcaption style={CAPTION}>Both arcs, the vessel and the ten-second band</figcaption>
      </figure>

      <figure style={FIGURE}>
        <div style={{ width: 170, height: 170 }}>
          <Pitch
            pitch={pitch}
            minAvgPitch={low}
            maxAvgPitch={high}
            vesselImageSide={VesselImage.genericSide}
            maxPitchAdvice={CAUTION_DEGREES}
            triggerPitchAdvice={beyondCaution}
            arcAngle={20}
            zoomToFitArc
          />
        </div>
        <figcaption style={CAPTION}>A 40 degree arc, zoomed to fill the box</figcaption>
      </figure>

      <figure style={FIGURE}>
        <div style={{ width: 150, height: 150 }}>
          <Pitch
            pitch={pitch}
            minAvgPitch={low}
            maxAvgPitch={high}
            maxPitchAdvice={CAUTION_DEGREES}
            triggerPitchAdvice={beyondCaution}
            hasReadout
            label="Pitch"
            unit="DEG"
            fractionDigits={1}
            priority={Priority.enhanced}
          />
        </div>
        <figcaption style={CAPTION}>The same pitch as a number</figcaption>
      </figure>
    </div>
  );
}

The pitch instrument shows the vessel's trim in degrees on an arc at the right, positive when the bow is up. A vessel silhouette and an indicator line turn with the value, a band marks the recent average, and caution zones start at the threshold the crew sets. It ports obc-pitch, which shares the single-axis inclinometer with Roll; the overview lists the other instruments.

Examples

Pitch

A trim of 4 degrees bow down, with the indicator turned to match.
Code
import { Pitch } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A trim of 4 degrees bow down, with the indicator turned to match.";

export default function PitchExample(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <Pitch pitch={-4} />
    </div>
  );
}

Bow-up convention and limits

Pitch is positive when the bow rises. Align the sensor's sign and reference before rendering, and label the window used for the recent-average band. The band is a summary of supplied observations, not a predicted motion.

Choose caution thresholds for the equipment and task. The visual zone does not configure an alert service. Keep a numeric angle and source-quality indication available, including when the silhouette reaches the edge of its useful visual range.

Props

PropTypeDefaultDescription
pitchnumber | undefined0Pitch (trim) angle in degrees; positive pitches bow-up.
minAvgPitchnumber | undefined0Lower edge of the average-pitch band, degrees.
maxAvgPitchnumber | undefined0Upper edge of the average-pitch band, degrees.
vesselImageSideVesselImage | undefinedVesselImage.psvSideSide-view vessel silhouette pitching with the value.
maxPitchAdvicenumber | undefined–Inner caution threshold in degrees; undefined hides the caution bands.
triggerPitchAdviceboolean | undefinedfalseWhether the caution bands are in the triggered state.
labelstring | undefined"Pitch"Readout label. Default Pitch.
unitstring | undefined"DEG"Readout unit. Default DEG.
fractionDigitsnumber | undefined0Number of fraction digits shown in the readout. Default 0.
typeObcPitchType | undefinedObcPitchType.singleScalesingle-scale shows one arc on the right (default); dual-scale also shows the scale on the opposite (left) arc.
zoomToFitArcboolean | undefinedfalseEnlarges the visible arc to fill the container; the vessel and the indicator keep their natural size. Default false.
hasReadoutboolean | undefinedfalseCenter readout instead of the horizon/indicator/vessel.
priorityPriority | undefinedPriority.regularColor priority: regular (gray palette, default) or enhanced (blue palette) for the scale fill, the indicator and the readout value.
arcAnglenumber | undefined45Half-extent of the watch arc in degrees (default 45).
classNamestring | undefined–Class names added to the instrument's root element.