E611 GitHub - 5ocworkshop/bgraph: A smooth graphing widget for ratatui based on the braille character graphs featured in `btop`
[go: up one dir, main page]

Skip to content

A smooth graphing widget for ratatui based on the braille character graphs featured in `btop`

License

Notifications You must be signed in to change notification settings

5ocworkshop/bgraph

bgraph

btop-style Braille graphing for terminal UIs

Create beautiful, high-resolution filled area graphs in your terminal using Unicode Braille characters. Inspired by btop's smooth CPU/GPU usage graphs.

License: MIT MSRV: 1.74+ Crates.io Docs.rs

Features

  • High Resolution - 5 vertical levels per cell; 8x modes for sparklines and braille gauges
  • 🎨 Smooth Gradients - btop-style 5×5 Braille lookup table creates natural transitions
  • 🔧 Simple API - Implement DataSource or wrap closures with FnDataSource
  • 📊 Filled Area Graphs - Beautiful btop-style aesthetic
  • 🎯 Zero-line Support - Optional horizontal reference line
  • 🔄 Flexible Rendering - Braille mode or block character fallback
  • 📦 Ratatui Integration - Widgets + StatefulWidgets for ratatui apps
  • 🧩 Widget Suite - MiniGraph, MiniTimeSeries, Dual/Multi graphs, meters, and gauges

Installation

Add to your Cargo.toml:

[dependencies]
bgraph = "0.1"
ratatui = "0.29"

Quick Start

use bgraph::{Graph, DataSource};
use ratatui::style::{Color, Style};

// Define your data source
struct SineWave;
impl DataSource for SineWave {
    fn sample(&self, x: f32) -> f32 {
        (x * 2.0 * std::f32::consts::PI).sin()
    }
}

// Create and render graph
let graph = Graph::new(&SineWave)
    .style(Style::default().fg(Color::Cyan))
    .x_range(0.0, 2.0)
    .y_range(-1.5, 1.5);

// In your ratatui draw loop:
frame.render_widget(graph, area);

Gallery

btop-style dashboard with multi-panel layouts, per-core sparklines, and real-time metrics:

btop-style dashboard demo

Smooth dual graphs with gradient color mapping and position-based coloring:

dual smooth graphs with gradients

Network statistics displaying RX/TX data with mirrored dual graphs and position-gradient coloring:

network dual graph with gradient mode

How It Works

bgraph uses the same technique as btop for creating smooth, filled area graphs:

The Braille Lookup Table

Each character encodes a transition from the previous value to the current value:

                  Current Level →
              0    1    2    3    4
           ┌─────────────────────────┐
Prev   0   │ ' ' '⢀' '⢠' '⢰' '⢸' │  (Empty → ...)
Level  1   │ '⡀' '⣀' '⣠' '⣰' '⣸' │  (25% → ...)
  ↓    2   │ '⡄' '⣄' '⣤' '⣴' '⣼' │  (50% → ...)
       3   │ '⡆' '⣆' '⣦' '⣶' '⣾' │  (75% → ...)
       4   │ '⡇' '⣇' '⣧' '⣷' '⣿' │  (Full → ...)
           └─────────────────────────┘

Example transitions:

  • '⢀' = 0%→25% (rising from empty)
  • '⣿' = 100%→100% (flat high)
  • '⡇' = 100%→0% (sharp drop)

This creates smooth gradients instead of scatter plots!

Visual Example

Sine wave with btop-style Braille rendering:

    ⢸⣿⣿⣿⡇    ⢸⣿⣿⣿⡇    ⢸⣿⣿⣿⡇
   ⢰⣿⣿⣿⣿⣿⡆  ⢰⣿⣿⣿⣿⣿⡆  ⢰⣿⣿⣿⣿⣿⡆
  ⢀⣾⣿⣿⣿⣿⣿⣿⡀⢀⣾⣿⣿⣿⣿⣿⣿⡀⢀⣾⣿⣿⣿⣿⣿⣿⡀
 ⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⣿⣿⣿⣿⡇    ⣿⣿⣿⣿⡇    ⣿⣿⣿⣿⡇

Usage Examples

Using a Closure

use bgraph::{FnDataSource, Graph};

let data = vec![0.2, 0.5, 0.8, 1.0, 0.7, 0.3];
let source = FnDataSource::new(move |x| {
    let idx = (x * (data.len() - 1) as f32) as usize;
    data.get(idx).copied().unwrap_or(0.0)
});

let graph = Graph::new(&source)
    .x_range(0.0, 1.0)
    .y_range(0.0, 1.2);

Multiple Graphs

struct CPUUsage { /* ... */ }
struct MemoryUsage { /* ... */ }

impl DataSource for CPUUsage {
    fn sample(&self, x: f32) -> f32 {
        // Return CPU % at time x
    }
}

let cpu_graph = Graph::new(&CPUUsage)
    .style(Style::default().fg(Color::Green));

let mem_graph = Graph::new(&MemoryUsage)
    .style(Style::default().fg(Color::Yellow));

Custom Styling

use ratatui::style::{Color, Modifier, Style};

let graph = Graph::new(&data_source)
    .style(
        Style::default()
            .fg(Color::Cyan)
            .add_modifier(Modifier::BOLD)
    )
    .show_zero_line(true)
    .zero_line_style(Style::default().fg(Color::DarkGray))
    .render_mode(RenderMode::Braille);  // or Block

Block Mode Fallback

For terminals that don't support Unicode Braille:

use bgraph::RenderMode;

let graph = Graph::new(&data_source)
    .render_mode(RenderMode::Block)
    .wave_char('█');

API Reference

DataSource Trait

pub trait DataSource {
    fn sample(&self, x: f32) -> f32;
}

Implement this to provide data to the graph.

Graph Widget

impl Graph<'_> {
    pub fn new(data: &dyn DataSource) -> Self;
    pub fn x_range(self, start: f32, end: f32) -> Self;
    pub fn y_range(self, min: f32, max: f32) -> Self;
    pub fn style(self, style: Style) -> Self;
    pub fn render_mode(self, mode: RenderMode) -> Self;
    pub fn wave_char(self, c: char) -> Self;
    pub fn show_zero_line(self, show: bool) -> Self;
    pub fn zero_line_style(self, style: Style) -> Self;
    pub fn gradient(self, gradient: ColorGradient) -> Self;
    pub fn gradient_mode(self, mode: GradientMode) -> Self;
    pub fn invert_y(self, invert: bool) -> Self;  // Fill from top down
}

RenderMode Enum

pub enum RenderMode {
    Braille,  // High-resolution filled area (default)
    Block,    // Simple block characters
}

GradientMode Enum

pub enum GradientMode {
    Value,     // Color by data value (default)
    Position,  // Color by row Y position (btop-style)
}

ColorGradient

impl ColorGradient {
    pub fn new() -> Self;
    pub fn two_point(start: Color, end: Color) -> Self;
    pub fn three_point(start: Color, mid: Color, end: Color) -> Self;
    pub fn add_stop(self, position: f32, color: Color) -> Self;
    pub fn get_color(&self, value: f32) -> Color;
    pub fn precompute(&self) -> [Color; 101];
}

Examples

Run the included examples:

# Simple sine wave
cargo run --example simple

# Multi-panel demo showcasing all features
cargo run --example demo

Demo Features

The demo example cycles through 7 panels showcasing all widgets, including the dense btop-style dashboard with per-core sparklines, memory graphs, disk meters, and braille gauges. See HOWTO-DEMO.md for panel-by-panel details.

Comparison with Other Approaches

Approach Resolution Effect Use Case
bgraph (Braille) 5 levels/cell Filled area btop-style graphs, high detail
Block chars 1 cell Simple bars Basic charts, compatibility
Scatter plot Variable Point cloud Scientific data

Gradient Color Mapping

Create heat-map effects like btop by mapping values to colors:

use bgraph::{Graph, ColorGradient, GradientMode};
use ratatui::style::Color;

// Convenience constructors for common patterns
let gradient = ColorGradient::three_point(
    Color::Rgb(80, 140, 200),   // Cool blue (0%)
    Color::Rgb(255, 180, 80),   // Warm orange (50%)
    Color::Rgb(255, 80, 80),    // Hot red (100%)
);

// Or build manually with add_stop()
let gradient = ColorGradient::new()
    .add_stop(0.0, Color::Green)
    .add_stop(0.5, Color::Yellow)
    .add_stop(1.0, Color::Red);

let graph = Graph::new(&cpu_data)
    .gradient(gradient)
    .y_range(0.0, 100.0);

Gradient Modes

Two coloring modes are available:

use bgraph::GradientMode;

// Value mode (default): color by data value at each position
// More information-dense - color changes reflect actual values
let graph = Graph::new(&data)
    .gradient(gradient.clone())
    .gradient_mode(GradientMode::Value);

// Position mode (btop-style): color by vertical row position
// Top rows get "hot" colors, bottom rows get "cool" colors
// More intuitive - "higher = hotter" physically matches the graph
let graph = Graph::new(&data)
    .gradient(gradient.clone())
    .gradient_mode(GradientMode::Position);

Pre-computed Gradient Tables

For performance-critical code, pre-compute the gradient lookup table:

let gradient = ColorGradient::three_point(
    Color::Blue, Color::Yellow, Color::Red
);

// Pre-compute 101 colors (0-100%)
let table: [Color; 101] = gradient.precompute();

// Fast lookup by percentage
let color = table[75];  // Color at 75%

Roadmap

  • Gradient color support (like btop's heat maps) ✅
  • Dual graph support (side-by-side/stacked like btop) ✅
  • Horizontal scrolling time series ✅
  • Multiple data series on one graph ✅
  • Position-based gradient mode (btop-style) ✅
  • Gradient convenience constructors ✅

License

MIT

Credits

This library implements the Braille-based graphing technique pioneered by btop, a resource monitor by aristocratos.

The original C++ implementation in btop introduced the brilliant 5×5 Braille lookup table approach for creating smooth, filled area graphs in the terminal. This Rust library adapts that technique for use in ratatui applications.

btop Copyright © 2021 Jakob P. Liljenberg (aristocratos) bgraph adapts the graphing technique under MIT License

Thank you to aristocratos for the inspiration and innovation!

Minimum Supported Rust Version (MSRV)

bgraph requires Rust 1.74.0 or later.

Safety

This crate uses #![deny(unsafe_code)] and contains no unsafe code.

About

A smooth graphing widget for ratatui based on the braille character graphs featured in `btop`

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

0