Loading page…
Loading page…
Three deterministic, pure-SVG charts — no chart library, no canvas. Color comes only from currentColor and the --ch-viz-* scale, and every chart shares the loading / empty / ready state machine. Hover cursors use a readable value plate, and allocation slices can be hovered, focused, or selected while the brand mark stays anchored in the donut center.
The inline trend line for asset rows — 24 hourly points, gradient fill under the line, sized to sit beside an AmountDisplay.
<Sparkline data={[]} state="loading" />--ch-bg-raisedSkeleton at exact chart size — no layout shift when data lands.
<Sparkline data={[]} state="empty" />--ch-border-faint--ch-fg-faintNo history yet — dashed bounds with the mono NO DATA mark.
<Sparkline data={btc.sparkline} showCurrent label="BTC 24-hour trend" />--ch-viz-1BTC, last 24 hours, on viz-1 with the gradient fill and the current-point dot.
<Sparkline data={dbl.sparkline} fill={false} className="text-viz-4" />--ch-viz-4DBL on viz-4, fill off — for dense tables where stacked gradients muddy.
The full line + area chart with y-grid and x-ticks — here the BTC → XMR rate over 24 hours at 30-minute resolution. Charts fill their container and re-plot on resize; the live crosshair carries a 13px high-contrast tooltip plate, while the crosshair prop pins a static marker for touch-independent forced states.
<PriceChart data={[]} state="loading" />--ch-bg-raisedFull-size skeleton block.
<PriceChart data={[]} state="empty" />--ch-border-faint--ch-fg-faintPair has no rate history on this node yet.
<PriceChart data={rates} xLabels={["14:30","20:30","02:30","08:00","14:00"]} formatY={(v) => v.toFixed(1)} />--ch-viz-1--ch-viz-grid--ch-fg-faint48 points, four y-grid lines, five time ticks.
<PriceChart data={rates} xLabels={labels} crosshair={36} />--ch-viz-1--ch-border-strong--ch-bgMarker pinned at point 36 — dashed rule, ring dot, and the value read out above the plot.
Move the pointer across any chart below — the vertical rule follows the pointer while the dot and legible tooltip snap to the nearest sample, and every other chart in the group follows. Charts publish a normalized position rather than an index, so series of different lengths line up by position. Pointer paint stays outside React state and is coalesced into one animation frame.
import { CursorSyncProvider, PriceChart } from "@coldharbor/webui";
<CursorSyncProvider>
<PriceChart data={rates} xLabels={labels} />
<PriceChart data={btcSeries} />
</CursorSyncProvider>--ch-border-strong--ch-bg--ch-motion-duration-fastDifferent lengths, one cursor. Wrap a dashboard in CursorSyncProvider to scope the group.
<Sparkline data={series} interactive />--ch-viz-1--ch-border-strongSparklines opt in with interactive — off by default, since a crosshair per row in a dense table is noise.
<PriceChart data={rates} interactive={false} />
<PriceChart data={rates} sync={false} />sync={false} keeps a chart's cursor to itself; interactive={false} removes it entirely.
Portfolio share as selectable ring segments cycling viz-1 through viz-5 with hairline gaps. The Privateer mark always owns the center; hover or keyboard-focus a slice for its value, and press it to keep the tooltip selected.
<AllocationDonut data={[]} state="loading" />--ch-bg-raised--ch-radius-pillCircle skeleton at ring size.
<AllocationDonut data={[]} state="empty" />--ch-border-faint--ch-fg-faintFresh install — nothing funded yet.
<AllocationDonut data={allocation} label="Portfolio allocation" />--ch-viz-1--ch-viz-2--ch-viz-3--ch-viz-4--ch-viz-5--ch-viz-gridSix focusable assets by fiat value; the tooltip persists when a slice is selected.
<AllocationDonut data={[{ label: "BTC", value: 164741 }]} />--ch-viz-1--ch-viz-gridOne holding — a full ring with no segment gap.
The allocation ring under dark, light, and Harbor Mode — the viz scale is tuned per layer, so series stay distinguishable on every background.
<AllocationDonut data={allocation} size={140} />--ch-viz-1--ch-viz-2--ch-viz-3--ch-viz-4--ch-viz-5