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.
DataSource or wrap closures with FnDataSourceAdd 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 Block
For terminals that don't support Unicode Braille:
use bgraph::RenderMode;
let graph = Graph::new(&data_source)
.render_mode(RenderMode::Block)
.wave_char('█');
DataSource Traitpub trait DataSource {
fn sample(&self, x: f32) -> f32;
}
Implement this to provide data to the graph.
Graph Widgetimpl 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 Enumpub enum RenderMode {
Braille, // High-resolution filled area (default)
Block, // Simple block characters
}
GradientMode Enumpub enum GradientMode {
Value, // Color by data value (default)
Position, // Color by row Y position (btop-style)
}
ColorGradientimpl 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 demo
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.
| 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%
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.
$ claude mcp add bgraph \
-- python -m otcore.mcp_server <graph>