@lilt-ui/charts 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +75 -0
  3. package/dist/analysis-BJRmXHOB.js +62 -0
  4. package/dist/cards/box-plot-card.d.ts +30 -0
  5. package/dist/cards/cartesian-card.d.ts +54 -3
  6. package/dist/cards/range-chart-card.d.ts +28 -0
  7. package/dist/cartesian-card-BDZs0v6E.js +7235 -0
  8. package/dist/chart-context.d.ts +18 -0
  9. package/dist/chart-data-CCwLQeXa.js +44 -0
  10. package/dist/chart.d.ts +3 -1
  11. package/dist/data.js +3 -2
  12. package/dist/engine/box.d.ts +18 -0
  13. package/dist/engine/domains.d.ts +2 -0
  14. package/dist/engine/geometry.d.ts +2 -1
  15. package/dist/engine/normalize.d.ts +2 -4
  16. package/dist/engine/ranges.d.ts +59 -0
  17. package/dist/finance/candle-geometry.d.ts +55 -0
  18. package/dist/finance/candles.d.ts +18 -0
  19. package/dist/finance/cards/candlestick-chart-card.d.ts +41 -0
  20. package/dist/finance/cards/depth-chart-card.d.ts +32 -0
  21. package/dist/finance/cards/indicator-chart-card.d.ts +52 -0
  22. package/dist/finance/cards/pane.d.ts +24 -0
  23. package/dist/finance/cards/portfolio-chart-card.d.ts +22 -0
  24. package/dist/finance/cards/price-chart-card.d.ts +27 -0
  25. package/dist/finance/index.d.ts +17 -0
  26. package/dist/finance/indicators.d.ts +65 -0
  27. package/dist/finance/order-book.d.ts +26 -0
  28. package/dist/finance.d.ts +2 -0
  29. package/dist/finance.js +1157 -0
  30. package/dist/index.d.ts +10 -1
  31. package/dist/index.js +2235 -8793
  32. package/dist/interaction/field-values.d.ts +12 -0
  33. package/dist/interaction/inspection-layer.d.ts +4 -1
  34. package/dist/marks/contract.d.ts +49 -0
  35. package/dist/marks/use-range-mark.d.ts +19 -0
  36. package/dist/primitives/annotations.d.ts +1 -1
  37. package/dist/primitives/box-plot.d.ts +4 -0
  38. package/dist/primitives/error-bar.d.ts +4 -0
  39. package/dist/primitives/interval-band.d.ts +2 -2
  40. package/dist/primitives/range-bar.d.ts +4 -0
  41. package/dist/styles.css +1 -1
  42. package/dist/types.d.ts +80 -13
  43. package/package.json +11 -2
  44. package/dist/analysis-BLLWEKBw.js +0 -105
package/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Finance charts, range charts, and series that carry more than one value.
6
+
7
+ ### Breaking
8
+
9
+ - A series' `interval` is replaced by `fields`: named companion values in the series' unit, such as a forecast's low and high, quartiles, or a candle's open, high, and low. Fields fit the y axis, appear in hover, and range marks read them by ID.
10
+ - `IntervalBand` now names the fields it draws: `<IntervalBand series="revenue" lower="low" upper="high" />`.
11
+ - Tooltip content (`ChartTooltipContext.series[]`) has `fields` instead of `interval`.
12
+
13
+ Cards are unchanged: `forecast={{ from, lower, upper }}` works as before. See "Upgrading from 0.1" in the README.
14
+
15
+ ### Finance, from `@lilt-ui/charts/finance`
16
+
17
+ - `CandlestickChartCard`: candles, hollow candles, OHLC bars, or a price area colored by direction, with an optional linked volume pane, a log scale, and live mode. Hover reads the candle's open, high, and low beside its date.
18
+ - `IndicatorChartCard`: simple or exponential averages and Bollinger bands over the price, with RSI, MACD, or volume panes underneath that move together on hover. Each pane reads its own value.
19
+ - `PriceChartCard`: a ticker with its latest price, change, and change in money; `versus` rebases other instruments to percent change on one axis.
20
+ - `DepthChartCard`: how much could trade at each price on the buy and sell side, with the gap between the best prices and the total resting on each side.
21
+ - `OrderBook`: resting orders as an accessible table, each level with a bar sized by the total behind it; changed levels highlight briefly.
22
+ - `PortfolioChartCard`: what an account is worth next to what went into it, with how far it sits below its best value in a linked pane.
23
+ - `Candles`, a primitive that draws any number of candles into a handful of paths.
24
+ - Helpers: `sma`, `ema`, `rsi` (Wilder), `macd`, `bollinger`, `drawdown`, `rebase`, and `depthLevels`.
25
+ - `--lilt-candle-up` and `--lilt-candle-down` tokens, following `--lilt-positive` and `--lilt-negative`.
26
+
27
+ ### Charts
28
+
29
+ - `RangeChartCard`: floating bars from low to high, or error bars around a value.
30
+ - `BoxPlotCard`: quartiles, a median, Tukey whiskers, and outliers computed from raw samples. `summarizeBox` returns the same statistics.
31
+ - Primitives: `RangeBar`, `ErrorBar`, and `BoxPlot`.
32
+ - `series.colorAt(row)` colors one bar, candle, range, or point, such as a falling day in red.
33
+ - `y.scale: 'log'` spaces values by ratio, with ticks on 1, 2, and 5.
34
+ - `ReferenceBand axis="y"` shades a range of values, such as RSI 30–70.
35
+ - `ChartPlot slots` gives every observation a bar-width slot, so a line pane lines up with bars or candles beside it.
36
+
37
+ ### Fixes
38
+
39
+ - A card `valueFormat` with more than one minimum fraction digit, such as two-decimal prices, no longer throws.
40
+
41
+ ## 0.1.0
42
+
43
+ The first release: Area, Line, Bar, Combo, Horizontal bar, Stat, Radial, Progress, Activity ring, Funnel, Heatmap, Scatter, Sankey, Radar, and Slope cards, with linked hover, targets, forecasts, and range comparison.
package/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  <p align="center">
15
15
  <a href="https://liltui.vercel.app"><strong>Documentation</strong></a> ·
16
16
  <a href="https://liltui.vercel.app/charts/area">Charts</a> ·
17
+ <a href="https://liltui.vercel.app/finance/candlestick">Finance</a> ·
17
18
  <a href="https://liltui.vercel.app/llms.txt">llms.txt</a>
18
19
  </p>
19
20
 
@@ -82,6 +83,64 @@ That's the whole card: the headline, the change chip, value tiles, and hover all
82
83
  | **Rankings** | Horizontal bar, with shares and an "Other" row |
83
84
  | **Flows and change** | Funnel, Sankey, and Slope |
84
85
  | **Patterns** | Heatmap, Scatter, and Radar |
86
+ | **Spread** | Range (floating or error bars) and Box plot, from raw samples |
87
+ | **Markets** | Candlestick, Indicators, Price, Depth, Order book, and Portfolio |
88
+
89
+ ## Finance charts
90
+
91
+ Candlesticks, indicators, and order books ship from their own entry point, so apps that never draw a candle never load one:
92
+
93
+ ```tsx
94
+ 'use client';
95
+
96
+ import { CandlestickChartCard } from '@lilt-ui/charts/finance';
97
+
98
+ const candles = [
99
+ {
100
+ date: new Date('2026-08-20'),
101
+ open: 243.1,
102
+ high: 246.4,
103
+ low: 241.8,
104
+ close: 245.2,
105
+ volume: 2410000,
106
+ },
107
+ {
108
+ date: new Date('2026-08-21'),
109
+ open: 245.2,
110
+ high: 247.0,
111
+ low: 243.9,
112
+ close: 244.6,
113
+ volume: 1980000,
114
+ },
115
+ ];
116
+
117
+ export function MarketCard() {
118
+ return (
119
+ <CandlestickChartCard
120
+ title="SOL · USDC"
121
+ data={candles}
122
+ x="date"
123
+ open="open"
124
+ high="high"
125
+ low="low"
126
+ close="close"
127
+ volume="volume"
128
+ valueFormat={{ style: 'currency', currency: 'USD' }}
129
+ />
130
+ );
131
+ }
132
+ ```
133
+
134
+ | Card | What it shows |
135
+ | ---------------------- | ------------------------------------------------------------------------------ |
136
+ | `CandlestickChartCard` | Candles, hollow candles, OHLC bars, or a price area, with a linked volume pane |
137
+ | `IndicatorChartCard` | Moving averages or Bollinger bands over the price; RSI, MACD, or volume panes |
138
+ | `PriceChartCard` | A ticker with its latest price and change, or several instruments rebased |
139
+ | `DepthChartCard` | How much could trade at each price, buying and selling, and the gap between |
140
+ | `OrderBook` | Resting orders level by level, the best prices meeting in the middle |
141
+ | `PortfolioChartCard` | What an account is worth next to what went in, and its fall from its best |
142
+
143
+ Candles draw into a handful of paths however many there are, so years of daily history stay fast. `scale="log"` spaces long histories by ratio, and `live` follows new rows as they arrive. The indicator math (`sma`, `ema`, `rsi`, `macd`, `bollinger`, `drawdown`, `depthLevels`) is exported too, for your own layouts.
85
144
 
86
145
  ## Built in, one prop away
87
146
 
@@ -102,6 +161,22 @@ Every Lilt style lives in the `lilt` cascade layer, so your CSS wins without spe
102
161
  @import 'tailwindcss';
103
162
  ```
104
163
 
164
+ ## Upgrading from 0.1
165
+
166
+ 0.2 replaces a series' `interval` with named `fields`, which carry any companion values: a forecast's low and high, quartiles, or a candle's open, high, and low. `IntervalBand` now names the fields it reads:
167
+
168
+ ```tsx
169
+ // 0.1
170
+ { id: 'revenue', accessor: (row) => row.revenue, interval: { lower: (row) => row.low, upper: (row) => row.high, meaning: 'Forecast' } }
171
+ <IntervalBand series="revenue" />
172
+
173
+ // 0.2
174
+ { id: 'revenue', accessor: (row) => row.revenue, fields: { low: { label: 'Low', accessor: (row) => row.low }, high: { label: 'High', accessor: (row) => row.high } } }
175
+ <IntervalBand series="revenue" lower="low" upper="high" />
176
+ ```
177
+
178
+ Cards are unchanged: `forecast={{ from, lower, upper }}` works as before. Tooltip content reads `series[].fields` instead of `series[].interval`.
179
+
105
180
  ## License
106
181
 
107
182
  MIT © Lilt UI
@@ -0,0 +1,62 @@
1
+ //#region src/engine/ranking.ts
2
+ function e(e, t, n) {
3
+ let r = /* @__PURE__ */ new Set();
4
+ return e.map((e, i) => {
5
+ let a = t.id(e), o = t.label(e), s = n(e);
6
+ if (typeof a != "string" || !a.trim()) throw Error(`Invalid category at row ${i + 1}: a non-empty string ID is required.`);
7
+ if (r.has(a)) throw Error(`Duplicate category ID "${a}".`);
8
+ if (typeof o != "string" || !o.trim()) throw Error(`Invalid label for category "${a}".`);
9
+ if (s !== null && (typeof s != "number" || !Number.isFinite(s))) throw Error(`Invalid value for category "${a}": expected a finite number or null.`);
10
+ return r.add(a), {
11
+ id: a,
12
+ label: o,
13
+ datum: e,
14
+ value: s
15
+ };
16
+ });
17
+ }
18
+ function t(e, t) {
19
+ return t === "input" ? [...e] : [...e].sort((e, n) => {
20
+ if (e.value === null && n.value !== null) return 1;
21
+ if (n.value === null && e.value !== null) return -1;
22
+ let r = (e.value ?? 0) - (n.value ?? 0);
23
+ return r ? r * (t === "descending" ? -1 : 1) : e.id < n.id ? -1 : +(e.id > n.id);
24
+ });
25
+ }
26
+ function n(e) {
27
+ let t = 0, n = 0;
28
+ for (let r of e) r.value !== null && (t = Math.min(t, r.value), n = Math.max(n, r.value));
29
+ return t === n ? [0, 1] : [t, n];
30
+ }
31
+ function r(e, t) {
32
+ let n = (e) => (e - t[0]) / (t[1] - t[0]) * 100, r = n(0), i = e === null ? r : n(e);
33
+ return {
34
+ zero: r,
35
+ left: Math.min(r, i),
36
+ width: Math.abs(i - r)
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/engine/analysis.ts
41
+ function i(e, t) {
42
+ if (e !== null && (!Number.isFinite(e) || e < 0)) throw Error(`${t} must be a non-negative finite number or null.`);
43
+ }
44
+ function a(t) {
45
+ let n = e(t.data, t.category, t.value), r = Infinity;
46
+ for (let e of n) if (i(e.value, `Stage ${e.id}`), e.value !== null) {
47
+ if (e.value > r) throw Error(`Stage ${e.id} exceeds a preceding stage; cumulative funnel counts must not increase.`);
48
+ r = e.value;
49
+ }
50
+ let a = n[0]?.value;
51
+ return n.map((e, t) => {
52
+ let r = n[t - 1]?.value;
53
+ return {
54
+ ...e,
55
+ share: e.value !== null && a != null && a > 0 ? e.value / a : null,
56
+ conversion: e.value !== null && r != null && r > 0 ? e.value / r : null,
57
+ drop: e.value !== null && r != null ? r - e.value : null
58
+ };
59
+ });
60
+ }
61
+ //#endregion
62
+ export { n as a, r as i, e as n, t as r, a as t };
@@ -0,0 +1,30 @@
1
+ import { ReactElement } from 'react';
2
+ import { BoxSummary } from '../engine/box';
3
+ import { CartesianCardProps } from './cartesian-card';
4
+ import { KeysOfType } from './keys';
5
+ /** One box, with its x value and the row it came from. */
6
+ interface BoxRow<Row> {
7
+ x: string | number | Date;
8
+ median: number | null;
9
+ box: BoxSummary | null;
10
+ source: Row;
11
+ }
12
+ type SharedProps<Row> = Omit<CartesianCardProps<BoxRow<Row>, 'median'>, 'series' | 'data' | 'ranges' | 'x' | 'headlineSeries' | 'pillSeries' | 'target' | 'forecast' | 'bleed' | 'tiles' | 'aggregate'>;
13
+ export interface BoxPlotCardProps<Row> extends SharedProps<Row> {
14
+ data?: readonly Row[];
15
+ /** One box per row, placed by this field: labels, numbers, or dates. */
16
+ x: KeysOfType<Row, string | number | Date>;
17
+ /** Field holding each row's raw samples; Lilt computes the quartiles, whiskers and outliers. */
18
+ samples: KeysOfType<Row, readonly number[]>;
19
+ /** Names the series in hover. Defaults to "Median". */
20
+ label?: string;
21
+ color?: string;
22
+ /** Draw samples beyond the whiskers as dots. Defaults to true. */
23
+ outliers?: boolean;
24
+ }
25
+ /**
26
+ * Distributions side by side: quartile boxes, a median line, Tukey whiskers and outliers,
27
+ * computed from each row's raw samples. The headline is the median of every sample.
28
+ */
29
+ export declare function BoxPlotCard<Row>({ data, x, samples, label, color, outliers, headline, ...props }: BoxPlotCardProps<Row>): ReactElement;
30
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { CSSProperties, ReactElement, ReactNode } from 'react';
2
- import { ChartAxisStyle, ChartBarAppearance, ChartBackground, ChartCurve, ChartMotion, ChartPalette, ChartHoverReadout, ChartHoverStyle, ChartTooltipIndicator, ChartStyle, ChartSurface } from '../types';
2
+ import { ChartController, ChartMargins, ChartTooltipContext, NumericXConfig, TimeXConfig, ChartAxisStyle, ChartBarAppearance, ChartBackground, ChartCurve, ChartMotion, ChartPalette, ChartHoverReadout, ChartHoverStyle, ChartSeries, ChartTooltipIndicator, ChartStyle, ChartSurface } from '../types';
3
3
  import { NumericKey, ResolvedX, XKey, XKind } from './keys';
4
4
  import { CardAggregate } from './format';
5
5
  import { AnimatedNumberVariant } from '../motion/animated-number';
@@ -191,16 +191,67 @@ export type CardShape = {
191
191
  /** Curve and point markers of lines drawn over the bars. */
192
192
  curve?: ChartCurve;
193
193
  points?: boolean;
194
- };
194
+ } | CardMarks;
195
+ /**
196
+ * Column marks drawn from each series' fields, such as candles or range bars. Cards built on
197
+ * this shape keep every Cartesian card behaviour: headline, ranges, hover sync and tiles.
198
+ */
199
+ export interface CardMarks {
200
+ kind: 'marks';
201
+ /** A stable description of everything below, so memoized series only rebuild when it changes. */
202
+ id: string;
203
+ /** Descriptor options for a series by key, such as its fields, colors, curve, or area. */
204
+ series?: (key: string) => Partial<ChartSeries<never>> | undefined;
205
+ /**
206
+ * The marks for the drawn series, as elements or fragments of them (not wrapped in a
207
+ * component), so the plot can find column marks. Dashed references still draw as lines.
208
+ */
209
+ draw: (ids: readonly string[]) => ReactNode;
210
+ includeZero: boolean;
211
+ scale?: 'linear' | 'log';
212
+ curve?: ChartCurve;
213
+ /** Values the y axis must reach, such as outliers beyond the fields. */
214
+ include?: readonly number[];
215
+ /** Slot every observation like a bar, so linked bar panes line up. Column marks imply it. */
216
+ slots?: boolean;
217
+ /** Label the x axis here. Turn off when a pane below carries it. Defaults to true. */
218
+ xAxis?: boolean;
219
+ /** Extra text after the hovered x in the headline caption, e.g. open, high and low. */
220
+ describe?: (inspection: ChartTooltipContext) => string | null;
221
+ }
222
+ /** Panes drawn under the main plot, linked to it: same data, x, margins and crosshair. */
223
+ export interface CardPaneContext<Row> {
224
+ data: readonly Row[];
225
+ x: TimeXConfig<Row> | NumericXConfig<Row>;
226
+ controller: ChartController;
227
+ margins: ChartMargins;
228
+ /** The main plot's `axisInset` and `slots`, so a pane's observations sit under its own. */
229
+ axisInset: number;
230
+ slots: boolean;
231
+ axis: ChartAxisStyle;
232
+ loading: boolean;
233
+ motion?: ChartMotion;
234
+ hoverStyle: ChartHoverStyle;
235
+ formatValue: (value: number) => string;
236
+ formatAxis: (value: number) => string;
237
+ }
195
238
  /** Matches the default `--lilt-card-padding`. */
196
239
  export declare const CARD_PADDING = 20;
197
240
  /**
198
241
  * The shared card body: headline, delta, period, plot, and value tiles, with hover sync and
199
242
  * range comparison. Area, Line, and Bar cards differ only in their `shape`.
200
243
  */
201
- export declare function CartesianCard<Row, Key extends NumericKey<Row>>({ shape, title, data: suppliedData, x, series: seriesInput, headline: suppliedHeadline, aggregate: suppliedAggregate, headlineSeries, delta: suppliedDelta, deltaTone, range, ranges, defaultRange, tiles, height, valueFormat, locale, formatValue: suppliedFormatValue, formatAxisValue: suppliedFormatAxisValue, formatX, xType, compare, target: suppliedTarget, forecast, sync, axis, hover, hoverStyle, background, spotlight, surface, badge, numberStyle, palette, bleed, axisInset, pillSeries, pillValue, pillPosition, tooltipIndicator, loading, motion, className, style, }: Omit<CartesianCardProps<Row, Key>, 'series'> & {
244
+ export declare function CartesianCard<Row, Key extends NumericKey<Row>>({ shape, title, data: suppliedData, x, series: seriesInput, headline: suppliedHeadline, aggregate: suppliedAggregate, headlineSeries, delta: suppliedDelta, deltaTone, range, ranges, defaultRange, tiles, height, valueFormat, locale, formatValue: suppliedFormatValue, formatAxisValue: suppliedFormatAxisValue, formatX, xType, compare, target: suppliedTarget, forecast, sync, axis, hover, hoverStyle, background, spotlight, surface, badge, numberStyle, palette, bleed, axisInset, pillSeries, pillValue, pillPosition, tooltipIndicator, loading, motion, className, style, panes, live, heading, restCaption, }: Omit<CartesianCardProps<Row, Key>, 'series'> & {
202
245
  series: readonly CoreSeries<Key>[];
203
246
  shape: CardShape;
247
+ /** Linked panes under the plot, such as volume or indicators. */
248
+ panes?: (context: CardPaneContext<Row>) => ReactNode;
249
+ /** Follow the newest observation as rows arrive. */
250
+ live?: boolean;
251
+ /** Richer title content, such as a ticker and its name; `title` still labels the chart. */
252
+ heading?: ReactNode;
253
+ /** Text beside the delta at rest, such as an absolute change, from the active data. */
254
+ restCaption?: (data: readonly Row[]) => ReactNode;
204
255
  }): ReactElement;
205
256
  /** The plot position where projections begin: a label's row, or the date or number itself. */
206
257
  export declare function forecastPosition<Row>(data: readonly Row[], x: string, resolved: ResolvedX<Row>, from: string | number): number | null;
@@ -0,0 +1,28 @@
1
+ import { ReactElement } from 'react';
2
+ import { CartesianCardProps } from './cartesian-card';
3
+ import { NumericKey } from './keys';
4
+ type SharedProps<Row, Key extends NumericKey<Row>> = Omit<CartesianCardProps<Row, Key>, 'series' | 'headlineSeries' | 'pillSeries' | 'target' | 'forecast' | 'bleed' | 'tiles'>;
5
+ export interface RangeChartCardProps<Row, Key extends NumericKey<Row>> extends SharedProps<Row, Key> {
6
+ /** Field holding the low end of each range. */
7
+ low: Key;
8
+ /** Field holding the high end of each range. */
9
+ high: Key;
10
+ /**
11
+ * Field holding each measured value, such as a mean inside its uncertainty. It drives the
12
+ * headline and pills. Defaults to `high`.
13
+ */
14
+ value?: Key;
15
+ /** Names the series in hover and tiles. Defaults to the value key. */
16
+ label?: string;
17
+ color?: string;
18
+ /** `bar` (default) floats a bar from low to high; `error` draws whiskers around the value. */
19
+ display?: 'bar' | 'error';
20
+ /** Per-series stat tile under the plot. Defaults to false. */
21
+ tiles?: boolean;
22
+ }
23
+ /**
24
+ * Ranges per observation: floating bars from low to high, or error bars around a value. The
25
+ * headline summarizes the values (the mean by default) and hover reads the range's ends.
26
+ */
27
+ export declare function RangeChartCard<Row, const Key extends NumericKey<Row>>({ low, high, value, label, color, display, aggregate, tiles, ...props }: RangeChartCardProps<Row, Key>): ReactElement;
28
+ export {};