Scatter chart
Inspect independent paired measurements with numeric spacing, source details and caller-formatted values.
On this page
Fuel consumption and RPM
Illustrative paired measurements
- Port engine
- Starboard engine
Code
import type { ReactElement } from "react";
import { ScatterChart } from "@bosunkit/components/scatter-chart";
import { ChartLegend } from "@bosunkit/components/chart-legend";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Illustrative engine observations retain repeated RPM values and separate paired samples.";
const rpmLabels: Readonly<Record<number, string>> = {
"1000": "1000 RPM",
"1200": "1200 RPM",
"1250": "1250 RPM",
"1500": "1500 RPM",
"1750": "1750 RPM",
"1800": "1800 RPM",
"2000": "2000 RPM",
"2200": "2200 RPM",
"2250": "2250 RPM",
"2500": "2500 RPM",
};
const fuelLabels: Readonly<Record<number, string>> = {
"0": "0 L/hr",
"3.2": "3.2 L/hr",
"3.8": "3.8 L/hr",
"4": "4 L/hr",
"7.1": "7.1 L/hr",
"7.6": "7.6 L/hr",
"7.8": "7.8 L/hr",
"8": "8 L/hr",
"11.2": "11.2 L/hr",
"12": "12 L/hr",
"12.4": "12.4 L/hr",
"16": "16 L/hr",
};
const formatRPM: (value: string | number) => string = (value: string | number): string =>
rpmLabels[Number(value)] ?? "";
const formatFuel: (value: number) => string = (value: number): string => fuelLabels[value] ?? "";
const config = {
port: { label: "Port engine", formatValue: formatFuel },
starboard: { label: "Starboard engine", formatValue: formatFuel },
};
export default function EngineObservations(): ReactElement {
return (
<ChartPanel
title="Fuel consumption and RPM"
description="Illustrative paired measurements"
legend={<ChartLegend config={config} label="Engine observations" mark="point" />}
>
<ScatterChart
data={[
{ rpm: 1800, port: 7.1, starboard: 7.8 },
{ rpm: 1200, port: 3.2, starboard: 3.8 },
{ rpm: 1800, port: 7.6, starboard: null },
{ rpm: 2200, port: 11.2, starboard: 12.4 },
]}
categoryKey="rpm"
categoryDomain={[1000, 2500]}
valueDomain={[0, 16]}
config={config}
series={["port", "starboard"]}
formatCategory={formatRPM}
formatAxisValue={formatFuel}
label="Illustrative fuel consumption versus RPM"
categoryLabel="RPM"
dataTableLabel="View paired observations"
missingValueText="Unavailable"
describeObservation={(): string => "Same observation time · illustrative"}
/>
</ChartPanel>
);
}
Use scatter plots for engine calibration, RPM versus fuel rate and actual versus expected values. Repeated and unordered x values remain separate observations. Each point must represent a defensible pair: the server owns observation-time matching, source identity and pairing tolerance. Missing measurements have no drawn point and remain explicit in the source table. Different series use different point shapes. A controlled numeric domain clips the drawing while retaining source values.
For a third quantity encoded by circle area, see the cost-versus-latency composition. It composes Recharts through ChartContainer and exposes request volume alongside both axes in a source table.
Examples
Composition
Fuel consumption and RPM
Illustrative paired measurements
- Port engine
- Starboard engine
Code
import type { ReactElement } from "react";
import { ScatterChart } from "@bosunkit/components/scatter-chart";
import { ChartLegend } from "@bosunkit/components/chart-legend";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Illustrative engine observations retain repeated RPM values and separate paired samples.";
const rpmLabels: Readonly<Record<number, string>> = {
"1000": "1000 RPM",
"1200": "1200 RPM",
"1250": "1250 RPM",
"1500": "1500 RPM",
"1750": "1750 RPM",
"1800": "1800 RPM",
"2000": "2000 RPM",
"2200": "2200 RPM",
"2250": "2250 RPM",
"2500": "2500 RPM",
};
const fuelLabels: Readonly<Record<number, string>> = {
"0": "0 L/hr",
"3.2": "3.2 L/hr",
"3.8": "3.8 L/hr",
"4": "4 L/hr",
"7.1": "7.1 L/hr",
"7.6": "7.6 L/hr",
"7.8": "7.8 L/hr",
"8": "8 L/hr",
"11.2": "11.2 L/hr",
"12": "12 L/hr",
"12.4": "12.4 L/hr",
"16": "16 L/hr",
};
const formatRPM: (value: string | number) => string = (value: string | number): string =>
rpmLabels[Number(value)] ?? "";
const formatFuel: (value: number) => string = (value: number): string => fuelLabels[value] ?? "";
const config = {
port: { label: "Port engine", formatValue: formatFuel },
starboard: { label: "Starboard engine", formatValue: formatFuel },
};
export default function EngineObservations(): ReactElement {
return (
<ChartPanel
title="Fuel consumption and RPM"
description="Illustrative paired measurements"
legend={<ChartLegend config={config} label="Engine observations" mark="point" />}
>
<ScatterChart
data={[
{ rpm: 1800, port: 7.1, starboard: 7.8 },
{ rpm: 1200, port: 3.2, starboard: 3.8 },
{ rpm: 1800, port: 7.6, starboard: null },
{ rpm: 2200, port: 11.2, starboard: 12.4 },
]}
categoryKey="rpm"
categoryDomain={[1000, 2500]}
valueDomain={[0, 16]}
config={config}
series={["port", "starboard"]}
formatCategory={formatRPM}
formatAxisValue={formatFuel}
label="Illustrative fuel consumption versus RPM"
categoryLabel="RPM"
dataTableLabel="View paired observations"
missingValueText="Unavailable"
describeObservation={(): string => "Same observation time · illustrative"}
/>
</ChartPanel>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| callout | ChartCallout | undefined | – | A visible summary bubble with caller-formatted value and context. |
| onCategoryDomainChange | ((range: readonly [number, number]) => void) | undefined | – | Receives a numeric viewport after drag-to-zoom or Shift-drag pan; requires categoryDomain. |
| maxGap | number | undefined | – | Maximum elapsed distance connected by a line; numeric categories only. |
| syncId | string | undefined | – | Charts sharing this identifier synchronize inspection by category value or nearest time. |
| syncTolerance | number | undefined | – | Maximum distance for synchronized numeric categories; zero requires exact matches. |
| interpolation | "linear" | "stepAfter" | undefined | – | Linear segments or step changes; linear by default. Missing values remain gaps. |
| datarequired | readonly T[] | – | Rows in display order; numeric time categories must be increasing. |
| categoryKeyrequired | ChartCategoryKey<NoInfer<T>> | – | The row field naming a category or locating a sample on a numeric axis. |
| configrequired | ChartConfig<NoInfer<T>> | – | Definitions for numeric series, shared with legends and controls. |
| seriesrequired | readonly ChartValueKey<NoInfer<T>>[] | – | The visible numeric fields, in display order. Filtering preserves the complete registry's domain. |
| formatCategoryrequired | (value: string | number) => string | – | Writes category ticks, tooltip headings and the first table column. |
| formatAxisValuerequired | (value: number) => string | – | Writes value-axis ticks; separate from each series' observation formatter. |
| labelrequired | string | – | The drawing's accessible name, naming the quantity and time window. |
| categoryLabelrequired | string | – | The first table column's heading, translated by the caller. |
| dataTableLabelrequired | string | – | The disclosure button and source table's accessible name, translated by the caller. |
| missingValueTextrequired | string | – | Display text for missing values in the tooltip and source table. |
| height | string | undefined | – | The plot height as a CSS length; the chart height token by default. |
| valueDomain | readonly [number, number] | undefined | – | An explicit increasing value domain that clips outlying values; bar domains always include zero. |
| showGrid | boolean | undefined | true | Draw the grid; on by default. |
| showAxes | boolean | undefined | true | Draw axes; on by default. Values remain available in the table when off. |
| motion | MotionProp | undefined | – | Entrance and exit motion; off by default. Measurements never interpolate. |
| children | ReactNode | undefined | – | Recharts annotations appended inside the plot, such as ReferenceLine. Use ChartContainer for a custom root. |
| className | string | undefined | – | Classes added to the root. |
| testId | string | undefined | – | Becomes data-testid on the root, with --plot and --table parts. |
| ref | Ref<HTMLDivElement> | undefined | – | The chart root element. |
| categoryDomain | readonly [number, number] | undefined | – | An increasing numeric x domain; inferred from all observations when unset. |
| describeObservation | ((row: T) => string) | undefined | – | Writes point identity, pairing tolerance and source details into the source table. |
Fuel consumption and RPM
Illustrative paired measurements
- Port engine
- Starboard engine
Preserve gaps and supply the source or validity information needed to interpret the observations.
Related
- Instrument history: stream selection, aggregation and asynchronous windows.
- Chart panel: descriptions, controls and source information.
- Chart container: shared registry and direct Recharts composition.