sparklinekit

Package Version Downloads Hex Docs CI License

Sparkline generator for Gleam. Unicode block characters for the terminal, SVG strings for the browser, PNG byte arrays for everything else. Pure Gleam, zero runtime dependencies, runs on both the Erlang and JavaScript targets — each exercised in CI on every push. Full API reference at https://hexdocs.pm/sparklinekit/.

Line sparkline example

Bar sparkline example

Mixed positive/negative bar sparkline example

Install

gleam add sparklinekit

Unicode block sparkline

import sparklinekit/unicode

pub fn shape() -> String {
  unicode.render([1.0, 5.0, 22.0, 13.0, 5.0, 2.0, 7.0])
  // -> "▁▂█▅▂▁▃"
}
import sparklinekit/unicode

pub fn shape_from_ints() -> String {
  unicode.render_ints([1, 5, 22, 13, 5, 2, 7])
  // -> "▁▂█▅▂▁▃"
}
import gleam/io
import sparklinekit/unicode

pub fn print_latency(samples: List(Float)) -> Nil {
  io.println("latency  " <> unicode.render(samples))
}

SVG line

Themed line with smoothing, area fill, and end-point spot

import sparklinekit/line

pub fn minimal_line() -> String {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0])
  |> line.to_svg
}
import sparklinekit/line
import sparklinekit/theme

pub fn themed_line() -> String {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0])
  |> line.with_theme(theme.ocean())
  |> line.to_svg
}
import sparklinekit/line
import sparklinekit/theme

pub fn smooth_line() -> String {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0, 9.0, 6.0])
  |> line.with_theme(theme.ocean())
  |> line.with_smoothing(0.25)
  |> line.to_svg
}
import sparklinekit/line
import sparklinekit/theme

pub fn area_line() -> String {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0, 9.0, 6.0])
  |> line.with_theme(theme.ocean())
  |> line.with_smoothing(0.25)
  |> line.with_area_fill(True)
  |> line.with_spot(3.0)
  |> line.to_svg
}
import sparklinekit/line

pub fn raw_colour_line() -> String {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0])
  |> line.with_color("#378ADD")
  |> line.with_size(240, 60)
  |> line.with_stroke_width(2.0)
  |> line.to_svg
}
import sparklinekit/line
import sparklinekit/theme

pub fn line_with_ints() -> String {
  line.new_ints([1, 5, 3, 8, 4])
  |> line.with_theme(theme.forest())
  |> line.to_svg
}

SVG bar

Rounded amber bars on white

Mixed positive / negative bars sharing a zero baseline

import sparklinekit/bar

pub fn minimal_bar() -> String {
  bar.new([3.0, 7.0, 2.0, 9.0, 5.0])
  |> bar.to_svg
}
import sparklinekit/bar
import sparklinekit/theme

pub fn rounded_bar() -> String {
  bar.new([3.0, 7.0, 2.0, 9.0, 5.0, 11.0, 6.0])
  |> bar.with_theme(theme.sunset())
  |> bar.with_corner_radius(3.0)
  |> bar.with_bar_gap(4.0)
  |> bar.to_svg
}
import sparklinekit/bar

pub fn win_loss() -> String {
  bar.new([3.0, -2.0, 5.0, -4.0, 6.0, -1.0])
  |> bar.with_color("#22A06B")
  |> bar.with_negative_color("#E5484D")
  |> bar.with_bar_gap(3.0)
  |> bar.to_svg
}
import sparklinekit/bar
import sparklinekit/theme

pub fn themed_win_loss() -> String {
  bar.new_ints([3, -2, 5, -4, 6, -1])
  |> bar.with_theme(theme.forest())
  |> bar.with_corner_radius(3.0)
  |> bar.to_svg
}

PNG output

The same builders produce 8-bit RGBA PNG byte arrays via to_png/1 — ready for simplifile.write_bits or bit_array.base64_encode.

Line rendered as PNG with anti-aliased stroke and area fill

Bar rendered as PNG with rounded corners

import sparklinekit/line

pub fn line_png() -> BitArray {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0])
  |> line.with_color("#378ADD")
  |> line.with_size(240, 60)
  |> line.with_smoothing(0.25)
  |> line.with_area_fill(True)
  |> line.to_png
}
import sparklinekit/bar
import sparklinekit/theme

pub fn bar_png() -> BitArray {
  bar.new([3.0, 7.0, 2.0, 9.0, 5.0, 11.0, 6.0])
  |> bar.with_theme(theme.sunset())
  |> bar.with_corner_radius(3.0)
  |> bar.to_png
}
import simplifile
import sparklinekit/line

pub fn save_to_disk() {
  line.new([1.0, 5.0, 3.0, 8.0, 4.0])
  |> line.to_png
  |> simplifile.write_bits(to: "chart.png", bits: _)
}

Themes

Ten built-in colour schemes, each a bundle of four CSS colour strings (foreground / background / area / negative). Pass one to line.with_theme / bar.with_theme to set all four slots at once; chain with_color / with_background_color / with_area_color / with_negative_color afterwards to override individual slots.

ThemeWhen to useForegroundBackgroundNegativePreview
theme.ocean()Default-ish blue, neutral dashboards#2563EB#FFFFFF#94A3B8ocean
theme.forest()“Up” / growth, finance-style green#10B981#FFFFFF#EF4444forest
theme.sunset()Attention, trending, warm accents#F97316#FFFFFF#7C3AEDsunset
theme.mono()Print, monochrome dashboards#0F172A#FFFFFF#94A3B8mono
theme.neon()Dark-mode UIs, high contrast#22D3EE#020617#F472B6neon
theme.pastel()Low-saturation, soft palettes#A78BFA#FAF5FF#FB7185pastel
theme.crimson()Losses, alerts, “attention required”#DC2626#FFFFFF#94A3B8crimson
theme.slate()Corporate neutral, brand-agnostic#475569#FFFFFF#F59E0Bslate
theme.amber()Finance / warning, softer than red#F59E0B#FFFFFF#DC2626amber
theme.midnight()Dark-mode default, off-white on navy#F8FAFC#020617#FB7185midnight

theme.default() is also available and applied implicitly when no with_theme call is made: currentColor foreground (inherits the surrounding CSS colour), no background fill, an auto-derived area tint, and #EF4444 for negative bars.

import sparklinekit/line
import sparklinekit/theme

pub fn theme_gallery() -> List(String) {
  let values = [1.0, 5.0, 3.0, 8.0, 4.0, 9.0, 6.0]
  let render = fn(t) {
    line.new(values)
    |> line.with_theme(t)
    |> line.with_area_fill(True)
    |> line.to_svg
  }
  [
    render(theme.ocean()),
    render(theme.forest()),
    render(theme.sunset()),
    render(theme.mono()),
    render(theme.neon()),
    render(theme.pastel()),
  ]
}

Edge cases

Empty input

import sparklinekit/bar
import sparklinekit/line
import sparklinekit/unicode

pub fn empty_unicode() -> String {
  unicode.render([])
  // -> ""
}

pub fn empty_line_svg() -> String {
  line.new([])
  |> line.to_svg
  // -> "<svg ...></svg>" with no <path>
}

pub fn empty_bar_svg() -> String {
  bar.new([])
  |> bar.to_svg
  // -> "<svg ...></svg>" with no <rect>
}

The PNG counterparts (line.to_png([]) / bar.to_png([])) return a blank canvas at the configured size — useful as a placeholder while data is loading.

Single value / all-equal series

A single value is rendered differently per renderer, because the “natural” shape differs. All-equal series (e.g. [5.0, 5.0, 5.0]) follow the same rules.

RendererShapeOutput
Unicodemiddle block repeatedunicode.render([5.0])"▄"
Line (SVG/PNG)flat horizontal segment at the midpoint
Bar (SVG/PNG)half-height bars rising from the bottom

The bar renderer deliberately avoids filling the whole canvas with a solid block — the half-height shape still says “constant value” without misrepresenting the scale.

Negative values

Negative values are fully supported. Unicode and line renderers normalise against the observed [min, max]; the bar renderer uses a zero baseline so positive and negative bars rise / fall from the same line.

Line with mixed positive/negative values

Bar with mixed positive/negative values

Colour handling

License

MIT

Search Document