Chart range
Navigate a controlled numeric or time window with keyboard and touch controls.
On this page
Shared observation window
Illustrative data · relative sample coordinates
Code
import { useState, type ReactElement } from "react";
import { ChartRange, type ChartRangeValue } from "@bosunkit/components/chart-range";
import { LineChart } from "@bosunkit/components/line-chart";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Two illustrative channels share a controlled viewport and synchronized inspection; the caller owns range state.";
const labels: Readonly<Record<number, string>> = {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "10",
"11": "11",
"12": "12",
"13": "13",
"14": "14",
"15": "15",
"16": "16",
"17": "17",
"18": "18",
"19": "19",
"20": "20",
"21": "21",
"22": "22",
"23": "23",
"24": "24",
"25": "25",
"26": "26",
"27": "27",
"28": "28",
"29": "29",
"30": "30",
"31": "31",
"32": "32",
"33": "33",
"34": "34",
"35": "35",
"36": "36",
"37": "37",
"38": "38",
"39": "39",
"40": "40",
"41": "41",
"42": "42",
"43": "43",
"44": "44",
"45": "45",
"46": "46",
"47": "47",
"48": "48",
"49": "49",
"50": "50",
"51": "51",
"52": "52",
"53": "53",
"54": "54",
"55": "55",
"56": "56",
"57": "57",
"58": "58",
"59": "59",
"60": "60",
"61": "61",
"62": "62",
"63": "63",
"64": "64",
"65": "65",
"66": "66",
"67": "67",
"68": "68",
"69": "69",
"70": "70",
"71": "71",
"72": "72",
"73": "73",
"74": "74",
"75": "75",
"76": "76",
"77": "77",
"78": "78",
"79": "79",
"80": "80",
"81": "81",
"82": "82",
"83": "83",
"84": "84",
"85": "85",
"86": "86",
"87": "87",
"88": "88",
"89": "89",
"90": "90",
"91": "91",
"92": "92",
"93": "93",
"94": "94",
"95": "95",
"96": "96",
"97": "97",
"98": "98",
"99": "99",
"100": "100",
};
const format: (value: string | number) => string = (value: string | number): string => labels[Number(value)] ?? "";
export default function SharedWindow(): ReactElement {
const [range, setRange] = useState<ChartRangeValue>([0, 100]);
const [following, setFollowing] = useState<boolean>(false);
const data = Array.from({ length: 21 }, (_: unknown, index: number): { time: number; observed: number } => ({
time: index * 5,
observed: [8, 9, 10, 9, 8, 7, 6][index % 7] ?? 8,
}));
return (
<ChartPanel
title="Shared observation window"
description="Illustrative data · relative sample coordinates"
controls={
<ChartRange
value={range}
onValueChange={setRange}
bounds={[0, 100]}
minSpan={10}
label="Observation window"
labels={["Earlier", "Later", "Zoom in", "Zoom out", "Follow live"]}
following={following}
onFollowingChange={(next: boolean): void => {
setFollowing(next);
if (next) setRange([50, 100]);
}}
/>
}
>
{["Primary", "Comparison"].map((name: string): ReactElement => (
<LineChart
key={name}
data={data}
categoryKey="time"
categoryScale="linear"
categoryDomain={range}
onCategoryDomainChange={setRange}
syncId="range-example"
config={{ observed: { label: name, formatValue: format } }}
series={["observed"]}
formatCategory={format}
formatAxisValue={(value: number): string => format(value)}
label={`${name} observations`}
categoryLabel="Coordinate"
dataTableLabel={`View ${name.toLowerCase()} observations`}
missingValueText="Unavailable"
/>
))}
</ChartPanel>
);
}
The caller owns the visible range, available-history bounds and follow-live state. Earlier and later pan by half a window; zoom changes the span around its center and preserves span at history edges. Every action emits a finite increasing range. Pass that range as categoryDomain on a numeric LineChart or AreaChart. Provide onCategoryDomainChange to allow drag-to-zoom and Shift-drag pan on the numeric plot. The buttons provide equivalent range navigation by keyboard and touch. Shared syncId values synchronize inspection by category, and syncTolerance bounds nearest-time matching. Application wiring fetches and validates the requested window, cancels superseded requests and exposes loading or failure beside the chart. The controls have no clock or network dependency.
Examples
Composition
Shared observation window
Illustrative data · relative sample coordinates
Code
import { useState, type ReactElement } from "react";
import { ChartRange, type ChartRangeValue } from "@bosunkit/components/chart-range";
import { LineChart } from "@bosunkit/components/line-chart";
import { ChartPanel } from "@bosunkit/components/chart-panel";
// Fixed illustrative display strings; consumers supply their quantity formatter.
export const description: string =
"Two illustrative channels share a controlled viewport and synchronized inspection; the caller owns range state.";
const labels: Readonly<Record<number, string>> = {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "10",
"11": "11",
"12": "12",
"13": "13",
"14": "14",
"15": "15",
"16": "16",
"17": "17",
"18": "18",
"19": "19",
"20": "20",
"21": "21",
"22": "22",
"23": "23",
"24": "24",
"25": "25",
"26": "26",
"27": "27",
"28": "28",
"29": "29",
"30": "30",
"31": "31",
"32": "32",
"33": "33",
"34": "34",
"35": "35",
"36": "36",
"37": "37",
"38": "38",
"39": "39",
"40": "40",
"41": "41",
"42": "42",
"43": "43",
"44": "44",
"45": "45",
"46": "46",
"47": "47",
"48": "48",
"49": "49",
"50": "50",
"51": "51",
"52": "52",
"53": "53",
"54": "54",
"55": "55",
"56": "56",
"57": "57",
"58": "58",
"59": "59",
"60": "60",
"61": "61",
"62": "62",
"63": "63",
"64": "64",
"65": "65",
"66": "66",
"67": "67",
"68": "68",
"69": "69",
"70": "70",
"71": "71",
"72": "72",
"73": "73",
"74": "74",
"75": "75",
"76": "76",
"77": "77",
"78": "78",
"79": "79",
"80": "80",
"81": "81",
"82": "82",
"83": "83",
"84": "84",
"85": "85",
"86": "86",
"87": "87",
"88": "88",
"89": "89",
"90": "90",
"91": "91",
"92": "92",
"93": "93",
"94": "94",
"95": "95",
"96": "96",
"97": "97",
"98": "98",
"99": "99",
"100": "100",
};
const format: (value: string | number) => string = (value: string | number): string => labels[Number(value)] ?? "";
export default function SharedWindow(): ReactElement {
const [range, setRange] = useState<ChartRangeValue>([0, 100]);
const [following, setFollowing] = useState<boolean>(false);
const data = Array.from({ length: 21 }, (_: unknown, index: number): { time: number; observed: number } => ({
time: index * 5,
observed: [8, 9, 10, 9, 8, 7, 6][index % 7] ?? 8,
}));
return (
<ChartPanel
title="Shared observation window"
description="Illustrative data · relative sample coordinates"
controls={
<ChartRange
value={range}
onValueChange={setRange}
bounds={[0, 100]}
minSpan={10}
label="Observation window"
labels={["Earlier", "Later", "Zoom in", "Zoom out", "Follow live"]}
following={following}
onFollowingChange={(next: boolean): void => {
setFollowing(next);
if (next) setRange([50, 100]);
}}
/>
}
>
{["Primary", "Comparison"].map((name: string): ReactElement => (
<LineChart
key={name}
data={data}
categoryKey="time"
categoryScale="linear"
categoryDomain={range}
onCategoryDomainChange={setRange}
syncId="range-example"
config={{ observed: { label: name, formatValue: format } }}
series={["observed"]}
formatCategory={format}
formatAxisValue={(value: number): string => format(value)}
label={`${name} observations`}
categoryLabel="Coordinate"
dataTableLabel={`View ${name.toLowerCase()} observations`}
missingValueText="Unavailable"
/>
))}
</ChartPanel>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| valuerequired | ChartRangeValue | – | Controlled visible range; time ranges use epoch milliseconds. |
| onValueChangerequired | (value: ChartRangeValue) => void | – | Receives the requested range; callers load the data asynchronously. |
| bounds | ChartRangeValue | undefined | – | Available history bounds; navigation preserves the span at these edges. |
| minSpan | number | undefined | 1 | Smallest permitted span in axis units; one by default. |
| labelrequired | string | – | Names the group of range controls. |
| labelsrequired | readonly [string, string, string, string, string] | – | Display labels for earlier, later, zoom in, zoom out and follow live. |
| following | boolean | undefined | false | Controlled follow-live state; a range interaction requests false. |
| onFollowingChange | ((following: boolean) => void) | undefined | – | Receives follow-live requests; the caller supplies the clock and newest range. |
| disabled | boolean | undefined | false | Disables navigation while the caller cannot accept a range request. |
| testId | string | undefined | – | Becomes data-testid on the group. |
| ref | Ref<HTMLDivElement> | undefined | – | The control group element. |
Shared observation window
Illustrative data · relative sample coordinates
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.