Roll
The roll (heel) of the vessel in degrees on a bottom arc, with an average band and caution zones.
On this page
Code
import { ObcRollType, Priority, Roll, VesselImage } from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"Close-hauled in gusts on a live feed: the yacht heels to starboard and rolls in the swell, the band of the last " +
"ten seconds follows, and the 20 degree caution zones trigger in the gusts, 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 = 20;
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 };
/** Heel in degrees at a time in seconds, positive to starboard: a steady heel, gusts and the roll of the swell. */
function rollAt(time: number): number {
const gust: number = Math.max(0, Math.sin(time / 6)) ** 3;
return 14 + 8 * gust + 3 * Math.sin((2 * Math.PI * time) / 6.5);
}
/** The lowest and highest heel 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 = rollAt(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 roll: number = rollAt(time);
const { low, high }: { low: number; high: number } = bandAt(time);
const beyondCaution: boolean = high > CAUTION_DEGREES;
return (
<div style={PANEL}>
<figure style={FIGURE}>
<div style={{ width: 260, height: 260 }}>
<Roll
roll={roll}
minAvgRoll={low}
maxAvgRoll={high}
vesselImageFore={VesselImage.sailingYachtFore}
scaleForeImage={1.4}
maxRollAdvice={CAUTION_DEGREES}
triggerRollAdvice={beyondCaution}
type={ObcRollType.dualScale}
priority={Priority.enhanced}
/>
</div>
<figcaption style={CAPTION}>Both arcs, the yacht enlarged and the ten-second band</figcaption>
</figure>
<figure style={FIGURE}>
<div style={{ width: 170, height: 170 }}>
<Roll
roll={roll}
minAvgRoll={low}
maxAvgRoll={high}
vesselImageFore={VesselImage.sailingYachtFore}
maxRollAdvice={CAUTION_DEGREES}
triggerRollAdvice={beyondCaution}
arcAngle={30}
zoomToFitArc
/>
</div>
<figcaption style={CAPTION}>A 60 degree arc, zoomed to fill the box</figcaption>
</figure>
<figure style={FIGURE}>
<div style={{ width: 150, height: 150 }}>
<Roll
roll={roll}
minAvgRoll={low}
maxAvgRoll={high}
maxRollAdvice={CAUTION_DEGREES}
triggerRollAdvice={beyondCaution}
hasReadout
label="Heel"
unit="DEG"
fractionDigits={1}
priority={Priority.enhanced}
/>
</div>
<figcaption style={CAPTION}>The same heel as a number</figcaption>
</figure>
</div>
);
}
The roll instrument shows the vessel's heel in degrees on an arc at the bottom, positive when the vessel rolls to
starboard. A fore-view 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-roll, which shares the single-axis inclinometer
with Pitch; the overview lists the other instruments.
Examples
Roll
Code
import { Roll } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "A heel of 8 degrees to starboard, with the indicator turned to match.";
export default function RollExample(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<Roll roll={8} />
</div>
);
}
Starboard convention and limits
Roll is positive to starboard. Keep the sensor reference, numeric readout and vessel silhouette consistent, and identify the window represented by the recent-average band. Do not infer a safe operating envelope from the size of the dial.
Caution zones depict configured limits; alert generation and acknowledgment live outside the instrument. Test both signs and missing measurements with the actual mounting convention. A centered silhouette alone cannot distinguish an upright vessel from a lost sensor.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| roll | number | undefined | 0 | Roll (heel) angle in degrees; positive rolls to starboard. |
| minAvgRoll | number | undefined | 0 | Lower edge of the average-roll band, degrees. |
| maxAvgRoll | number | undefined | 0 | Upper edge of the average-roll band, degrees. |
| vesselImageFore | VesselImage | undefined | VesselImage.psvFore | Fore-view vessel silhouette rolling with the value. |
| scaleForeImage | number | undefined | 1 | Extra vessel scaling (clamped to [0, 2]). |
| maxRollAdvice | number | undefined | – | Inner caution threshold in degrees; undefined hides the caution bands. |
| triggerRollAdvice | boolean | undefined | false | Whether the caution bands are in the triggered state. |
| label | string | undefined | "Roll" | Readout label. Default Roll. |
| unit | string | undefined | "DEG" | Readout unit. Default DEG. |
| fractionDigits | number | undefined | 0 | Number of fraction digits shown in the readout. Default 0. |
| type | ObcRollType | undefined | ObcRollType.singleScale | single-scale shows one arc at the bottom (default); dual-scale also
shows the scale on the top arc (the indicator's opposite end). |
| zoomToFitArc | boolean | undefined | false | Enlarges the visible arc to fill the container; the vessel and the indicator keep their natural size. Default
false. |
| hasReadout | boolean | undefined | false | Center readout instead of the horizon/indicator/vessel. |
| priority | Priority | undefined | Priority.regular | Color priority: regular (gray palette, default) or enhanced (blue palette) for the scale fill, the indicator
and the readout value. |
| arcAngle | number | undefined | 45 | Half-extent of the watch arc in degrees (default 45). |
| className | string | undefined | – | Class names added to the instrument's root element. |
Related
- Pitch: the same instrument for trim.
- Single-axis inclinometer: the shared renderer.
- Heave: the vertical motion.