diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-17 19:03:03 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-17 19:03:03 +0200 |
| commit | 1aa67dbe3e711617895d7b1d6e4dffccc74fa95a (patch) | |
| tree | 792a15a803602dc318daa51019d7433a1cf4fc14 | |
| parent | d9fb5c42e8a8deed21d93b6961a0775acea5540a (diff) | |
| download | conky-theme-udt-1aa67dbe3e711617895d7b1d6e4dffccc74fa95a.tar.gz conky-theme-udt-1aa67dbe3e711617895d7b1d6e4dffccc74fa95a.zip | |
feat: add card.plot, a shared-scale line chart
Several series on one baseline and one ceiling. The shared ceiling is the
point: scaled independently, a 200kB/s upload draws the same height as a
40MB/s download, which is a lie about a link's shape.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -rw-r--r-- | lib/card.lua | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/lib/card.lua b/lib/card.lua index f25c393..337347e 100644 --- a/lib/card.lua +++ b/lib/card.lua @@ -270,4 +270,58 @@ function M.truncate(s, limit) return s:sub(1, cut - 1) .. '\u{2026}' end +-- A line chart: several series sharing one baseline and one vertical scale. +-- +-- `series` is a list of { values = <array>, colour = <rgb> }. `max` is the +-- shared ceiling; passing one rather than computing per series is the whole +-- point, because two series scaled independently lie about their relative +-- size: a 200kB/s upload would draw the same height as a 40MB/s download. +-- +-- Values are drawn oldest-left, one per array entry, so a caller sizing its +-- history to the pixel width gets one sample per column and no interpolation. +-- A series shorter than its buffer draws only what it has, which is what a +-- freshly started dashboard shows while the window fills. +function M.plot(cr, x, y, w, h, series, max, colors) + if not (w > 0 and h > 0) then return end + max = tonumber(max) or 0 + if max <= 0 then max = 1 end -- an idle link is a flat line, not a division by zero + + local prev_width = cairo_get_line_width(cr) + local prev_cap = cairo_get_line_cap(cr) + + -- The baseline, so an empty chart still reads as a chart rather than a gap. + M.rgba(cr, colors.rule, 0.5) + cairo_new_path(cr) + cairo_set_line_width(cr, 1) + cairo_move_to(cr, x, y + h) + cairo_line_to(cr, x + w, y + h) + cairo_stroke(cr) + + cairo_set_line_width(cr, math.max(1.5, h * 0.02)) + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND) + + for _, s in ipairs(series) do + local v = s.values + local n = #v + if n >= 2 then + -- One sample per column when the caller sized its buffer to the width. + local step = w / math.max(n - 1, 1) + M.rgba(cr, s.colour, 1) + cairo_new_path(cr) + for i = 1, n do + local frac = v[i] / max + if frac < 0 then frac = 0 elseif frac > 1 then frac = 1 end + local px = x + (i - 1) * step + local py = y + h - frac * h + if i == 1 then cairo_move_to(cr, px, py) else cairo_line_to(cr, px, py) end + end + cairo_stroke(cr) + end + end + + -- Leave the line state as it was found, for the same reason card.ring does. + cairo_set_line_width(cr, prev_width) + cairo_set_line_cap(cr, prev_cap) +end + return M |
