Add braille mode for the chart widget

This commit is contained in:
Florian Dehau 2016-10-27 12:35:56 +02:00
parent bcb3d751bf
commit 95a160cf50
3 changed files with 120 additions and 47 deletions

View file

@ -22,7 +22,7 @@ use log4rs::config::{Appender, Config, Root};
use tui::Terminal; use tui::Terminal;
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, 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::layout::{Group, Direction, Size, Rect};
use tui::style::Color; use tui::style::Color;
@ -51,14 +51,16 @@ impl Iterator for RandomSignal {
#[derive(Clone)] #[derive(Clone)]
struct SinSignal { struct SinSignal {
x: f64, x: f64,
interval: f64,
period: f64, period: f64,
scale: f64, scale: f64,
} }
impl SinSignal { impl SinSignal {
fn new(period: f64, scale: f64) -> SinSignal { fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
SinSignal { SinSignal {
x: 0.0, x: 0.0,
interval: interval,
period: period, period: period,
scale: scale, scale: scale,
} }
@ -68,7 +70,7 @@ impl SinSignal {
impl Iterator for SinSignal { impl Iterator for SinSignal {
type Item = (f64, f64); type Item = (f64, f64);
fn next(&mut self) -> Option<(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)) Some((self.x, ((self.x * 1.0 / self.period).sin() + 1.0) * self.scale))
} }
} }
@ -110,8 +112,8 @@ fn main() {
info!("Start"); info!("Start");
let mut rand_signal = RandomSignal::new(Range::new(0, 100)); let mut rand_signal = RandomSignal::new(Range::new(0, 100));
let mut sin_signal = SinSignal::new(4.0, 20.0); let mut sin_signal = SinSignal::new(1.0, 4.0, 20.0);
let mut sin_signal2 = SinSignal::new(2.0, 10.0); let mut sin_signal2 = SinSignal::new(0.1, 2.0, 10.0);
let mut app = App { let mut app = App {
size: Rect::default(), size: Rect::default(),
@ -125,7 +127,7 @@ fn main() {
progress: 0, progress: 0,
data: rand_signal.clone().take(200).collect(), data: rand_signal.clone().take(200).collect(),
data2: sin_signal.clone().take(20).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), data4: vec![("B1", 9),
("B2", 12), ("B2", 12),
("B3", 5), ("B3", 5),
@ -150,6 +152,8 @@ fn main() {
for _ in 0..20 { for _ in 0..20 {
sin_signal.next(); sin_signal.next();
}
for _ in 0..200 {
sin_signal2.next(); sin_signal2.next();
} }
@ -215,8 +219,12 @@ fn main() {
app.data.pop(); app.data.pop();
app.data2.remove(0); app.data2.remove(0);
app.data2.push(sin_signal.next().unwrap()); app.data2.push(sin_signal.next().unwrap());
for _ in 0..10 {
app.data3.remove(0); app.data3.remove(0);
}
for _ in 0..10 {
app.data3.push(sin_signal2.next().unwrap()); app.data3.push(sin_signal2.next().unwrap());
}
let i = app.data4.pop().unwrap(); let i = app.data4.pop().unwrap();
app.data4.insert(0, i); app.data4.insert(0, i);
app.window[0] += 1.0; app.window[0] += 1.0;
@ -311,8 +319,14 @@ fn draw(t: &mut Terminal, app: &App) {
.color(Color::Gray) .color(Color::Gray)
.bounds([0.0, 40.0]) .bounds([0.0, 40.0])
.labels(&["0", "20", "40"])) .labels(&["0", "20", "40"]))
.datasets(&[Dataset::default().color(Color::Cyan).data(&app.data2), .datasets(&[Dataset::default()
Dataset::default().color(Color::Yellow).data(&app.data3)]) .marker(Marker::Dot)
.color(Color::Cyan)
.data(&app.data2),
Dataset::default()
.marker(Marker::Braille)
.color(Color::Yellow)
.data(&app.data3)])
.render(&chunks[1], t); .render(&chunks[1], t);
} }
}); });

View file

@ -8,6 +8,12 @@ use layout::Rect;
use style::Color; use style::Color;
use symbols; 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> { pub struct Axis<'a> {
title: Option<&'a str>, title: Option<&'a str>,
title_color: Color, title_color: Color,
@ -62,8 +68,14 @@ impl<'a> Axis<'a> {
} }
} }
pub enum Marker {
Dot,
Braille,
}
pub struct Dataset<'a> { pub struct Dataset<'a> {
data: &'a [(f64, f64)], data: &'a [(f64, f64)],
marker: Marker,
color: Color, color: Color,
} }
@ -71,6 +83,7 @@ impl<'a> Default for Dataset<'a> {
fn default() -> Dataset<'a> { fn default() -> Dataset<'a> {
Dataset { Dataset {
data: &[], data: &[],
marker: Marker::Dot,
color: Color::Reset, color: Color::Reset,
} }
} }
@ -82,32 +95,17 @@ impl<'a> Dataset<'a> {
self self
} }
pub fn marker(mut self, marker: Marker) -> Dataset<'a> {
self.marker = marker;
self
}
pub fn color(mut self, color: Color) -> Dataset<'a> { pub fn color(mut self, color: Color) -> Dataset<'a> {
self.color = color; self.color = color;
self 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)] #[derive(Debug)]
struct ChartLayout { struct ChartLayout {
legend_x: Option<(u16, u16)>, 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> { impl<'a> Chart<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a> { pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a> {
self.block = Some(block); self.block = Some(block);
@ -159,6 +177,7 @@ impl<'a> Chart<'a> {
self self
} }
fn layout(&self, area: &Rect) -> ChartLayout { fn layout(&self, area: &Rect) -> ChartLayout {
let mut layout = ChartLayout::default(); let mut layout = ChartLayout::default();
if area.height == 0 || area.width == 0 { if area.height == 0 || area.width == 0 {
@ -271,38 +290,78 @@ impl<'a> Widget for Chart<'a> {
if let Some(y) = layout.axis_x { if let Some(y) = layout.axis_x {
for x in graph_area.left()..graph_area.right() { 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 { if let Some(x) = layout.axis_y {
for y in graph_area.top()..graph_area.bottom() { 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(y) = layout.axis_x {
if let Some(x) = layout.axis_y { 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 dataset in self.datasets {
match dataset.marker {
Marker::Dot => {
for &(x, y) in dataset.data.iter() { for &(x, y) in dataset.data.iter() {
if x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] || 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] { y < self.y_axis.bounds[0] ||
y > self.y_axis.bounds[1] {
continue; continue;
} }
let dy = (self.y_axis.bounds[1] - y) * graph_area.height as f64 / let dy = (self.y_axis.bounds[1] - y) * graph_area.height as f64 /
(self.y_axis.bounds[1] - self.y_axis.bounds[0]); (self.y_axis.bounds[1] - self.y_axis.bounds[0]);
let dx = (self.x_axis.bounds[1] - x) * graph_area.width as f64 / let dx = (self.x_axis.bounds[1] - x) * graph_area.width as f64 /
(self.x_axis.bounds[1] - self.x_axis.bounds[0]); (self.x_axis.bounds[1] - self.x_axis.bounds[0]);
buf.update_cell(dx as u16 + graph_area.left(), buf.set_cell(dx as u16 + graph_area.left(),
dy as u16 + graph_area.top(), dy as u16 + graph_area.top(),
symbols::BLACK_CIRCLE, symbols::BLACK_CIRCLE,
dataset.color, dataset.color,
self.bg); 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;
});
}
}
}
}
}
} }
} }

View file

@ -11,7 +11,7 @@ pub use self::text::Text;
pub use self::list::List; pub use self::list::List;
pub use self::gauge::Gauge; pub use self::gauge::Gauge;
pub use self::sparkline::Sparkline; 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; pub use self::barchart::BarChart;
use buffer::Buffer; use buffer::Buffer;