KQ-728 · COMPARE/LINES
Compare Lines
Two assets on one plate, each series normalised to its own first point so the lines share a scale of percent-since-start and a dashed baseline at zero. On mount both traces draw in together — pathLength from 0 to 1 on one durations.page tween with the enter ease — so they finish on the same frame and the eye reads the divergence as it happens. The legend is two toggles: hiding a series fades its line and area on the exit ease with no redraw, and the vertical domain is fixed across both series so a toggle never re-scales the plate. Pointing at the plate sets a cursor at the nearest sample — a hairline, a dot per visible series, a bar between them and a readout chip that prints both values and the gap in percentage points — and the chip moves on snap while keeping inside the plate; the plate is a focusable group where Left and Right step the cursor, Home and End jump to the ends and Escape clears it.
Basin BSN up 8.41 percent and Fernwork FRN up 3.11 percent over 24 sessions.
BSN +8.41% · FRN +3.11% · hover for the gap
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/compare-lines
Props
- series
- [CompareSeries, CompareSeries]
- default —
- { id, label, points: number[] }, closes oldest first; both series share the same sample count.
- labels
- string[]
- default —
- Already-formatted x labels, one per sample, for the readout.
- hidden / defaultHidden
- string[]
- default []
- Controlled or initial set of hidden series ids.
- onHiddenChange
- (hidden: string[]) => void
- default —
- Fires from the toggle that changed it.
- onCursorChange
- (index: number | null) => void
- default —
- Fires from the pointer or key that moved the cursor.
- format
- (value: number) => string
- default Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
- Prints a raw close in the readout. The default pins an explicit locale so server and client agree.
- label
- React.ReactNode
- default —
- Visible heading. Omit it and pass aria-label.
- height
- number
- default 140
- Plate height in px; the width is fluid and measured.
- aria-label
- string
- default —
- Invisible label when label is omitted.
| Prop | Type | Default | Description |
|---|---|---|---|
| series | [CompareSeries, CompareSeries] | — | { id, label, points: number[] }, closes oldest first; both series share the same sample count. |
| labels | string[] | — | Already-formatted x labels, one per sample, for the readout. |
| hidden / defaultHidden | string[] | [] | Controlled or initial set of hidden series ids. |
| onHiddenChange | (hidden: string[]) => void | — | Fires from the toggle that changed it. |
| onCursorChange | (index: number | null) => void | — | Fires from the pointer or key that moved the cursor. |
| format | (value: number) => string | Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }) | Prints a raw close in the readout. The default pins an explicit locale so server and client agree. |
| label | React.ReactNode | — | Visible heading. Omit it and pass aria-label. |
| height | number | 140 | Plate height in px; the width is fluid and measured. |
| aria-label | string | — | Invisible label when label is omitted. |