mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 12:43:16 +00:00
Add braille mode for the chart widget
This commit is contained in:
parent
bcb3d751bf
commit
95a160cf50
3 changed files with 120 additions and 47 deletions
|
@ -22,7 +22,7 @@ use log4rs::config::{Appender, Config, Root};
|
|||
|
||||
use tui::Terminal;
|
||||
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
|
||||
BarChart};
|
||||
BarChart, Marker};
|
||||
use tui::layout::{Group, Direction, Size, Rect};
|
||||
use tui::style::Color;
|
||||
|
||||
|
@ -51,14 +51,16 @@ impl Iterator for RandomSignal {
|
|||
#[derive(Clone)]
|
||||
struct SinSignal {
|
||||
x: f64,
|
||||
interval: f64,
|
||||
period: f64,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
fn new(period: f64, scale: f64) -> SinSignal {
|
||||
fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
x: 0.0,
|
||||
interval: interval,
|
||||
period: period,
|
||||
scale: scale,
|
||||
}
|
||||
|
@ -68,7 +70,7 @@ impl SinSignal {
|
|||
impl Iterator for SinSignal {
|
||||
type Item = (f64, f64);
|
||||
fn next(&mut self) -> Option<(f64, f64)> {
|
||||
self.x += 1.0;
|
||||
self.x += self.interval;
|
||||
Some((self.x, ((self.x * 1.0 / self.period).sin() + 1.0) * self.scale))
|
||||
}
|
||||
}
|
||||
|
@ -110,8 +112,8 @@ fn main() {
|
|||
info!("Start");
|
||||
|
||||
let mut rand_signal = RandomSignal::new(Range::new(0, 100));
|
||||
let mut sin_signal = SinSignal::new(4.0, 20.0);
|
||||
let mut sin_signal2 = SinSignal::new(2.0, 10.0);
|
||||
let mut sin_signal = SinSignal::new(1.0, 4.0, 20.0);
|
||||
let mut sin_signal2 = SinSignal::new(0.1, 2.0, 10.0);
|
||||
|
||||
let mut app = App {
|
||||
size: Rect::default(),
|
||||
|
@ -125,7 +127,7 @@ fn main() {
|
|||
progress: 0,
|
||||
data: rand_signal.clone().take(200).collect(),
|
||||
data2: sin_signal.clone().take(20).collect(),
|
||||
data3: sin_signal2.clone().take(20).collect(),
|
||||
data3: sin_signal2.clone().take(200).collect(),
|
||||
data4: vec![("B1", 9),
|
||||
("B2", 12),
|
||||
("B3", 5),
|
||||
|
@ -150,6 +152,8 @@ fn main() {
|
|||
|
||||
for _ in 0..20 {
|
||||
sin_signal.next();
|
||||
}
|
||||
for _ in 0..200 {
|
||||
sin_signal2.next();
|
||||
}
|
||||
|
||||
|
@ -215,8 +219,12 @@ fn main() {
|
|||
app.data.pop();
|
||||
app.data2.remove(0);
|
||||
app.data2.push(sin_signal.next().unwrap());
|
||||
app.data3.remove(0);
|
||||
app.data3.push(sin_signal2.next().unwrap());
|
||||
for _ in 0..10 {
|
||||
app.data3.remove(0);
|
||||
}
|
||||
for _ in 0..10 {
|
||||
app.data3.push(sin_signal2.next().unwrap());
|
||||
}
|
||||
let i = app.data4.pop().unwrap();
|
||||
app.data4.insert(0, i);
|
||||
app.window[0] += 1.0;
|
||||
|
@ -311,8 +319,14 @@ fn draw(t: &mut Terminal, app: &App) {
|
|||
.color(Color::Gray)
|
||||
.bounds([0.0, 40.0])
|
||||
.labels(&["0", "20", "40"]))
|
||||
.datasets(&[Dataset::default().color(Color::Cyan).data(&app.data2),
|
||||
Dataset::default().color(Color::Yellow).data(&app.data3)])
|
||||
.datasets(&[Dataset::default()
|
||||
.marker(Marker::Dot)
|
||||
.color(Color::Cyan)
|
||||
.data(&app.data2),
|
||||
Dataset::default()
|
||||
.marker(Marker::Braille)
|
||||
.color(Color::Yellow)
|
||||
.data(&app.data3)])
|
||||
.render(&chunks[1], t);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,12 @@ use layout::Rect;
|
|||
use style::Color;
|
||||
use symbols;
|
||||
|
||||
pub const DOTS: [[u16; 2]; 4] =
|
||||
[[0x0001, 0x0008], [0x0002, 0x0010], [0x0004, 0x0020], [0x0040, 0x0080]];
|
||||
pub const BRAILLE_OFFSET: u16 = 0x2800;
|
||||
pub const BRAILLE_BLANK: char = '⠀';
|
||||
|
||||
|
||||
pub struct Axis<'a> {
|
||||
title: Option<&'a str>,
|
||||
title_color: Color,
|
||||
|
@ -62,8 +68,14 @@ impl<'a> Axis<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum Marker {
|
||||
Dot,
|
||||
Braille,
|
||||
}
|
||||
|
||||
pub struct Dataset<'a> {
|
||||
data: &'a [(f64, f64)],
|
||||
marker: Marker,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
|
@ -71,6 +83,7 @@ impl<'a> Default for Dataset<'a> {
|
|||
fn default() -> Dataset<'a> {
|
||||
Dataset {
|
||||
data: &[],
|
||||
marker: Marker::Dot,
|
||||
color: Color::Reset,
|
||||
}
|
||||
}
|
||||
|
@ -82,32 +95,17 @@ impl<'a> Dataset<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn marker(mut self, marker: Marker) -> Dataset<'a> {
|
||||
self.marker = marker;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Dataset<'a> {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chart<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
x_axis: Axis<'a>,
|
||||
y_axis: Axis<'a>,
|
||||
datasets: &'a [Dataset<'a>],
|
||||
bg: Color,
|
||||
}
|
||||
|
||||
impl<'a> Default for Chart<'a> {
|
||||
fn default() -> Chart<'a> {
|
||||
Chart {
|
||||
block: None,
|
||||
x_axis: Axis::default(),
|
||||
y_axis: Axis::default(),
|
||||
bg: Color::Reset,
|
||||
datasets: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ChartLayout {
|
||||
legend_x: Option<(u16, u16)>,
|
||||
|
@ -133,6 +131,26 @@ impl Default for ChartLayout {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Chart<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
x_axis: Axis<'a>,
|
||||
y_axis: Axis<'a>,
|
||||
datasets: &'a [Dataset<'a>],
|
||||
bg: Color,
|
||||
}
|
||||
|
||||
impl<'a> Default for Chart<'a> {
|
||||
fn default() -> Chart<'a> {
|
||||
Chart {
|
||||
block: None,
|
||||
x_axis: Axis::default(),
|
||||
y_axis: Axis::default(),
|
||||
bg: Color::Reset,
|
||||
datasets: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Chart<'a> {
|
||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a> {
|
||||
self.block = Some(block);
|
||||
|
@ -159,6 +177,7 @@ impl<'a> Chart<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
|
||||
fn layout(&self, area: &Rect) -> ChartLayout {
|
||||
let mut layout = ChartLayout::default();
|
||||
if area.height == 0 || area.width == 0 {
|
||||
|
@ -271,37 +290,77 @@ impl<'a> Widget for Chart<'a> {
|
|||
|
||||
if let Some(y) = layout.axis_x {
|
||||
for x in graph_area.left()..graph_area.right() {
|
||||
buf.update_cell(x, y, symbols::line::HORIZONTAL, self.x_axis.color, self.bg);
|
||||
buf.set_cell(x, y, symbols::line::HORIZONTAL, self.x_axis.color, self.bg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(x) = layout.axis_y {
|
||||
for y in graph_area.top()..graph_area.bottom() {
|
||||
buf.update_cell(x, y, symbols::line::VERTICAL, self.y_axis.color, self.bg);
|
||||
buf.set_cell(x, y, symbols::line::VERTICAL, self.y_axis.color, self.bg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(y) = layout.axis_x {
|
||||
if let Some(x) = layout.axis_y {
|
||||
buf.update_cell(x, y, symbols::line::BOTTOM_LEFT, self.x_axis.color, self.bg);
|
||||
buf.set_cell(x, y, symbols::line::BOTTOM_LEFT, self.x_axis.color, self.bg);
|
||||
}
|
||||
}
|
||||
|
||||
for dataset in self.datasets {
|
||||
for &(x, y) in dataset.data.iter() {
|
||||
if x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] ||
|
||||
y < self.y_axis.bounds[0] || y > self.y_axis.bounds[1] {
|
||||
continue;
|
||||
match dataset.marker {
|
||||
Marker::Dot => {
|
||||
for &(x, y) in dataset.data.iter() {
|
||||
if x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] ||
|
||||
y < self.y_axis.bounds[0] ||
|
||||
y > self.y_axis.bounds[1] {
|
||||
continue;
|
||||
}
|
||||
let dy = (self.y_axis.bounds[1] - y) * graph_area.height as f64 /
|
||||
(self.y_axis.bounds[1] - self.y_axis.bounds[0]);
|
||||
let dx = (self.x_axis.bounds[1] - x) * graph_area.width as f64 /
|
||||
(self.x_axis.bounds[1] - self.x_axis.bounds[0]);
|
||||
buf.set_cell(dx as u16 + graph_area.left(),
|
||||
dy as u16 + graph_area.top(),
|
||||
symbols::BLACK_CIRCLE,
|
||||
dataset.color,
|
||||
self.bg);
|
||||
}
|
||||
}
|
||||
Marker::Braille => {
|
||||
let width = graph_area.width as usize;
|
||||
let height = graph_area.height as usize;
|
||||
let mut grid: Vec<u16> = vec![BRAILLE_OFFSET; width * height + 1];
|
||||
for &(x, y) in dataset.data.iter() {
|
||||
if x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] ||
|
||||
y < self.y_axis.bounds[0] ||
|
||||
y > self.y_axis.bounds[1] {
|
||||
continue;
|
||||
}
|
||||
let dy =
|
||||
((self.y_axis.bounds[1] - y) * graph_area.height as f64 * 4.0 /
|
||||
(self.y_axis.bounds[1] -
|
||||
self.y_axis.bounds[0])) as usize;
|
||||
let dx =
|
||||
((self.x_axis.bounds[1] - x) * graph_area.width as f64 * 2.0 /
|
||||
(self.x_axis.bounds[1] -
|
||||
self.x_axis.bounds[0])) as usize;
|
||||
grid[dy / 4 * width + dx / 2] |= DOTS[dy % 4][dx % 2];
|
||||
}
|
||||
let string = String::from_utf16(&grid).unwrap();
|
||||
for (i, ch) in string.chars().enumerate() {
|
||||
if ch != BRAILLE_BLANK {
|
||||
let (x, y) = (i % width, i / width);
|
||||
buf.update_cell(x as u16 + graph_area.left(),
|
||||
y as u16 + graph_area.top(),
|
||||
|c| {
|
||||
c.symbol.clear();
|
||||
c.symbol.push(ch);
|
||||
c.fg = dataset.color;
|
||||
c.bg = self.bg;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let dy = (self.y_axis.bounds[1] - y) * graph_area.height as f64 /
|
||||
(self.y_axis.bounds[1] - self.y_axis.bounds[0]);
|
||||
let dx = (self.x_axis.bounds[1] - x) * graph_area.width as f64 /
|
||||
(self.x_axis.bounds[1] - self.x_axis.bounds[0]);
|
||||
buf.update_cell(dx as u16 + graph_area.left(),
|
||||
dy as u16 + graph_area.top(),
|
||||
symbols::BLACK_CIRCLE,
|
||||
dataset.color,
|
||||
self.bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ pub use self::text::Text;
|
|||
pub use self::list::List;
|
||||
pub use self::gauge::Gauge;
|
||||
pub use self::sparkline::Sparkline;
|
||||
pub use self::chart::{Chart, Axis, Dataset};
|
||||
pub use self::chart::{Chart, Axis, Dataset, Marker};
|
||||
pub use self::barchart::BarChart;
|
||||
|
||||
use buffer::Buffer;
|
||||
|
|
Loading…
Reference in a new issue