Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 106x 106x 106x 485x 414x 71x 71x 71x 409x 409x 409x 409x 1168x 409x 71x 483x 2x 481x 481x 481x 50x 431x 2476x 7x 2469x 2469x | /**
* Unicode block-character sparkline renderer.
*
* Maps numeric data points to Unicode block characters (▁▂▃▄▅▆▇█)
* for compact inline trend visualization in terminal tables.
*
* Zero values use `⎽` (U+23BD scan line 9) as a thin baseline marker.
* Non-zero values map to `▁`–`█` (8 levels), so even the smallest
* positive value is visibly taller than zero.
*
* Each block character is exactly 1 terminal column wide (verified by
* `string-width`), making sparklines safe for column-aligned table output.
*/
/** 8 block characters for non-zero values, ordered by height (1/8 to 8/8). */
const BLOCKS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] as const;
/**
* Scan-line character used to represent zero-value data points.
*
* U+23BD HORIZONTAL SCAN LINE-9 — a thin horizontal line at the very bottom
* of the character cell. Visually thinner than `▁` (lower 1/8 block) while
* staying vertically aligned with block-drawing characters, unlike text-metric
* characters (`_`, underlined space) which sit at the text descender.
*/
const ZERO_CHAR = "⎽";
/** Default sparkline width when not specified. */
const DEFAULT_WIDTH = 8;
/**
* Downsample an array of values to a target length by averaging adjacent buckets.
*
* Divides the source array into `targetLen` equal-width buckets and returns
* the mean of each bucket. When `values.length <= targetLen`, returns the
* original array unchanged.
*
* @param values - Source data points
* @param targetLen - Desired output length (must be >= 1)
* @returns Downsampled values with length <= targetLen
*/
/** Downsample an array of values to a target length by averaging buckets. */
export function downsample(values: number[], targetLen: number): number[] {
if (values.length <= targetLen) {
return values;
}
const bucketSize = values.length / targetLen;
const result: number[] = [];
for (let i = 0; i < targetLen; i++) {
const start = Math.floor(i * bucketSize);
const end = Math.floor((i + 1) * bucketSize);
let sum = 0;
for (let j = start; j < end; j++) {
sum += values[j] ?? 0;
}
result.push(sum / (end - start));
}
return result;
}
/**
* Render a sparkline string from numeric values using Unicode block characters.
*
* Zero maps to `⎽` (scan line). Non-zero values map to `▁`–`█` (8 levels)
* based on proportion of the maximum. When data has more points than `width`,
* adjacent points are averaged (downsampled). When fewer, the natural
* length is preserved (no upsampling to avoid visual artifacts).
*
* @param values - Numeric data points (e.g., event counts per time bucket)
* @param width - Maximum sparkline width in characters. Defaults to 8.
* @returns Sparkline string, or empty string if no data
*
* @example
* sparkline([0, 1, 3, 7, 4, 2, 1, 0]) // "⎽▁▃█▅▂▁⎽"
* sparkline([0, 0, 0, 0]) // "⎽⎽⎽⎽"
* sparkline([]) // ""
*/
export function sparkline(values: number[], width = DEFAULT_WIDTH): string {
if (values.length === 0) {
return "";
}
const sampled = downsample(values, width);
const max = Math.max(...sampled);
// All zeros — flat baseline
if (max === 0) {
return ZERO_CHAR.repeat(sampled.length);
}
return sampled
.map((v) => {
if (v === 0) {
return ZERO_CHAR;
}
const normalized = Math.round((v / max) * 7);
return BLOCKS[Math.min(7, Math.max(0, normalized))] ?? BLOCKS[0];
})
.join("");
}
|