Skip to content
design
Instruments

Gauge radial

A radial gauge for any numeric value, on a 270, 180 or 90 degree sweep, with a readout and advice zones.

On this page
01000200030004000
1750
Engine
rpm
406080100120
84.2
Coolant
°C
0246
3.2
Oil
bar
050100115
38
Alternator
A
0255075100
0
Generator
%
The engine panel on a live feed: revolutions, coolant, oil pressure, alternator load and the generator switched off.
Code
import {
  AdviceType,
  GaugeRadial,
  GaugeRadialHorizontalAlignment,
  GaugeRadialSector,
  GaugeRadialVerticalAlignment,
  InstrumentState,
  ObcGaugeRadialType,
  Priority,
  TickmarkStyle,
  type GaugeRadialAdvice,
} from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "The engine panel of a Yanmar 4JH80 on a live feed: revolutions on a needle with the cruise setpoint, coolant " +
  "filling a half dial into its caution zone, oil pressure and alternator load on quarter dials, and the " +
  "generator switched off.";

const RPM_ADVICES: GaugeRadialAdvice[] = [
  { minValue: 1600, maxValue: 2400, type: AdviceType.advice, hinted: true },
  { minValue: 3200, maxValue: 4000, type: AdviceType.caution, hinted: true },
];

const COOLANT_ADVICES: GaugeRadialAdvice[] = [
  { minValue: 70, maxValue: 88, type: AdviceType.advice, hinted: true },
  { minValue: 92, maxValue: 120, type: AdviceType.caution, hinted: true },
];

const PANEL: CSSProperties = {
  display: "grid",
  gridTemplateColumns: "repeat(auto-fit, minmax(190px, 1fr))",
  gap: 20,
  alignItems: "center",
  justifyItems: "center",
  width: "100%",
  maxWidth: 660,
};

const COLUMN: CSSProperties = { display: "flex", flexDirection: "column", gap: 12, alignItems: "center" };

/** A short, wide strip: the dial fits the height and sits at the left of the slack. */
const STRIP: CSSProperties = { width: 190, height: 110 };

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);
    };
  }, []);

  // The skipper eases the throttle up to 2400 rpm and back every half minute; the coolant follows behind.
  const load: number = (1 - Math.cos((second * Math.PI) / 15)) / 2;
  const rpm: number = 1750 + 650 * load + 25 * Math.sin(second * 1.7);
  const coolant: number = 82 + (13 * (1 - Math.cos(((second - 4) * Math.PI) / 15))) / 2;
  const oilPressure: number = 3.2 + 0.8 * load;
  const alternator: number = 38 + 14 * Math.sin(second / 6);

  return (
    <div style={PANEL}>
      <GaugeRadial
        value={rpm}
        minValue={0}
        maxValue={4000}
        type={ObcGaugeRadialType.needle}
        priority={Priority.enhanced}
        tickmarkStyle={TickmarkStyle.enhanced}
        setpoint={1800}
        autoAtSetpoint
        autoAtSetpointDeadband={100}
        animateSetpoint
        advices={RPM_ADVICES}
        primaryTickmarkInterval={1000}
        secondaryTickmarkInterval={500}
        tertiaryTickmarkInterval={100}
        showLabels
        hasReadout
        label="Engine"
        unit="rpm"
        faceDiameter={190}
      />

      <GaugeRadial
        sector={GaugeRadialSector.deg180}
        value={coolant}
        minValue={40}
        maxValue={120}
        advices={COOLANT_ADVICES}
        setpoint={85}
        primaryTickmarkInterval={20}
        secondaryTickmarkInterval={10}
        tertiaryTickmarkInterval={2}
        showLabels
        hasReadout
        label="Coolant"
        unit="°C"
        fractionDigits={1}
        faceDiameter={190}
      />

      <div style={COLUMN}>
        <GaugeRadial
          sector={GaugeRadialSector.deg90Left}
          value={oilPressure}
          minValue={0}
          maxValue={6}
          primaryTickmarkInterval={2}
          secondaryTickmarkInterval={1}
          showLabels
          tickmarksInside
          hasReadout
          label="Oil"
          unit="bar"
          fractionDigits={1}
          style={STRIP}
          horizontalAlignment={GaugeRadialHorizontalAlignment.left}
          verticalAlignment={GaugeRadialVerticalAlignment.bottom}
        />
        <GaugeRadial
          sector={GaugeRadialSector.deg90Right}
          type={ObcGaugeRadialType.bar}
          value={alternator}
          minValue={0}
          maxValue={115}
          primaryTickmarkInterval={50}
          secondaryTickmarkInterval={10}
          showLabels
          hasReadout
          label="Alternator"
          unit="A"
          style={STRIP}
          horizontalAlignment={GaugeRadialHorizontalAlignment.right}
          verticalAlignment={GaugeRadialVerticalAlignment.top}
        />
      </div>

      <GaugeRadial
        state={InstrumentState.off}
        value={0}
        minValue={0}
        maxValue={100}
        setpoint={70}
        setpointOverride
        primaryTickmarkInterval={25}
        secondaryTickmarkInterval={5}
        showLabels
        hasReadout
        label="Generator"
        unit="%"
        faceDiameter={150}
      />
    </div>
  );
}

The radial gauge shows any value on a range, such as a load, a temperature or a count of revolutions. It fills the arc from zero or turns a needle, marks advice and caution zones, and prints the value with its label and unit. sector selects the 270, 180 or 90 degree sweep, and faceDiameter pins the size so that gauges side by side match. On a pinned face the readout scales with the dial, but its label and unit never draw under 11 px. A 90 degree dial under 160 px tall has no room for the readout in its open corner, so it prints the value and unit in one line under the arc, and the dial gives that line its height. It ports obc-gauge-radial; the overview lists the other instruments.

Examples

Three quarter

0255075100
50
Load
%
The default 270 degree sweep, filled from zero to 50 percent, with the readout in the center.
Code
import { GaugeRadial } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "The default 270 degree sweep, filled from zero to 50 percent, with the readout in the center.";

export default function ThreeQuarter(): ReactElement {
  return (
    <GaugeRadial
      value={50}
      minValue={0}
      maxValue={100}
      faceDiameter={180}
      primaryTickmarkInterval={25}
      secondaryTickmarkInterval={5}
      showLabels
      hasReadout
      label="Load"
      unit="%"
    />
  );
}

Needle and setpoint

0255075100
50
RPM
×100
A needle in the enhanced palette at 50, with the setpoint marker at 75.
Code
import { GaugeRadial, ObcGaugeRadialType, Priority } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A needle in the enhanced palette at 50, with the setpoint marker at 75.";

export default function NeedleAndSetpoint(): ReactElement {
  return (
    <GaugeRadial
      value={50}
      minValue={0}
      maxValue={100}
      faceDiameter={180}
      type={ObcGaugeRadialType.needle}
      priority={Priority.enhanced}
      setpoint={75}
      primaryTickmarkInterval={25}
      secondaryTickmarkInterval={5}
      showLabels
      hasReadout
      label="RPM"
      unit="×100"
    />
  );
}

Half sector

0255075100
62
Trim
%
A 180 degree sweep at 62 percent, with the readout below the arc.
Code
import { GaugeRadial, GaugeRadialSector } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A 180 degree sweep at 62 percent, with the readout below the arc.";

export default function HalfSector(): ReactElement {
  return (
    <GaugeRadial
      sector={GaugeRadialSector.deg180}
      value={62}
      minValue={0}
      maxValue={100}
      faceDiameter={180}
      primaryTickmarkInterval={25}
      secondaryTickmarkInterval={5}
      showLabels
      hasReadout
      label="Trim"
      unit="%"
    />
  );
}

Quarter sector

0255075100
62
Aux
%
A 90 degree sweep on the right at 62 percent; the dial is under 160 px tall, so the value and unit sit under the arc.
Code
import { GaugeRadial, GaugeRadialSector } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "A 90 degree sweep on the right at 62 percent; the dial is under 160 px tall, so the value and unit sit under the arc.";

export default function QuarterSector(): ReactElement {
  return (
    <GaugeRadial
      sector={GaugeRadialSector.deg90Right}
      value={62}
      minValue={0}
      maxValue={100}
      faceDiameter={180}
      primaryTickmarkInterval={25}
      secondaryTickmarkInterval={5}
      showLabels
      hasReadout
      label="Aux"
      unit="%"
    />
  );
}

Advice triggered

0255075100
80
Temp
°C
A temperature of 80 inside the caution zone, which is drawn as triggered, beside an advice zone that is only hinted.
Code
import { AdviceType, GaugeRadial } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "A temperature of 80 inside the caution zone, which is drawn as triggered, beside an advice zone that is only hinted.";

export default function AdviceTriggered(): ReactElement {
  return (
    <GaugeRadial
      value={80}
      minValue={0}
      maxValue={100}
      faceDiameter={180}
      primaryTickmarkInterval={25}
      secondaryTickmarkInterval={5}
      showLabels
      hasReadout
      label="Temp"
      unit="°C"
      advices={[
        { minValue: 70, maxValue: 100, type: AdviceType.caution, hinted: true },
        { minValue: 25, maxValue: 60, type: AdviceType.advice, hinted: true },
      ]}
    />
  );
}

No reading

A value that is not a number draws no bar, so the gauge shows no reading.
Code
import { GaugeRadial } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "A value that is not a number draws no bar, so the gauge shows no reading.";

export default function NoReading(): ReactElement {
  return (
    <div style={{ width: 200, height: 200 }}>
      <GaugeRadial value={Number.NaN} minValue={0} maxValue={100} />
    </div>
  );
}

Choose the domain before the dial

Set the minimum, maximum, unit and advice ranges from the quantity being shown. Keep comparable gauges on the same domain and use faceDiameter to align their visible faces. Equal outer boxes can contain different face sizes when labels or external scales take space.

Choose the sector for the space available without losing readable ticks or the exact value. Advice ranges are visual guidance; the application owns threshold evaluation and alerts. If a value can exceed the displayed range, retain its numeric value and state rather than letting a needle at the limit hide the excursion.

Props

PropTypeDefaultDescription
valuenumber | undefined0Value to show, in the unit of minValue and maxValue. Clamped to the range. A value that is not a number shows no reading. Default 0.
maxValuenumber | undefined100Value at the end of the scale. Default 100.
minValuenumber | undefined0Value at the start of the scale. Default 0.
showLabelsboolean | undefinedfalseShow numeric labels at primary ticks and the zero/min/max positions.
primaryTickmarkIntervalnumber | undefined50Interval for primary tickmarks in value units; undefined/<= 0 shows none.
secondaryTickmarkIntervalnumber | undefined10Interval for secondary tickmarks in value units; undefined/<= 0 shows none.
tertiaryTickmarkIntervalnumber | undefined–Interval for tertiary tickmarks in value units; undefined/<= 0 shows none.
stateInstrumentState | undefinedInstrumentState.activeInstrument state: active (default), loading or off. loading and off draw the bar, the needle and the setpoint marker disabled.
priorityPriority | undefinedPriority.regularColor priority: regular (gray palette, default) or enhanced (blue palette) for the bar, the needle and the setpoint marker.
typeObcGaugeRadialType | undefinedObcGaugeRadialType.filledHow the value is drawn: filled (default) fills the arc from zero, or from the nearest end of the range, to the value; bar draws that arc in the quieter tertiary color with a short marker at the value; needle draws a pointer from the center instead of an arc.
tickmarksInsideboolean | undefinedfalseDraws the tickmarks and their labels inside the ring instead of outside it. Default false.
tickmarkStyleTickmarkStyle | undefinedTickmarkStyle.regularTickmark color style. regular (default) draws every tick in the dim tertiary color; enhanced draws ticks in the brighter primary color and tertiary ticks in the secondary color.
advicesGaugeRadialAdvice[] | undefined[]Caution/alert arcs. Ignored on sector: 90-left / 90-right.
sectorGaugeRadialSector | undefinedGaugeRadialSector.deg270Arc span: 270 (default), 180, 90-left, or 90-right.
horizontalAlignmentGaugeRadialHorizontalAlignment | undefinedGaugeRadialHorizontalAlignment.centerHorizontal placement of the dial when the host is wider than the dial (e.g. a short, wide container shrinks the dial to fit the height, leaving horizontal slack). Default center.
verticalAlignmentGaugeRadialVerticalAlignment | undefinedGaugeRadialVerticalAlignment.centerVertical placement of the dial when the host is taller than the dial (e.g. a tall, narrow container shrinks the dial to fit the width, leaving vertical slack). Default center.
hasReadoutboolean | undefinedfalseWhen true, shows the center readout(s) with the current value (and optional label/unit). Layout depends on sector and type — see the placement matrix in the component doc. Default false.
labelstring | undefined""Name shown with the readout, such as Load. Default empty.
unitstring | undefined""Unit shown with the readout, such as %. Default empty.
fractionDigitsnumber | undefined0Number of fraction digits shown in the readout. Default 0.
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.
styleCSSProperties | undefined–Inline style for the instrument's root element.
setpointnumber | undefined–Target setpoint value.
newSetpointnumber | undefined–Adjustment preview (2-step interface): while defined, the original setpoint marker dims and a focus-state marker renders at this value.
atSetpointboolean | undefined–Manual at-setpoint override; used when autoAtSetpoint is false.
touchingboolean | undefined–User is physically interacting with the control. Forces focus rendering and suppresses at-setpoint detection while true.
autoAtSetpointboolean | undefined–Enable automatic at-setpoint detection (default true).
autoAtSetpointDeadbandnumber | undefined–Tolerance for auto at-setpoint detection (default 2).
setpointAtZeroDeadbandnumber | undefined–Zero-snap tolerance for the marker's equalZero state (default 0.5).
setpointOverrideboolean | undefined–Derive the setpoint color from priority even when loading/off.
animateSetpointboolean | undefined–Enable the CSS-animated confirm transition.