Bar vertical
A vertical bar with a scale of tickmarks and labels, drawn in SVG at a height you give it.
On this page
Throttle: Cruising, 2,200 rpm
Code
import {
AdvicePosition,
AdviceType,
BarContainerStyle,
BarVertical,
BorderRadiusPosition,
ExternalScaleSide,
FillMode,
FrameStyle,
InstrumentState,
Priority,
Readout,
ScaleType,
} from "@bosunkit/instruments";
import type { ExternalScaleAdvice, ReadoutProps } from "@bosunkit/instruments";
import { useEffect, useState } from "react";
import type { CSSProperties, Dispatch, ReactElement, ReactNode, SetStateAction } from "react";
export const description: string =
"Althea's engine bars on a one-second feed: the coolant's normal range and a hinted caution range beside its scale, " +
"the house current filled from zero on a condensed scale at the left, the throttle's setpoint the crew adjusts " +
"and its caution range inside the bar, the fuel flow as a tinted band with a dot at the value on a flat frame, and " +
"the watermaker off with its pressure setpoint kept.";
/** Feed steps before the throttle sequence repeats. */
const CYCLE: number = 24;
const BAR_HEIGHT: number = 240;
const COOLANT_ADVICE: ExternalScaleAdvice[] = [
{ min: 75, max: 90, type: AdviceType.advice, hinted: false },
{ min: 95, max: 110, type: AdviceType.caution, hinted: true },
];
const THROTTLE_ADVICE: ExternalScaleAdvice[] = [{ min: 85, max: 100, type: AdviceType.caution, hinted: false }];
const CELL_STYLE: CSSProperties = { display: "grid", justifyItems: "center", alignContent: "start", gap: 6 };
const NAME_STYLE: CSSProperties = { fontSize: 12, color: "var(--element-neutral-color)" };
interface Throttle {
setpoint: number;
newSetpoint: number | undefined;
touching: boolean;
note: string;
}
/**
* The helmsman's throttle sequence: cruising at 62 percent, takes the lever, previews 90 percent, confirms it into the
* caution range to motor off a lee shore, then eases back.
*/
function throttleAt(step: number): Throttle {
if (step < 6) {
return { setpoint: 62, newSetpoint: undefined, touching: false, note: "Cruising, 2,200 rpm" };
}
if (step < 9) {
return { setpoint: 62, newSetpoint: 90, touching: true, note: "Lever moved, waiting to confirm" };
}
if (step < 18) {
return { setpoint: 90, newSetpoint: undefined, touching: false, note: "Above 85 % for 10 min at most" };
}
return { setpoint: 62, newSetpoint: undefined, touching: false, note: "Eased back to cruising" };
}
function Cell({ readout, children }: { readout: ReadoutProps; children: ReactNode }): ReactElement {
return (
<div style={CELL_STYLE}>
<Readout {...readout} />
{children}
</div>
);
}
export default function Showcase(): ReactElement {
const [step, setStep]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
useEffect((): (() => void) => {
const timer: number = window.setInterval((): void => {
setStep((previous: number): number => previous + 1);
}, 1000);
return (): void => {
window.clearInterval(timer);
};
}, []);
const phase: number = step % CYCLE;
const throttle: Throttle = throttleAt(phase);
const throttleLevel: number = phase >= 9 && phase < 18 ? Math.min(90, 62 + (phase - 8) * 6) : 62;
const coolant: number = 84 + 3 * Math.sin(step / 3) + (throttle.setpoint > 85 ? (phase - 9) * 0.9 : 0);
const batteryCurrent: number = Math.round(-18 + 46 * Math.max(0, Math.sin(step / 5)));
const fuelFlow: number = 1.2 + (throttleLevel / 100) * 4.6;
return (
<div style={{ display: "grid", gap: 10, width: "100%", maxWidth: 620 }}>
<div style={{ display: "flex", flexWrap: "wrap", gap: 14, justifyContent: "center" }}>
<Cell readout={{ label: "Coolant", value: coolant, unit: "°C", maxDigits: 3 }}>
<BarVertical
height={BAR_HEIGHT}
minValue={40}
maxValue={110}
value={coolant}
hasBar
primaryTickmarkInterval={10}
secondaryTickmarkInterval={5}
advices={COOLANT_ADVICE}
advicePosition={AdvicePosition.outer}
priority={Priority.enhanced}
/>
</Cell>
<Cell readout={{ label: "House current", value: batteryCurrent, unit: "A", maxDigits: 2 }}>
<BarVertical
height={BAR_HEIGHT}
minValue={-60}
maxValue={60}
value={batteryCurrent}
fillMin={0}
fillMax={batteryCurrent}
hasBar
side={ExternalScaleSide.left}
mainTickmarks={[-60, 0, 60]}
primaryTickmarkInterval={20}
secondaryTickmarkInterval={10}
scaleType={ScaleType.condensed}
scaleBackground
barContainerStyle={BarContainerStyle.secondary}
/>
</Cell>
<Cell readout={{ label: "Throttle", value: throttleLevel, unit: "%", maxDigits: 3 }}>
<BarVertical
height={BAR_HEIGHT}
minValue={0}
maxValue={100}
value={throttleLevel}
hasBar
mainTickmarks={[0, 100]}
barThickness={32}
primaryTickmarkInterval={20}
secondaryTickmarkInterval={10}
tertiaryTickmarkInterval={5}
setpoint={throttle.setpoint}
newSetpoint={throttle.newSetpoint}
touching={throttle.touching}
autoAtSetpointDeadband={3}
animateSetpoint
advices={THROTTLE_ADVICE}
advicePosition={AdvicePosition.center}
priority={Priority.enhanced}
/>
</Cell>
<Cell readout={{ label: "Fuel flow", value: fuelFlow, unit: "L/h", fractionDigits: 1 }}>
<BarVertical
height={BAR_HEIGHT}
minValue={0}
maxValue={8}
value={fuelFlow}
hasBar
fillMode={FillMode.tint}
fillMin={2.6}
fillMax={3.8}
mainTickmarks={[0, 8]}
barFillColor="var(--automation-medium-fuel)"
highlightCurrentValue
primaryTickmarkInterval={2}
secondaryTickmarkInterval={1}
frameStyle={FrameStyle.flat}
borderRadiusPosition={BorderRadiusPosition.outerLastChild}
borderRadius={4}
labelThickness={36}
/>
</Cell>
<Cell readout={{ label: "Watermaker", value: 0, unit: "bar", off: true, offText: "Off" }}>
<BarVertical
height={BAR_HEIGHT}
minValue={0}
maxValue={70}
value={0}
hasBar
state={InstrumentState.off}
mainTickmarks={[0, 70]}
setpoint={55}
primaryTickmarkInterval={10}
showLabels={false}
tickThickness={16}
/>
</Cell>
</div>
<p style={{ margin: 0, textAlign: "center", ...NAME_STYLE }}>Throttle: {throttle.note}</p>
</div>
);
}
A vertical bar draws a value as a bar and sets a scale of tickmarks and labels beside it. height is the length in
pixels, and the width follows from the bands that are on: the bar, the tickmarks and the labels. side puts the scale
on the left or right of the bar. The bar fills from fillMin to the value, or tints a fixed range. It takes advice
ranges and a setpoint marker.
The vertical gauge is this bar with a fixed layout, a bar and a scale background that are always on.
Examples
Bar
Code
import { BarVertical } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "A vertical bar filled to 68 on a 0 to 100 scale, with a label every 25.";
export default function Bar(): ReactElement {
return (
<BarVertical hasBar value={68} minValue={0} maxValue={100} primaryTickmarkInterval={25} showLabels />
);
}
Align columns and their domains
Choose a common height, scale side and numeric domain when vertical bars form a comparison. Leave room for labels outside the bar; a parent that clips overflow can hide the very limits needed to read it.
A fill from a baseline, a tinted band and a setpoint marker have distinct meanings. State the quantity and unit and keep the exact reading available. Adapt missing or invalid samples before they reach the primitive rather than displaying them as an empty tank or zero load.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| minValue | number | undefined | 0 | Minimum scale value. |
| maxValue | number | undefined | 100 | Maximum scale value. |
| height | number | undefined | 320 | Total height in pixels (including padding bands). |
| paddingTop | number | undefined | BAR_CANVAS_PADDING | Padding above the drawing area. |
| paddingBottom | number | undefined | BAR_CANVAS_PADDING | Padding below the drawing area. |
| side | ExternalScaleSide | undefined | ExternalScaleSide.right | Which side this scale lives on ('left' | 'right'). |
| hasScale | boolean | undefined | true | Show scale tickmarks. |
| showLabels | boolean | undefined | true | Show numeric labels at primary tickmark intervals. |
| hasBar | boolean | undefined | false | Show bar. |
| scaleBackground | boolean | undefined | false | Show background behind the scale tickmarks. |
| barContainerStyle | BarContainerStyle | undefined | – | Bar container background style. When undefined, defaults based on scaleBackground. |
| barThickness | number | undefined | 24 | Bar/fill thickness in pixels. |
| tickThickness | number | undefined | 24 | Tickmark band thickness in pixels. |
| labelThickness | number | undefined | 60 | Label band thickness in pixels. |
| mainTickmarks | number[] | undefined | [] | Array of values for main tickmarks. When undefined, no main tickmarks are shown. When empty array [], defaults to [minValue, 0, maxValue]. |
| primaryTickmarkInterval | number | undefined | – | Interval for primary (longest) tickmarks with labels. |
| secondaryTickmarkInterval | number | undefined | – | Interval for secondary (medium) tickmarks. |
| tertiaryTickmarkInterval | number | undefined | – | Interval for tertiary (shortest) tickmarks. |
| scaleType | ScaleType | undefined | ScaleType.regular | Scale display mode: regular or condensed (shorter ticks). |
| frameStyle | FrameStyle | undefined | FrameStyle.regular | Frame style: regular (4px gap for all ticks) or flat (main tickmarks touch the edge). |
| borderRadiusPosition | BorderRadiusPosition | undefined | – | Border radius position based on component layout. |
| borderRadius | number | undefined | – | Border radius in pixels. Upstream reads this from the
--instrument-components-watchface-frame-regular-border-radius CSS
variable via a MutationObserver; this port takes the numeric value
directly, defaulting to 8px regular / 4px condensed. |
| priority | Priority | undefined | Priority.regular | Color priority: enhanced uses blue instrument colors for bar fill and setpoint. |
| fillMode | FillMode | undefined | FillMode.fill | Fill visualization mode: fill or tint. |
| barFillColor | string | undefined | – | Optional fill paint override; loading/off state still renders transparent. |
| fillMin | number | undefined | – | Minimum fill value (defaults to 0). |
| fillMax | number | undefined | – | Maximum fill value (defaults to value). |
| value | number | undefined | – | Current value (bar fill level). |
| state | InstrumentState | undefined | InstrumentState.active | Instrument state (affects colors and some marker behavior). |
| advicePosition | AdvicePosition | undefined | AdvicePosition.inner | Advice overlay positioning: center (in bar), inner (covers minor ticks), outer (no overlap). |
| advices | ExternalScaleAdvice[] | undefined | [] | Advice/alert overlays. When undefined or empty, no advice shown. |
| highlightCurrentValue | boolean | undefined | false | Display a dot indicator at the current value position instead of relying on bar fill. |
| className | string | undefined | – | Class name added to the root element. |
| style | CSSProperties | undefined | – | Inline style added to the root element. |
| setpoint | number | undefined | – | Target setpoint value. |
| newSetpoint | number | undefined | – | Adjustment preview (2-step interface): while defined, the original setpoint marker dims and a focus-state marker renders at this value. |
| atSetpoint | boolean | undefined | – | Manual at-setpoint override; used when autoAtSetpoint is false. |
| touching | boolean | undefined | – | User is physically interacting with the control. Forces focus rendering and suppresses at-setpoint detection while true. |
| autoAtSetpoint | boolean | undefined | – | Enable automatic at-setpoint detection (default true). |
| autoAtSetpointDeadband | number | undefined | – | Tolerance for auto at-setpoint detection (default 2). |
| setpointAtZeroDeadband | number | undefined | – | Zero-snap tolerance for the marker's equalZero state (default 0.5). |
| setpointOverride | boolean | undefined | – | Derive the setpoint color from priority even when loading/off. |
| animateSetpoint | boolean | undefined | – | Enable the CSS-animated confirm transition. |
Related
- Gauge vertical: the same bar with a fixed layout.
- Bar horizontal: the bar on a horizontal axis.
- Instruments: the other instruments.