The Sparkline widget renders a mini inline chart using block characters, perfect for showing trends in dashboards and tables.
Basic Usage
import { ui } from "@rezi-ui/core";
ui.sparkline([1, 3, 5, 7, 5, 3, 1, 4, 8, 2]);
Props
Data points to visualize. Values are normalized to 0-1 range for display.
Display width in cells. Defaults to data.length.
Minimum value for scaling. Auto-detected if omitted.
Maximum value for scaling. Auto-detected if omitted.
Enable sub-cell rendering for smoother lines.
Rendering mode for highRes mode. One of "braille", "sextant", "quadrant", "halfblock".
Display Modes
Standard Mode (Default)
Uses block characters ▁▂▃▄▅▆▇█ for 8-level bars:
ui.sparkline([1, 2, 4, 7, 9, 5, 3]);
// Output: ▁▂▄▆█▅▃
High-Resolution Mode
Uses sub-cell blitters for smooth lines:
ui.sparkline([1, 3, 5, 7, 5, 3, 1], {
highRes: true,
blitter: "braille",
});
Best for:
"braille" - Smooth curves
"halfblock" - Good for sparklines
"sextant" - Balance of resolution/visibility
Scaling
Auto-Scale (Default)
Values auto-scale to fit full height:
ui.sparkline([10, 20, 30, 40]);
// 10 = bottom, 40 = top
Fixed Range
Manually set min/max for consistent scaling:
ui.sparkline([10, 20, 30, 40], {
min: 0,
max: 100,
});
// All values scale to 0-100 range
Fixed Width
ui.sparkline([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], {
width: 5, // Compress to 5 cells
});
Examples
Inline Metrics
function metricRow(label: string, value: number, trend: number[]): VNode {
return ui.row({ gap: 2 }, [
ui.text(label, { style: { bold: true } }),
ui.text(String(value)),
ui.sparkline(trend, { width: 20 }),
]);
}
Dashboard Table
type Metric = {
name: string;
current: number;
history: number[];
};
function metricsDashboard(metrics: Metric[]): VNode {
return ui.column({ gap: 1, p: 1 }, [
ui.text("Metrics", { variant: "heading" }),
...metrics.map((m) =>
ui.row({ gap: 2, key: m.name }, [
ui.text(m.name, { style: { bold: true } }),
ui.text(String(m.current)),
ui.sparkline(m.history, { width: 30 }),
])
),
]);
}
Status Summary
function statusSummary(services: Array<{
name: string;
uptime: number[];
}>): VNode {
return ui.column({ gap: 0 }, [
ui.text("Service Health (Last 24h)", { variant: "heading" }),
...services.map((s) => {
const avgUptime = s.uptime.reduce((a, b) => a + b) / s.uptime.length;
const color = avgUptime > 0.99 ? "#10b981" : avgUptime > 0.95 ? "#f59e0b" : "#ef4444";
return ui.row({ gap: 2, key: s.name }, [
ui.text(s.name),
ui.sparkline(s.uptime, {
width: 24,
min: 0.9,
max: 1.0,
style: { fg: color },
}),
ui.text(`${(avgUptime * 100).toFixed(1)}%`, { style: { dim: true } }),
]);
}),
]);
}
Real-Time Monitor
import { defineWidget } from "@rezi-ui/core";
type MonitorState = {
metrics: Record<string, number[]>;
maxHistory: number;
};
function realtimeMonitor(state: MonitorState): VNode {
return ui.column({ gap: 1, p: 1 }, [
ui.text("Live Metrics", { variant: "heading" }),
...Object.entries(state.metrics).map(([name, history]) => {
// Keep only recent history
const recent = history.slice(-state.maxHistory);
const current = recent[recent.length - 1] ?? 0;
return ui.row({ gap: 2, key: name }, [
ui.text(name.padEnd(12)),
ui.text(current.toFixed(1).padStart(6)),
ui.sparkline(recent, {
width: 40,
highRes: true,
blitter: "braille",
}),
]);
}),
]);
}
Comparison View
function comparisonSparklines(
series: Array<{ name: string; data: number[] }>
): VNode {
// Compute global min/max for consistent scaling
const allValues = series.flatMap((s) => s.data);
const min = Math.min(...allValues);
const max = Math.max(...allValues);
return ui.column({ gap: 0 }, [
ui.text("Comparison", { variant: "heading" }),
...series.map((s) =>
ui.row({ gap: 2, key: s.name }, [
ui.text(s.name.padEnd(15)),
ui.sparkline(s.data, {
width: 50,
min,
max,
}),
])
),
]);
}
Trend Indicator
function trendIndicator(history: number[]): VNode {
const first = history[0] ?? 0;
const last = history[history.length - 1] ?? 0;
const change = ((last - first) / first) * 100;
const arrow = change > 0 ? "↑" : change < 0 ? "↓" : "→";
const color = change > 0 ? "#10b981" : change < 0 ? "#ef4444" : "#6b7280";
return ui.row({ gap: 2 }, [
ui.sparkline(history, { width: 15 }),
ui.text(arrow, { style: { fg: color, bold: true } }),
ui.text(`${change > 0 ? "+" : ""}${change.toFixed(1)}%`, {
style: { fg: color },
}),
]);
}
Table Integration
function metricsTable(
data: Array<{
id: string;
name: string;
value: number;
history: number[];
}>
): VNode {
return ui.table({
id: "metrics-table",
columns: [
{ key: "name", header: "Metric", width: 20 },
{ key: "value", header: "Current", width: 10, align: "right" },
{
key: "history",
header: "Trend",
width: 30,
render: (value) => {
const history = value as number[];
return ui.sparkline(history, { width: 28 });
},
},
],
data,
getRowKey: (row) => row.id,
});
}
Multi-Line Display
function multilineSparklines(datasets: Record<string, number[]>): VNode {
return ui.column({ gap: 0, p: 1 }, [
ui.text("System Metrics", { variant: "heading" }),
ui.divider(),
...Object.entries(datasets).map(([label, data]) =>
ui.row({ gap: 1, key: label }, [
ui.text(label.padEnd(10), { style: { dim: true } }),
ui.sparkline(data, {
width: 60,
highRes: true,
blitter: "braille",
}),
])
),
]);
}
Styling
Custom Colors
ui.sparkline(data, {
style: {
fg: "#3b82f6",
bold: true,
},
});
Conditional Colors
function coloredSparkline(data: number[], threshold: number): VNode {
const current = data[data.length - 1] ?? 0;
const color = current > threshold ? "#ef4444" : "#10b981";
return ui.sparkline(data, {
style: { fg: color },
});
}
- Standard mode: Instant for any size
- High-res mode: Best for < 200 points
- Recommended width: 10-50 cells
Common Patterns
Rolling Window
function rollingSparkline(history: number[], windowSize: number): VNode {
const window = history.slice(-windowSize);
return ui.sparkline(window);
}
Normalized Display
function normalizedSparkline(data: number[]): VNode {
const mean = data.reduce((a, b) => a + b) / data.length;
const stdDev = Math.sqrt(
data.reduce((a, b) => a + (b - mean) ** 2, 0) / data.length
);
const normalized = data.map((v) => (v - mean) / stdDev);
return ui.sparkline(normalized, {
min: -3,
max: 3,
});
}
See Also