skip to content
==> src/sparkline.c <==
1/* ratatui-widgets 0.3.2's Sparkline (every bar present: no absent values). */
2#include "tui/sparkline.h"
3
4static const char *const NINE_LEVELS[9] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"};
5
6/* floor(a * b / c) for b < 2^20, in 128 bits made of two u64 (ratatui computes in u128). */
7static uint64_t mul_div(uint64_t a, uint64_t b, uint64_t c) {
8    uint64_t lo_part = (a & 0xFFFFFFFFu) * b, hi_part = (a >> 32) * b;
9    uint64_t lo = lo_part + (hi_part << 32);
10    uint64_t hi = (hi_part >> 32) + (lo < lo_part);
11    uint64_t q = 0, r = 0;
12    for (int i = 127; i >= 0; i--) {
13        uint64_t bit = i >= 64 ? (hi >> (i - 64)) & 1 : (lo >> i) & 1;
14        uint64_t top = r >> 63;
15        r = r << 1 | bit;
16        if (top || r >= c) {
17            r -= c;
18            if (i < 64) q |= (uint64_t)1 << i;
19        }
20    }
21    return q; /* the quotient's high half is 0 for our inputs (value * ticks / max <= ticks when value <= max) */
22}
23
24static uint64_t scale_height(uint64_t value, uint64_t max, uint16_t max_height) {
25    if (max == 0) return 0;
26    uint64_t max_ticks = (uint64_t)max_height * 8;
27    uint64_t ticks = mul_div(value, max_ticks, max);
28    /* A quotient past 2^64 (value far above max) still clamps to max_ticks. */
29    if (value > max && value / max >= max_ticks) return max_ticks;
30    return ticks < max_ticks ? ticks : max_ticks;
31}
32
33void tui_sparkline_render(tui_arena *a, const tui_sparkline *s, tui_rect area, tui_buffer *buf) {
34    if (s->block) tui_block_render(a, s->block, area, buf);
35    tui_rect sa = tui_block_inner_opt(s->block, area);
36    if (tui_rect_empty(sa)) return;
37    uint64_t max = s->max;
38    if (!s->has_max) {
39        max = 1;
40        if (s->n) {
41            max = 0;
42            for (size_t i = 0; i < s->n; i++)
43                if (s->data[i] > max) max = s->data[i];
44        }
45    }
46    size_t count = s->n < sa.width ? s->n : sa.width;
47    for (size_t i = 0; i < count; i++) {
48        uint16_t x = s->right_to_left ? (uint16_t)(tui_rect_right(sa) - i - 1) : (uint16_t)(sa.x + i);
49        uint64_t h = scale_height(s->data[i], max, sa.height);
50        for (uint16_t j = sa.height; j-- > 0;) {
51            const char *sym = NINE_LEVELS[h > 8 ? 8 : h];
52            h = h > 8 ? h - 8 : 0;
53            tui_cell *c = tui_buffer_cell(buf, x, (uint16_t)(sa.y + j));
54            if (!c) continue;
55            tui_cell_set_str(c, sym);
56            tui_cell_set_style(c, s->style);
57        }
58    }
59}