mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 22:20:31 +00:00
More work on the widgets API
* Change slice to vecs in table and canvas to ease the interaction with data constructed on the fly * Add a legend to charts based on datasets' names and colors * Update demo to reflect previous updates
This commit is contained in:
parent
20465f2159
commit
9e5195096a
4 changed files with 154 additions and 32 deletions
|
@ -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<Server<'a>>,
|
||||
}
|
||||
|
||||
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::<Vec<Vec<&str>>>())
|
||||
.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::<Vec<Label>>())
|
||||
.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)])
|
||||
|
|
|
@ -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<Iterator<Item = (f64, f64)> + 'a>;
|
||||
}
|
||||
|
||||
pub struct Label<'a> {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub text: &'a str,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
pub struct Canvas<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
x_bounds: [f64; 2],
|
||||
y_bounds: [f64; 2],
|
||||
layers: &'a [&'a [&'a Shape<'a>]],
|
||||
layers: Vec<Cow<'a, [&'a Shape<'a>]>>,
|
||||
labels: Vec<Label<'a>>,
|
||||
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<L>(&mut self, layers: Vec<L>) -> &mut Canvas<'a>
|
||||
where L: Into<Cow<'a, [&'a Shape<'a>]>>
|
||||
{
|
||||
self.layers =
|
||||
layers.into_iter().map(|l| l.into()).collect::<Vec<Cow<'a, [&'a Shape<'a>]>>>();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn layer<L>(&mut self, layer: L) -> &mut Canvas<'a>
|
||||
where L: Into<Cow<'a, [&'a Shape<'a>]>>
|
||||
{
|
||||
self.layers.push(layer.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn labels<L>(&mut self, labels: L) -> &mut Canvas<'a>
|
||||
where L: Into<Vec<Label<'a>>>
|
||||
{
|
||||
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<u16> = vec![BRAILLE_OFFSET; width * height];
|
||||
let mut colors: Vec<Color> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u16>,
|
||||
label_y: Option<u16>,
|
||||
axis_x: Option<u16>,
|
||||
axis_y: Option<u16>,
|
||||
legend_area: Option<Rect>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Cow<'a, [&'a str]>>,
|
||||
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<R>(&mut self, rows: Vec<R>) -> &mut Table<'a>
|
||||
where R: Into<Cow<'a, [&'a str]>>
|
||||
{
|
||||
self.rows = rows.into_iter().map(|r| r.into()).collect::<Vec<Cow<'a, [&'a str]>>>();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue