Skip to content
ShineUI
Install

Navigator

A draggable context window.

<sui-chart-navigator>ChartsStable

About

Context strip with a draggable window over a long series. The strip draws the series as a quiet area and the accent window marks the visible slice. Drag the window to pan, a handle to resize, or set zoom for the wheel and pinch. The element reports the index range and the chart in its slot re-slices to it.

Usage

<sui-chart-navigator heading="90-day cash trend" subtitle="Drag the orange window to pan · the detail chart re-slices" caption="Handles resize the window · the strip summarises the whole series"> <sui-chart-line plain kind="area" height="200" money></sui-chart-line> </sui-chart-navigator> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-chart-navigator"); el.data = [ 82000, 84295, 86564, 88778, 90912, 92941, 94843, 96596, 98181, 99582, 100786, 101783, 102565, 103129, 103476, 103607, 103530, 103254, 102793, 102162, 101379, /* ... 44 more */ ]; /* number[] */ el.window = [49, 85]; /* [number, number] | undefined */ el.addEventListener("suiWindowChange", (e) => console.log(e.detail)); </script>
import { SuiChartNavigator } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const data = [ 82000, 84295, 86564, 88778, 90912, 92941, 94843, 96596, 98181, 99582, 100786, 101783, 102565, 103129, 103476, 103607, 103530, 103254, 102793, 102162, 101379, /* ... 44 more */ ]; /* number[] */ const window = [49, 85]; /* [number, number] | undefined */ return ( <SuiChartNavigator heading="90-day cash trend" subtitle="Drag the orange window to pan · the detail chart re-slices" caption="Handles resize the window · the strip summarises the whole series" data={data} window={window} onSuiWindowChange={(e) => console.log(e.detail)}> <sui-chart-line plain kind="area" height="200" money></sui-chart-line> </SuiChartNavigator> ); }
<script setup> import { SuiChartNavigator } from "@shineui/vue"; import "@shineui/core/shineui.css"; const data = [ 82000, 84295, 86564, 88778, 90912, 92941, 94843, 96596, 98181, 99582, 100786, 101783, 102565, 103129, 103476, 103607, 103530, 103254, 102793, 102162, 101379, /* ... 44 more */ ]; // number[] const window = [49, 85]; // [number, number] | undefined const onChange = (e) => console.log(e.detail); </script> <template> <SuiChartNavigator heading="90-day cash trend" subtitle="Drag the orange window to pan · the detail chart re-slices" caption="Handles resize the window · the strip summarises the whole series" :data="data" :window="window" @sui-window-change="onChange"><sui-chart-line plain kind="area" height="200" money></sui-chart-line></SuiChartNavigator> </template>
import { Component } from "@angular/core"; import { SuiChartNavigator } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiChartNavigator], template: `<sui-chart-navigator heading="90-day cash trend" subtitle="Drag the orange window to pan · the detail chart re-slices" caption="Handles resize the window · the strip summarises the whole series" [(window)]="window" [data]="data"><sui-chart-line plain kind="area" height="200" money></sui-chart-line></sui-chart-navigator>`, }) export class Example { window?: [number, number] | undefined; data = [ 82000, 84295, 86564, 88778, 90912, 92941, 94843, 96596, 98181, 99582, 100786, 101783, 102565, 103129, 103476, 103607, 103530, 103254, 102793, 102162, 101379, /* ... 44 more */ ]; // number[] }

Basic

The window is a role="slider" with tabindex 0, the host's aria-label or else "Context window" as its name, and an aria-valuetext that reads the day range; a pointer press on it also gives it focus. The strip itself is aria-hidden because the detail chart above carries the figures.

<sui-chart-navigator heading="90-day cash trend" subtitle="Drag the orange window to pan · the detail chart re-slices" caption="Handles resize the window · the strip summarises the whole series"> <sui-chart-line plain kind="area" height="200" money></sui-chart-line> </sui-chart-navigator>
// setup(root): properties and events setup(root) { const nav = root.querySelector('sui-chart-navigator'); nav.window = [49, 85]; wire(nav, root.querySelector('sui-chart-line'), CASH, 'Cash'); }

Wheel and pinch zoom

zoom turns on the wheel and two-finger pinch over the detail chart as well as the strip. Over the strip it zooms about the pointer's place in the whole series, over the detail about its place in the window. A wheel that cannot move the window further goes to the page, so a scrolling page never traps the reader.

<sui-chart-navigator id="nv-zoom" zoom curve heading="90-day cash trend" subtitle="Roll the wheel over either chart to zoom, drag to pan" caption="The strip draws its summary as a smooth curve"> <sui-chart-line id="nv-zoom-line" plain kind="area" height="190" money></sui-chart-line> </sui-chart-navigator>
// setup(root): properties and events setup(root) { const nav = root.querySelector('#nv-zoom'); nav.window = [36, 71]; wire(nav, root.querySelector('#nv-zoom-line'), CASH, 'Cash'); }

Bare strip

Leave heading, subtitle and caption unset and the navigator renders the slot and the strip on their own, for a panel that already has its own header.

<sui-chart-navigator id="nv-bare"> <sui-chart-line id="nv-bare-line" plain kind="area" height="150" money></sui-chart-line> </sui-chart-navigator>
// setup(root): properties and events setup(root) { const nav = root.querySelector('#nv-bare'); nav.window = [62, 89]; wire(nav, root.querySelector('#nv-bare-line'), CASH, 'Cash'); }

Events

window holds the first and last point shown, [start, end]. suiWindowChange reports a new window, never every pixel of a drag; re-slice the detail chart's labels and series to it on every event, and read window once the element is ready for the first slice.

Idle.
<div style="display:flex;flex-direction:column;gap:12px"> <sui-chart-navigator id="nv-events" heading="90-day cash trend" subtitle="Drag the window, or focus it and press the arrow keys"> <sui-chart-line id="nv-events-line" plain kind="area" height="170" money></sui-chart-line> </sui-chart-navigator> <span id="nv-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const nav = root.querySelector('#nv-events'); const log = root.querySelector('#nv-log'); nav.window = [53, 89]; wire(nav, root.querySelector('#nv-events-line'), CASH, 'Cash'); nav.addEventListener('suiWindowChange', (e) => { log.textContent = `Window day ${e.detail[0] + 1} to ${e.detail[1] + 1} of ${CASH.length}.`; }); }

Empty and loading

loading stands a shimmer in for the strip and marks the host aria-busy while the series is on its way; the detail chart in the slot says its own. A strip with fewer than two points says so in place of the window, and emptyMessage is the sentence.

<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"> <sui-chart-navigator heading="90-day cash trend" subtitle="Fetching the series" loading></sui-chart-navigator> <sui-chart-navigator id="nv-empty" heading="90-day cash trend" subtitle="No series yet" empty-message="No balances have been recorded."></sui-chart-navigator> </div>
// setup(root): properties and events setup(root) { root.querySelector('#nv-empty').data = []; }

Keyboard

KeyAction
ArrowLeft ArrowDownPans the window one point earlier.
ArrowRight ArrowUpPans the window one point later.
Shift+ArrowLeft Shift+ArrowDownShrinks the window by one point at its right edge.
Shift+ArrowRight Shift+ArrowUpGrows the window by one point at its right edge.
PageDown PageUpPans the window by its own width, earlier or later.
HomeMoves the window to the start of the series.
EndMoves the window to the end of the series.

API

<sui-chart-navigator>

Properties

PropertyAttributeTypeDefaultDescription
captioncaptionstringstring | undefinedFootnote under the strip.
curvecurvebooleanfalseDraw the strip's summary as a smooth curve instead of straight segments. The curve never overshoots a point. It is the strip's own shape only; a curve detail chart sets its own. Reflected.
dataproperty onlynumber[][]The full series the strip summarizes. The strip keeps zero in view whatever the data does, so a series that runs below zero stays inside the box. The summary's area paints --sui-chart-navigator-area, the fourth ramp step by default, under a line in --sui-chart-navigator-line, the second ramp step by default. Set them on the element or any ancestor; a page can match them to the detail chart's series. Any CSS color or var(--token) works; use a token so the color follows the theme. Property, not attribute.
emptyMessageempty-messagestringstring | undefinedSentence drawn in the strip when data holds fewer than two points, in place of the summary and the window. Defaults to "Nothing to plot yet."; the detail chart in the slot says its own.
headingheadingstringstring | undefinedOptional card title; when set (or subtitle/caption), the navigator renders inside the chart card. It does not name the window: an aria-label or aria-labelledby on the host does, and without one the window reads "Context window".
loadingloadingbooleanfalseThe series is still on its way: a shimmer stands in for the strip, the slot and any heading stay, and the host is aria-busy. Reflected, so a page can style the wait.
subtitlesubtitlestringstring | undefinedOne-line context under the heading.
widthwidthnumber460ViewBox width of the strip in px; it scales to the container width.
windowproperty onlySuiChartWindow[number, number] | undefinedThe visible window, [start, end]: the first and the last point the detail chart shows, as inclusive indices into data (the reference defaultWindow, which took fractions). Unset, it opens on the last 45% of the series and the element writes the range it chose here, so read it for the first slice once the element is ready. The element writes it as the user drags, zooms or presses a key, and emits suiWindowChange; a range past the series is held to it. Property, not attribute.
zoomzoombooleanfalseZoom the window with the wheel and with a two-finger pinch, over the detail chart as well as the strip. Off by default, because a chart that swallows the wheel on a scrolling page is a trap: a wheel that cannot move the window any further is left to the page instead. Over the strip the gesture zooms about the pointer's position in the whole series, over the detail chart about its position in the current window, so the figure under the pointer stays put.

Events

EventDetailReactVueDescription
suiWindowChangeSuiChartWindow[number, number]onSuiWindowChange@sui-window-changeFires when the element writes a new window, the detail the new [start, end]: on a drag, a key, the wheel or a pinch, and when new data moves the range. Re-slice the detail chart's labels and series from start to end on every event. It does not fire for a drag that stays on the same points. Does not bubble.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves focus to the window, the strip's one tab stop, from which the arrow keys move it.

Slots

SlotDescription
(default)The detail chart, rendered above the strip: usually a plain sui-chart-line. This slot and suiWindowChange replace the reference render(startIdx, endIdx) prop; the caller listens for suiWindowChange and re-slices the chart's labels and series to the reported range.
header-endControls at the end of the head row, after the heading (only when a heading is set).

Types

SuiChartWindow typeThe navigator's window: the first and the last point it shows, as inclusive indices into data. The value window holds and suiWindowChange reports.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also