diff --git a/examples/demo.rs b/examples/demo.rs index 5a5cb825..bd575f3a 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -24,7 +24,7 @@ use log4rs::config::{Appender, Config, Root}; use tui::Terminal; use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, BarChart, Marker, Tabs, Table}; -use tui::widgets::canvas::{Canvas, Line, Map, MapResolution}; +use tui::widgets::canvas::{Canvas, Line, Shape, Map, MapResolution, Label}; use tui::layout::{Group, Direction, Size, Rect}; use tui::style::Color; @@ -78,6 +78,13 @@ impl Iterator for SinSignal { } } +struct Server<'a> { + name: &'a str, + location: &'a str, + coords: (f64, f64), + status: &'a str, +} + struct MyTabs { titles: [&'static str; 2], selection: usize, @@ -110,6 +117,7 @@ struct App<'a> { window: [f64; 2], colors: [Color; 2], color_index: usize, + servers: Vec>, } enum Event { @@ -151,7 +159,7 @@ fn main() { "Event15", "Event16", "Event17", "Event18", "Event19"], selected: 0, tabs: MyTabs { - titles: ["Main", "Map"], + titles: ["Tab0", "Tab1"], selection: 0, }, show_chart: true, @@ -177,6 +185,18 @@ fn main() { window: [0.0, 20.0], colors: [Color::Magenta, Color::Red], color_index: 0, + servers: vec![Server { + name: "US-1", + location: "New York City", + coords: (40.71, -74.00), + status: "Up", + }, + Server { + name: "Europe-1", + location: "Paris", + coords: (48.85, 2.35), + status: "Failure", + }], }; let (tx, rx) = mpsc::channel(); let input_tx = tx.clone(); @@ -307,16 +327,35 @@ fn draw(t: &mut Terminal, app: &App) { .header(&["Server", "Location", "Status"]) .header_color(Color::Red) .widths(&[20, 20, 20]) - .rows(&[&["Europe#1", "Paris", "Up"], - &["Europe#2", "Berlin", "Up"]]) - .color(Color::Green) + .rows(app.servers + .iter() + .map(|s| vec![s.name, s.location, s.status]) + .collect::>>()) .render(&chunks[0], t); + Canvas::default() .block(Block::default().title("World").borders(border::ALL)) - .layers(&[&[&Map { - color: Color::Green, - resolution: MapResolution::High, - }]]) + .layer([&Map { + color: Color::White, + resolution: MapResolution::High, + } as &Shape] + .as_ref()) + .labels(app.servers + .iter() + .map(|s| { + let color = if s.status == "Up" { + Color::Green + } else { + Color::Red + }; + Label { + x: s.coords.1, + y: s.coords.0, + text: "X", + color: color, + } + }) + .collect::>()) .x_bounds([-180.0, 180.0]) .y_bounds([-90.0, 90.0]) .render(&chunks[1], t); @@ -414,10 +453,12 @@ fn draw_main(t: &mut Terminal, app: &App, area: &Rect) { .bounds([-25.0, 25.0]) .labels(&["-25", "0", "25"])) .datasets(&[Dataset::default() + .name("data2") .marker(Marker::Dot) .color(Color::Cyan) .data(&app.data2), Dataset::default() + .name("data3") .marker(Marker::Braille) .color(Color::Yellow) .data(&app.data3)]) diff --git a/src/widgets/canvas/mod.rs b/src/widgets/canvas/mod.rs index 379d3f32..c0029a76 100644 --- a/src/widgets/canvas/mod.rs +++ b/src/widgets/canvas/mod.rs @@ -7,6 +7,8 @@ pub use self::points::Points; pub use self::line::Line; pub use self::map::{Map, MapResolution}; +use std::borrow::Cow; + use style::Color; use buffer::Buffer; use widgets::{Block, Widget}; @@ -22,11 +24,19 @@ pub trait Shape<'a> { fn points(&'a self) -> Box + 'a>; } +pub struct Label<'a> { + pub x: f64, + pub y: f64, + pub text: &'a str, + pub color: Color, +} + pub struct Canvas<'a> { block: Option>, x_bounds: [f64; 2], y_bounds: [f64; 2], - layers: &'a [&'a [&'a Shape<'a>]], + layers: Vec]>>, + labels: Vec>, background_color: Color, } @@ -36,7 +46,8 @@ impl<'a> Default for Canvas<'a> { block: None, x_bounds: [0.0, 0.0], y_bounds: [0.0, 0.0], - layers: &[], + layers: Vec::new(), + labels: Vec::new(), background_color: Color::Reset, } } @@ -55,8 +66,25 @@ impl<'a> Canvas<'a> { self.y_bounds = bounds; self } - pub fn layers(&mut self, layers: &'a [&'a [&'a Shape<'a>]]) -> &mut Canvas<'a> { - self.layers = layers; + pub fn layers(&mut self, layers: Vec) -> &mut Canvas<'a> + where L: Into]>> + { + self.layers = + layers.into_iter().map(|l| l.into()).collect::]>>>(); + self + } + + pub fn layer(&mut self, layer: L) -> &mut Canvas<'a> + where L: Into]>> + { + self.layers.push(layer.into()); + self + } + + pub fn labels(&mut self, labels: L) -> &mut Canvas<'a> + where L: Into>> + { + self.labels = labels.into(); self } @@ -79,7 +107,7 @@ impl<'a> Widget for Canvas<'a> { let width = canvas_area.width as usize; let height = canvas_area.height as usize; - for layer in self.layers { + for layer in &self.layers { let mut grid: Vec = vec![BRAILLE_OFFSET; width * height]; let mut colors: Vec = vec![Color::Reset; width * height]; @@ -116,5 +144,20 @@ impl<'a> Widget for Canvas<'a> { } } } + + for label in self.labels.iter().filter(|l| { + !(l.x < self.x_bounds[0] || l.x > self.x_bounds[1] || l.y < self.y_bounds[0] || + l.y > self.y_bounds[1]) + }) { + let dy = ((self.y_bounds[1] - label.y) * (canvas_area.height - 1) as f64 / + (self.y_bounds[1] - self.y_bounds[0])) as u16; + let dx = ((label.x - self.x_bounds[0]) * (canvas_area.width - 1) as f64 / + (self.x_bounds[1] - self.x_bounds[0])) as u16; + buf.set_string(dx + canvas_area.left(), + dy + canvas_area.top(), + label.text, + label.color, + self.background_color); + } } } diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 2c5e6593..7584bfb0 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -2,8 +2,8 @@ use std::cmp::max; use unicode_width::UnicodeWidthStr; -use widgets::{Widget, Block}; -use widgets::canvas::{Canvas, Points}; +use widgets::{Widget, Block, border}; +use widgets::canvas::{Canvas, Shape, Points}; use buffer::Buffer; use layout::Rect; use style::Color; @@ -69,6 +69,7 @@ pub enum Marker { } pub struct Dataset<'a> { + name: &'a str, data: &'a [(f64, f64)], marker: Marker, color: Color, @@ -77,6 +78,7 @@ pub struct Dataset<'a> { impl<'a> Default for Dataset<'a> { fn default() -> Dataset<'a> { Dataset { + name: "", data: &[], marker: Marker::Dot, color: Color::Reset, @@ -85,6 +87,11 @@ impl<'a> Default for Dataset<'a> { } impl<'a> Dataset<'a> { + pub fn name(mut self, name: &'a str) -> Dataset<'a> { + self.name = name; + self + } + pub fn data(mut self, data: &'a [(f64, f64)]) -> Dataset<'a> { self.data = data; self @@ -103,24 +110,26 @@ impl<'a> Dataset<'a> { #[derive(Debug)] struct ChartLayout { - legend_x: Option<(u16, u16)>, - legend_y: Option<(u16, u16)>, + title_x: Option<(u16, u16)>, + title_y: Option<(u16, u16)>, label_x: Option, label_y: Option, axis_x: Option, axis_y: Option, + legend_area: Option, graph_area: Rect, } impl Default for ChartLayout { fn default() -> ChartLayout { ChartLayout { - legend_x: None, - legend_y: None, + title_x: None, + title_y: None, label_x: None, label_y: None, axis_x: None, axis_y: None, + legend_area: None, graph_area: Rect::default(), } } @@ -211,14 +220,26 @@ impl<'a> Chart<'a> { if let Some(title) = self.x_axis.title { let w = title.width() as u16; if w < layout.graph_area.width && layout.graph_area.height > 2 { - layout.legend_x = Some((x + layout.graph_area.width - w, y)); + layout.title_x = Some((x + layout.graph_area.width - w, y)); } } if let Some(title) = self.y_axis.title { let w = title.width() as u16; if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 { - layout.legend_y = Some((x + 1, area.top())); + layout.title_y = Some((x + 1, area.top())); + } + } + + if let Some(inner_width) = self.datasets.iter().map(|d| d.name.width() as u16).max() { + let legend_width = inner_width + 2; + let legend_height = self.datasets.len() as u16 + 2; + if legend_width < layout.graph_area.width / 3 && + legend_height < layout.graph_area.height / 3 { + layout.legend_area = Some(Rect::new(layout.graph_area.right() - legend_width, + layout.graph_area.top(), + legend_width, + legend_height)); } } layout @@ -245,12 +266,12 @@ impl<'a> Widget for Chart<'a> { self.background(&chart_area, buf, self.background_color); } - if let Some((x, y)) = layout.legend_x { + if let Some((x, y)) = layout.title_x { let title = self.x_axis.title.unwrap(); buf.set_string(x, y, title, self.x_axis.title_color, self.background_color); } - if let Some((x, y)) = layout.legend_y { + if let Some((x, y)) = layout.title_y { let title = self.y_axis.title.unwrap(); buf.set_string(x, y, title, self.y_axis.title_color, self.background_color); } @@ -344,13 +365,27 @@ impl<'a> Widget for Chart<'a> { .background_color(self.background_color) .x_bounds(self.x_axis.bounds) .y_bounds(self.y_axis.bounds) - .layers(&[&[&Points { - coords: dataset.data, - color: dataset.color, - }]]) + .layer([&Points { + coords: dataset.data, + color: dataset.color, + } as &Shape] + .as_ref()) .draw(&graph_area, buf); } } } + + if let Some(legend_area) = layout.legend_area { + Block::default() + .borders(border::ALL) + .draw(&legend_area, buf); + for (i, dataset) in self.datasets.iter().enumerate() { + buf.set_string(legend_area.x + 1, + legend_area.y + 1 + i as u16, + dataset.name, + dataset.color, + self.background_color); + } + } } } diff --git a/src/widgets/table.rs b/src/widgets/table.rs index 9385467f..a05080a6 100644 --- a/src/widgets/table.rs +++ b/src/widgets/table.rs @@ -1,4 +1,5 @@ use std::cmp::max; +use std::borrow::Cow; use unicode_width::UnicodeWidthStr; @@ -12,7 +13,7 @@ pub struct Table<'a> { header: &'a [&'a str], header_color: Color, widths: &'a [u16], - rows: &'a [&'a [&'a str]], + rows: Vec>, color: Color, column_spacing: u16, background_color: Color, @@ -25,7 +26,7 @@ impl<'a> Default for Table<'a> { header: &[], header_color: Color::Reset, widths: &[], - rows: &[], + rows: Vec::new(), color: Color::Reset, column_spacing: 1, background_color: Color::Reset, @@ -54,8 +55,10 @@ impl<'a> Table<'a> { self } - pub fn rows(&mut self, rows: &'a [&'a [&'a str]]) -> &mut Table<'a> { - self.rows = rows; + pub fn rows(&mut self, rows: Vec) -> &mut Table<'a> + where R: Into> + { + self.rows = rows.into_iter().map(|r| r.into()).collect::>>(); self }