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.
- ⚡ 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
DataSourceor wrap closures withFnDataSource - 📊 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
Add to your Cargo.toml:
[dependencies]
bgraph = "0.1"
ratatui = "0.29"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);btop-style dashboard with multi-panel layouts, per-core sparklines, and real-time metrics:
Smooth dual graphs with gradient color mapping and position-based coloring:
Network statistics displaying RX/TX data with mirrored dual graphs and position-gradient coloring:
bgraph uses the same technique as btop for creating smooth, filled area graphs:
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!
Sine wave with btop-style Braille rendering:
⢸⣿⣿⣿⡇ ⢸⣿⣿⣿⡇ ⢸⣿⣿⣿⡇
⢰⣿⣿⣿⣿⣿⡆ ⢰⣿⣿⣿⣿⣿⡆ ⢰⣿⣿⣿⣿⣿⡆
⢀⣾⣿⣿⣿⣿⣿⣿⡀⢀⣾⣿⣿⣿⣿⣿⣿⡀⢀⣾⣿⣿⣿⣿⣿⣿⡀
⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⡇
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);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));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 BlockFor terminals that don't support Unicode Braille:
use bgraph::RenderMode;
let graph = Graph::new(&data_source)
.render_mode(RenderMode::Block)
.wave_char('█');pub trait DataSource {
fn sample(&self, x: f32) -> f32;
}Implement this to provide data to the graph.
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
}pub enum RenderMode {
Braille, // High-resolution filled area (default)
Block, // Simple block characters
}pub enum GradientMode {
Value, // Color by data value (default)
Position, // Color by row Y position (btop-style)
}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];
}Run the included examples:
# Simple sine wave
cargo run --example simple
# Multi-panel demo showcasing all features
cargo run --example demoThe 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.
| 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 |
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);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);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%- 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 ✅
MIT
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!
bgraph requires Rust 1.74.0 or later.
This crate uses #![deny(unsafe_code)] and contains no unsafe code.


