diff --git a/examples/prototype.rs b/examples/prototype.rs index c4065d7..f78f83a 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -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); } }); diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 544b6a2..b561e85 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -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,29 +95,14 @@ impl<'a> Dataset<'a> { self } - pub fn color(mut self, color: Color) -> Dataset<'a> { - self.color = color; + pub fn marker(mut self, marker: Marker) -> Dataset<'a> { + self.marker = marker; self } -} -pub struct Chart<'a> { - block: Option>, - 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: &[], - } + pub fn color(mut self, color: Color) -> Dataset<'a> { + self.color = color; + self } } @@ -133,6 +131,26 @@ impl Default for ChartLayout { } } +pub struct Chart<'a> { + block: Option>, + 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 = 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); } } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index c749063..07047f5 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -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;