Get btop-style Braille graphs in your terminal app in under 5 minutes.
[dependencies]
bgraph = "0.1"
ratatui = "0.29"
crossterm = "0.28"Display a sine wave with one data source:
use bgraph::{DataSource, Graph};
use ratatui::style::{Color, Style};
// Define your data source
struct SineWave;
impl DataSource for SineWave {
fn sample(&self, x: f32) -> f32 {
(x * 6.28).sin()
}
}
// In your ratatui draw loop:
let graph = Graph::new(&SineWave)
.x_range(0.0, 2.0)
.y_range(-1.2, 1.2)
.style(Style::default().fg(Color::Cyan));
frame.render_widget(graph, area);Or use a closure:
use bgraph::{FnDataSource, Graph};
let data = vec![10.0, 45.0, 80.0, 60.0, 30.0];
let source = FnDataSource::new(move |x| {
let i = (x * (data.len() - 1) as f32) as usize;
data.get(i).copied().unwrap_or(0.0)
});
frame.render_widget(Graph::new(&source).x_range(0.0, 1.0), area);Real-time scrolling graph with O(1) updates:
use bgraph::{TimeSeries, TimeSeriesState};
// Create state once (keeps last 100 points, Y: 0-100)
let mut state = TimeSeriesState::with_range(100, 0.0, 100.0);
// In your update loop:
state.push(new_value); // O(1) - oldest point auto-removed
// In your draw loop:
let graph = TimeSeries::new();
frame.render_stateful_widget(graph, area, &mut state);Add value-based coloring with smooth RGB interpolation:
use bgraph::ColorGradient;
use ratatui::style::Color;
let gradient = ColorGradient::new()
.add_stop(0.0, Color::Rgb(0, 150, 0)) // Green (low)
.add_stop(0.5, Color::Rgb(255, 255, 0)) // Yellow (mid)
.add_stop(1.0, Color::Rgb(255, 0, 0)); // Red (high)
let graph = TimeSeries::new().gradient(gradient);Overlay multiple data series (e.g., CPU cores):
use bgraph::{MultiTimeSeries, MultiTimeSeriesState, LegendPosition};
// 4 series, 100 points each, Y: 0-100
let mut state = MultiTimeSeriesState::with_range(4, 100, 0.0, 100.0);
state.set_label(0, "Core 0".to_string());
state.set_label(1, "Core 1".to_string());
// ...
// Update each series
state.push(0, core0_value);
state.push(1, core1_value);
// Render with legend
let graph = MultiTimeSeries::new()
.show_legend(true)
.legend_position(LegendPosition::TopRight);
frame.render_stateful_widget(graph, area, &mut state);Two independent graphs in one widget - horizontal or vertical split:
use bgraph::{DualTimeSeries, DualTimeSeriesState, SplitDirection};
let mut state = DualTimeSeriesState::with_ranges(
100, // capacity
(0.0, 100.0), // first Y-range
(0.0, 100.0), // second Y-range
);
state.push(val1, val2);
// Horizontal: left | right (default)
let graph = DualTimeSeries::new().split_ratio(0.5);
// Vertical: top / bottom
let graph = DualTimeSeries::new()
.split_direction(SplitDirection::Vertical)
.split_ratio(0.5);
frame.render_stateful_widget(graph, area, &mut state);Snap graph height for mathematically clean scale markers:
use bgraph::snap_graph_area;
// Snap area to nice height (10, 20, etc.) and center vertically
let graph_area = snap_graph_area(inner_area);
frame.render_stateful_widget(graph, graph_area, &mut state);Result: If your layout gives 12 rows, snap_graph_area returns 10 rows centered. Each row = exactly 10% of range, each braille sub-level = 2%. The 50% mark lands exactly on a row boundary.
Override: Don't call snap functions if you want full resolution.
See all features in action:
cargo run --example demo| Use Case | Widget | State |
|---|---|---|
| Static function plot | Graph |
None (Widget) |
| Real-time scrolling | TimeSeries |
TimeSeriesState |
| Compare two metrics | DualTimeSeries |
DualTimeSeriesState |
| Multiple overlays | MultiTimeSeries |
MultiTimeSeriesState |
// All time-series state has the same pattern:
let mut state = TimeSeriesState::with_range(capacity, y_min, y_max);
state.push(value); // Add new point
state.clear(); // Reset
// All widgets support:
.style(Style::default().fg(Color::Cyan)) // Base color
.gradient(ColorGradient::new()...) // Heat-map
.render_mode(RenderMode::Block) // Fallback mode- See
BGRAPH_CHEATSHEET.mdfor complete API reference - See
CHANGELOG.mdfor release notes - Run
cargo doc --openfor full documentation