mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-29 14:04:50 +00:00
feat(barchart): Add direction attribute. (horizontal bars support) (#325)
* feat(barchart): Add direction attribute Enable rendring the bars horizontally. In some cases this allow us to make more efficient use of the available space. Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de> * feat(barchart)!: render the group labels depending on the alignment This is a breaking change, since the alignment by default is set to Left and the group labels are always rendered in the center. Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de> --------- Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
This commit is contained in:
parent
a937500ae4
commit
0dca6a689a
@ -62,7 +62,7 @@ impl<'a> App<'a> {
|
|||||||
},
|
},
|
||||||
Company {
|
Company {
|
||||||
label: "Comp.B",
|
label: "Comp.B",
|
||||||
revenue: [1500, 2500, 3000, 4100],
|
revenue: [1500, 2500, 3000, 500],
|
||||||
bar_style: Style::default().fg(Color::Yellow),
|
bar_style: Style::default().fg(Color::Yellow),
|
||||||
},
|
},
|
||||||
Company {
|
Company {
|
||||||
@ -140,14 +140,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(2)
|
.margin(2)
|
||||||
.constraints(
|
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)].as_ref())
|
||||||
[
|
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
|
|
||||||
let barchart = BarChart::default()
|
let barchart = BarChart::default()
|
||||||
@ -158,16 +151,17 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||||||
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow));
|
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow));
|
||||||
f.render_widget(barchart, chunks[0]);
|
f.render_widget(barchart, chunks[0]);
|
||||||
|
|
||||||
draw_bar_with_group_labels(f, app, chunks[1], false);
|
let chunks = Layout::default()
|
||||||
draw_bar_with_group_labels(f, app, chunks[2], true);
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(chunks[1]);
|
||||||
|
|
||||||
|
draw_bar_with_group_labels(f, app, chunks[0]);
|
||||||
|
draw_horizontal_bars(f, app, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_bar_with_group_labels<B>(f: &mut Frame<B>, app: &App, area: Rect, bar_labels: bool)
|
fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGroup<'a>> {
|
||||||
where
|
app.months
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
let groups: Vec<BarGroup> = app
|
|
||||||
.months
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, &month)| {
|
.map(|(i, &month)| {
|
||||||
@ -182,17 +176,34 @@ where
|
|||||||
Style::default()
|
Style::default()
|
||||||
.bg(c.bar_style.fg.unwrap())
|
.bg(c.bar_style.fg.unwrap())
|
||||||
.fg(Color::Black),
|
.fg(Color::Black),
|
||||||
)
|
);
|
||||||
.text_value(format!("{:.1}", (c.revenue[i] as f64) / 1000.));
|
|
||||||
if bar_labels {
|
if combine_values_and_labels {
|
||||||
bar = bar.label(c.label.into());
|
bar = bar.text_value(format!(
|
||||||
|
"{} ({:.1} M)",
|
||||||
|
c.label,
|
||||||
|
(c.revenue[i] as f64) / 1000.
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
bar = bar
|
||||||
|
.text_value(format!("{:.1}", (c.revenue[i] as f64) / 1000.))
|
||||||
|
.label(c.label.into());
|
||||||
}
|
}
|
||||||
bar
|
bar
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
BarGroup::default().label(month.into()).bars(&bars)
|
BarGroup::default()
|
||||||
|
.label(Line::from(month).alignment(Alignment::Center))
|
||||||
|
.bars(&bars)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bar_with_group_labels<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
||||||
|
where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let groups = create_groups(app, false);
|
||||||
|
|
||||||
let mut barchart = BarChart::default()
|
let mut barchart = BarChart::default()
|
||||||
.block(Block::default().title("Data1").borders(Borders::ALL))
|
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||||
@ -207,11 +218,44 @@ where
|
|||||||
|
|
||||||
const LEGEND_HEIGHT: u16 = 6;
|
const LEGEND_HEIGHT: u16 = 6;
|
||||||
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||||
|
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||||
let legend_area = Rect {
|
let legend_area = Rect {
|
||||||
height: LEGEND_HEIGHT,
|
height: LEGEND_HEIGHT,
|
||||||
width: TOTAL_REVENUE.len() as u16 + 2,
|
width: legend_width,
|
||||||
y: area.y,
|
y: area.y,
|
||||||
x: area.x,
|
x: area.right() - legend_width,
|
||||||
|
};
|
||||||
|
draw_legend(f, legend_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_horizontal_bars<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
||||||
|
where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let groups = create_groups(app, true);
|
||||||
|
|
||||||
|
let mut barchart = BarChart::default()
|
||||||
|
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||||
|
.bar_width(1)
|
||||||
|
.group_gap(1)
|
||||||
|
.bar_gap(0)
|
||||||
|
.direction(Direction::Horizontal);
|
||||||
|
|
||||||
|
for group in groups {
|
||||||
|
barchart = barchart.data(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(barchart, area);
|
||||||
|
|
||||||
|
const LEGEND_HEIGHT: u16 = 6;
|
||||||
|
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||||
|
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||||
|
let legend_area = Rect {
|
||||||
|
height: LEGEND_HEIGHT,
|
||||||
|
width: legend_width,
|
||||||
|
y: area.y,
|
||||||
|
x: area.right() - legend_width,
|
||||||
};
|
};
|
||||||
draw_legend(f, legend_area);
|
draw_legend(f, legend_area);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{buffer::Buffer, style::Style, text::Line};
|
use crate::{buffer::Buffer, prelude::Rect, style::Style, text::Line};
|
||||||
|
|
||||||
/// represent a bar to be shown by the Barchart
|
/// represent a bar to be shown by the Barchart
|
||||||
///
|
///
|
||||||
@ -56,6 +56,45 @@ impl<'a> Bar<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the value of the bar. value_text is used if set, otherwise the value is converted to
|
||||||
|
/// string. The value is rendered using value_style. If the value width is greater than the
|
||||||
|
/// bar width, then the value is split into 2 parts. the first part is rendered in the bar
|
||||||
|
/// using value_style. The second part is rendered outside the bar using bar_style
|
||||||
|
pub(super) fn render_value_with_different_styles(
|
||||||
|
self,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
area: Rect,
|
||||||
|
bar_length: usize,
|
||||||
|
default_value_style: Style,
|
||||||
|
bar_style: Style,
|
||||||
|
) {
|
||||||
|
let text = if let Some(text) = self.text_value {
|
||||||
|
text
|
||||||
|
} else {
|
||||||
|
self.value.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !text.is_empty() {
|
||||||
|
let style = default_value_style.patch(self.value_style);
|
||||||
|
// Since the value may be longer than the bar itself, we need to use 2 different styles
|
||||||
|
// while rendering. Render the first part with the default value style
|
||||||
|
buf.set_stringn(area.x, area.y, &text, bar_length, style);
|
||||||
|
// render the second part with the bar_style
|
||||||
|
if text.len() > bar_length {
|
||||||
|
let (first, second) = text.split_at(bar_length);
|
||||||
|
|
||||||
|
let style = bar_style.patch(self.style);
|
||||||
|
buf.set_stringn(
|
||||||
|
area.x + first.len() as u16,
|
||||||
|
area.y,
|
||||||
|
second,
|
||||||
|
area.width as usize - first.len(),
|
||||||
|
style,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn render_label_and_value(
|
pub(super) fn render_label_and_value(
|
||||||
self,
|
self,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
@ -79,14 +118,18 @@ impl<'a> Bar<'a> {
|
|||||||
x + (max_width.saturating_sub(value_label.len() as u16) >> 1),
|
x + (max_width.saturating_sub(value_label.len() as u16) >> 1),
|
||||||
y,
|
y,
|
||||||
value_label,
|
value_label,
|
||||||
self.value_style.patch(default_value_style),
|
default_value_style.patch(self.value_style),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the label
|
// render the label
|
||||||
if let Some(mut label) = self.label {
|
if let Some(mut label) = self.label {
|
||||||
label.patch_style(default_label_style);
|
// patch label styles
|
||||||
|
for span in &mut label.spans {
|
||||||
|
span.style = default_label_style.patch(span.style);
|
||||||
|
}
|
||||||
|
|
||||||
buf.set_line(
|
buf.set_line(
|
||||||
x + (max_width.saturating_sub(label.width() as u16) >> 1),
|
x + (max_width.saturating_sub(label.width() as u16) >> 1),
|
||||||
y + 1,
|
y + 1,
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use super::Bar;
|
use super::Bar;
|
||||||
use crate::text::Line;
|
use crate::{
|
||||||
|
prelude::{Alignment, Buffer, Rect},
|
||||||
|
style::Style,
|
||||||
|
text::Line,
|
||||||
|
};
|
||||||
|
|
||||||
/// represent a group of bars to be shown by the Barchart
|
/// represent a group of bars to be shown by the Barchart
|
||||||
///
|
///
|
||||||
@ -35,6 +39,23 @@ impl<'a> BarGroup<'a> {
|
|||||||
pub(super) fn max(&self) -> Option<u64> {
|
pub(super) fn max(&self) -> Option<u64> {
|
||||||
self.bars.iter().max_by_key(|v| v.value).map(|v| v.value)
|
self.bars.iter().max_by_key(|v| v.value).map(|v| v.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn render_label(self, buf: &mut Buffer, area: Rect, default_label_style: Style) {
|
||||||
|
if let Some(mut label) = self.label {
|
||||||
|
// patch label styles
|
||||||
|
for span in &mut label.spans {
|
||||||
|
span.style = default_label_style.patch(span.style);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_offset = match label.alignment {
|
||||||
|
Some(Alignment::Center) => area.width.saturating_sub(label.width() as u16) >> 1,
|
||||||
|
Some(Alignment::Right) => area.width.saturating_sub(label.width() as u16),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
buf.set_line(area.x + x_offset, area.y, &label, area.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&[(&'a str, u64)]> for BarGroup<'a> {
|
impl<'a> From<&[(&'a str, u64)]> for BarGroup<'a> {
|
||||||
|
@ -53,6 +53,8 @@ pub struct BarChart<'a> {
|
|||||||
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
||||||
/// the maximum value in the data is taken as reference)
|
/// the maximum value in the data is taken as reference)
|
||||||
max: Option<u64>,
|
max: Option<u64>,
|
||||||
|
/// direction of the bars
|
||||||
|
direction: Direction,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for BarChart<'a> {
|
impl<'a> Default for BarChart<'a> {
|
||||||
@ -69,6 +71,7 @@ impl<'a> Default for BarChart<'a> {
|
|||||||
group_gap: 0,
|
group_gap: 0,
|
||||||
bar_set: symbols::bar::NINE_LEVELS,
|
bar_set: symbols::bar::NINE_LEVELS,
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
|
direction: Direction::Vertical,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +107,9 @@ impl<'a> BarChart<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the default style of the bar.
|
||||||
|
/// It is also possible to set individually the style of each Bar.
|
||||||
|
/// In this case the default style will be patched by the individual style
|
||||||
pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
|
pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
|
||||||
self.bar_style = style;
|
self.bar_style = style;
|
||||||
self
|
self
|
||||||
@ -124,11 +130,17 @@ impl<'a> BarChart<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the default value style of the bar.
|
||||||
|
/// It is also possible to set individually the value style of each Bar.
|
||||||
|
/// In this case the default value style will be patched by the individual value style
|
||||||
pub fn value_style(mut self, style: Style) -> BarChart<'a> {
|
pub fn value_style(mut self, style: Style) -> BarChart<'a> {
|
||||||
self.value_style = style;
|
self.value_style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the default label style of the groups and bars.
|
||||||
|
/// It is also possible to set individually the label style of each Bar or Group.
|
||||||
|
/// In this case the default label style will be patched by the individual label style
|
||||||
pub fn label_style(mut self, style: Style) -> BarChart<'a> {
|
pub fn label_style(mut self, style: Style) -> BarChart<'a> {
|
||||||
self.label_style = style;
|
self.label_style = style;
|
||||||
self
|
self
|
||||||
@ -143,6 +155,12 @@ impl<'a> BarChart<'a> {
|
|||||||
self.style = style;
|
self.style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// set the direction ob the bars
|
||||||
|
pub fn direction(mut self, direction: Direction) -> BarChart<'a> {
|
||||||
|
self.direction = direction;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BarChart<'a> {
|
impl<'a> BarChart<'a> {
|
||||||
@ -196,7 +214,72 @@ impl<'a> BarChart<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_bars(&self, buf: &mut Buffer, bars_area: Rect, max: u64) {
|
fn render_horizontal_bars(self, buf: &mut Buffer, bars_area: Rect, max: u64) {
|
||||||
|
// convert the bar values to ratatui::symbols::bar::Set
|
||||||
|
let groups: Vec<Vec<u16>> = self
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.map(|group| {
|
||||||
|
group
|
||||||
|
.bars
|
||||||
|
.iter()
|
||||||
|
.map(|bar| (bar.value * u64::from(bars_area.width) / max) as u16)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// print all visible bars (without labels and values)
|
||||||
|
let mut bar_y = bars_area.top();
|
||||||
|
for (group_data, mut group) in groups.into_iter().zip(self.data) {
|
||||||
|
let bars = std::mem::take(&mut group.bars);
|
||||||
|
|
||||||
|
let label_offset = bars.len() as u16 * (self.bar_width + self.bar_gap) - self.bar_gap;
|
||||||
|
// if group_gap is zero, then there is no place to print the group label
|
||||||
|
// check also if the group label is still inside the visible area
|
||||||
|
if self.group_gap > 0 && bar_y < bars_area.bottom() - label_offset {
|
||||||
|
let label_rect = Rect {
|
||||||
|
y: bar_y + label_offset,
|
||||||
|
..bars_area
|
||||||
|
};
|
||||||
|
group.render_label(buf, label_rect, self.label_style);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (bar_length, bar) in group_data.into_iter().zip(bars) {
|
||||||
|
let bar_style = self.bar_style.patch(bar.style);
|
||||||
|
|
||||||
|
for y in 0..self.bar_width {
|
||||||
|
for x in 0..bars_area.width {
|
||||||
|
let symbol = if x < bar_length {
|
||||||
|
self.bar_set.full
|
||||||
|
} else {
|
||||||
|
self.bar_set.empty
|
||||||
|
};
|
||||||
|
buf.get_mut(bars_area.left() + x, bar_y + y)
|
||||||
|
.set_symbol(symbol)
|
||||||
|
.set_style(bar_style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bar_value_area = Rect {
|
||||||
|
y: bar_y + (self.bar_width >> 1),
|
||||||
|
..bars_area
|
||||||
|
};
|
||||||
|
bar.render_value_with_different_styles(
|
||||||
|
buf,
|
||||||
|
bar_value_area,
|
||||||
|
bar_length as usize,
|
||||||
|
self.value_style,
|
||||||
|
self.bar_style,
|
||||||
|
);
|
||||||
|
|
||||||
|
bar_y += self.bar_gap + self.bar_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
bar_y += self.group_gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_vertical_bars(&self, buf: &mut Buffer, bars_area: Rect, max: u64) {
|
||||||
// convert the bar values to ratatui::symbols::bar::Set
|
// convert the bar values to ratatui::symbols::bar::Set
|
||||||
let mut groups: Vec<Vec<u64>> = self
|
let mut groups: Vec<Vec<u64>> = self
|
||||||
.data
|
.data
|
||||||
@ -227,7 +310,7 @@ impl<'a> BarChart<'a> {
|
|||||||
_ => self.bar_set.full,
|
_ => self.bar_set.full,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bar_style = bar.style.patch(self.bar_style);
|
let bar_style = self.bar_style.patch(bar.style);
|
||||||
|
|
||||||
for x in 0..self.bar_width {
|
for x in 0..self.bar_width {
|
||||||
buf.get_mut(bar_x + x, bars_area.top() + j)
|
buf.get_mut(bar_x + x, bars_area.top() + j)
|
||||||
@ -264,23 +347,24 @@ impl<'a> BarChart<'a> {
|
|||||||
// print labels and values in one go
|
// print labels and values in one go
|
||||||
let mut bar_x = area.left();
|
let mut bar_x = area.left();
|
||||||
let bar_y = area.bottom() - label_height - 1;
|
let bar_y = area.bottom() - label_height - 1;
|
||||||
for group in self.data.into_iter() {
|
for mut group in self.data.into_iter() {
|
||||||
// print group labels under the bars or the previous labels
|
if group.bars.is_empty() {
|
||||||
if let Some(mut label) = group.label {
|
continue;
|
||||||
label.patch_style(self.label_style);
|
|
||||||
let label_max_width = group.bars.len() as u16 * self.bar_width
|
|
||||||
+ (group.bars.len() as u16 - 1) * self.bar_gap;
|
|
||||||
|
|
||||||
buf.set_line(
|
|
||||||
bar_x + (label_max_width.saturating_sub(label.width() as u16) >> 1),
|
|
||||||
area.bottom() - 1,
|
|
||||||
&label,
|
|
||||||
label_max_width,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
let bars = std::mem::take(&mut group.bars);
|
||||||
|
// print group labels under the bars or the previous labels
|
||||||
|
let label_max_width =
|
||||||
|
bars.len() as u16 * (self.bar_width + self.bar_gap) - self.bar_gap;
|
||||||
|
let group_area = Rect {
|
||||||
|
x: bar_x,
|
||||||
|
y: area.bottom() - 1,
|
||||||
|
width: label_max_width,
|
||||||
|
height: 1,
|
||||||
|
};
|
||||||
|
group.render_label(buf, group_area, self.label_style);
|
||||||
|
|
||||||
// print the bar values and numbers
|
// print the bar values and numbers
|
||||||
for bar in group.bars.into_iter() {
|
for bar in bars.into_iter() {
|
||||||
bar.render_label_and_value(
|
bar.render_label_and_value(
|
||||||
buf,
|
buf,
|
||||||
self.bar_width,
|
self.bar_width,
|
||||||
@ -314,16 +398,23 @@ impl<'a> Widget for BarChart<'a> {
|
|||||||
|
|
||||||
let max = self.maximum_data_value();
|
let max = self.maximum_data_value();
|
||||||
|
|
||||||
// remove invisible groups and bars, since we don't need to print them
|
match self.direction {
|
||||||
self.remove_invisible_groups_and_bars(area.width);
|
Direction::Horizontal => {
|
||||||
|
// remove invisible groups and bars, since we don't need to print them
|
||||||
let bars_area = Rect {
|
self.remove_invisible_groups_and_bars(area.height);
|
||||||
height: area.height - label_height,
|
self.render_horizontal_bars(buf, area, max);
|
||||||
..area
|
}
|
||||||
};
|
Direction::Vertical => {
|
||||||
self.render_bars(buf, bars_area, max);
|
// remove invisible groups and bars, since we don't need to print them
|
||||||
|
self.remove_invisible_groups_and_bars(area.width);
|
||||||
self.render_labels_and_values(area, buf, label_height);
|
let bars_area = Rect {
|
||||||
|
height: area.height - label_height,
|
||||||
|
..area
|
||||||
|
};
|
||||||
|
self.render_vertical_bars(buf, bars_area, max);
|
||||||
|
self.render_labels_and_values(area, buf, label_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +705,7 @@ mod tests {
|
|||||||
let expected = Buffer::with_lines(vec![
|
let expected = Buffer::with_lines(vec![
|
||||||
"█ █ █ █ ",
|
"█ █ █ █ ",
|
||||||
"█ █ █ █ █ █ █ █ █",
|
"█ █ █ █ █ █ █ █ █",
|
||||||
" G1 G2 G3 ",
|
"G1 G2 G3 ",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_buffer_eq!(buffer, expected);
|
assert_buffer_eq!(buffer, expected);
|
||||||
@ -637,7 +728,7 @@ mod tests {
|
|||||||
let expected = Buffer::with_lines(vec![
|
let expected = Buffer::with_lines(vec![
|
||||||
"█ █ █ ",
|
"█ █ █ ",
|
||||||
"█ █ █ █ █ █ █",
|
"█ █ █ █ █ █ █",
|
||||||
" G1 G2 G",
|
"G1 G2 G",
|
||||||
]);
|
]);
|
||||||
assert_buffer_eq!(buffer, expected);
|
assert_buffer_eq!(buffer, expected);
|
||||||
}
|
}
|
||||||
@ -659,7 +750,7 @@ mod tests {
|
|||||||
let expected = Buffer::with_lines(vec![
|
let expected = Buffer::with_lines(vec![
|
||||||
"█ █ █ ",
|
"█ █ █ ",
|
||||||
"█ █ █ █ █ █ ",
|
"█ █ █ █ █ █ ",
|
||||||
" G1 G2 ",
|
"G1 G2 ",
|
||||||
]);
|
]);
|
||||||
assert_buffer_eq!(buffer, expected);
|
assert_buffer_eq!(buffer, expected);
|
||||||
}
|
}
|
||||||
@ -753,7 +844,189 @@ mod tests {
|
|||||||
|
|
||||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
||||||
chart.render(buffer.area, &mut buffer);
|
chart.render(buffer.area, &mut buffer);
|
||||||
let expected = Buffer::with_lines(vec![" █", "█ █", " G "]);
|
let expected = Buffer::with_lines(vec![" █", "█ █", "G "]);
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_test_barchart<'a>() -> BarChart<'a> {
|
||||||
|
BarChart::default()
|
||||||
|
.data(BarGroup::default().label("G1".into()).bars(&[
|
||||||
|
Bar::default().value(2),
|
||||||
|
Bar::default().value(3),
|
||||||
|
Bar::default().value(4),
|
||||||
|
]))
|
||||||
|
.data(BarGroup::default().label("G2".into()).bars(&[
|
||||||
|
Bar::default().value(3),
|
||||||
|
Bar::default().value(4),
|
||||||
|
Bar::default().value(5),
|
||||||
|
]))
|
||||||
|
.group_gap(1)
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.bar_gap(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_horizontal_bars() {
|
||||||
|
let chart: BarChart<'_> = build_test_barchart();
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 8));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
let expected = Buffer::with_lines(vec![
|
||||||
|
"2█ ",
|
||||||
|
"3██ ",
|
||||||
|
"4███ ",
|
||||||
|
"G1 ",
|
||||||
|
"3██ ",
|
||||||
|
"4███ ",
|
||||||
|
"5████",
|
||||||
|
"G2 ",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_horizontal_bars_no_space_for_group_label() {
|
||||||
|
let chart: BarChart<'_> = build_test_barchart();
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 7));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
let expected = Buffer::with_lines(vec![
|
||||||
|
"2█ ",
|
||||||
|
"3██ ",
|
||||||
|
"4███ ",
|
||||||
|
"G1 ",
|
||||||
|
"3██ ",
|
||||||
|
"4███ ",
|
||||||
|
"5████",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_horizontal_bars_no_space_for_all_bars() {
|
||||||
|
let chart: BarChart<'_> = build_test_barchart();
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
let expected = Buffer::with_lines(vec!["2█ ", "3██ ", "4███ ", "G1 ", "3██ "]);
|
||||||
|
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_horizontal_bars_label_width_greater_than_bar(bar_color: Option<Color>) {
|
||||||
|
let mut bar = Bar::default()
|
||||||
|
.value(2)
|
||||||
|
.text_value("label".into())
|
||||||
|
.value_style(Style::default().red());
|
||||||
|
|
||||||
|
if let Some(color) = bar_color {
|
||||||
|
bar = bar.style(Style::default().fg(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
let chart: BarChart<'_> = BarChart::default()
|
||||||
|
.data(BarGroup::default().bars(&[bar, Bar::default().value(5)]))
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.bar_style(Style::default().yellow())
|
||||||
|
.value_style(Style::default().italic())
|
||||||
|
.bar_gap(0);
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
|
||||||
|
let mut expected = Buffer::with_lines(vec!["label", "5████"]);
|
||||||
|
|
||||||
|
// first line has a yellow foreground. first cell contains italic "5"
|
||||||
|
expected.get_mut(0, 1).modifier.insert(Modifier::ITALIC);
|
||||||
|
for x in 0..5 {
|
||||||
|
expected.get_mut(x, 1).set_fg(Color::Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_color = if let Some(color) = bar_color {
|
||||||
|
color
|
||||||
|
} else {
|
||||||
|
Color::Yellow
|
||||||
|
};
|
||||||
|
|
||||||
|
// second line contains the word "label". Since the bar value is 2,
|
||||||
|
// then the first 2 characters of "label" are italic red.
|
||||||
|
// the rest is white (using the Bar's style).
|
||||||
|
let cell = expected.get_mut(0, 0).set_fg(Color::Red);
|
||||||
|
cell.modifier.insert(Modifier::ITALIC);
|
||||||
|
let cell = expected.get_mut(1, 0).set_fg(Color::Red);
|
||||||
|
cell.modifier.insert(Modifier::ITALIC);
|
||||||
|
expected.get_mut(2, 0).set_fg(expected_color);
|
||||||
|
expected.get_mut(3, 0).set_fg(expected_color);
|
||||||
|
expected.get_mut(4, 0).set_fg(expected_color);
|
||||||
|
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_horizontal_bars_label_width_greater_than_bar_without_style() {
|
||||||
|
test_horizontal_bars_label_width_greater_than_bar(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_horizontal_bars_label_width_greater_than_bar_with_style() {
|
||||||
|
test_horizontal_bars_label_width_greater_than_bar(Some(Color::White))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_group_label_style() {
|
||||||
|
let chart: BarChart<'_> = BarChart::default()
|
||||||
|
.data(
|
||||||
|
BarGroup::default()
|
||||||
|
.label(Span::from("G1").red().into())
|
||||||
|
.bars(&[Bar::default().value(2)]),
|
||||||
|
)
|
||||||
|
.group_gap(1)
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.label_style(Style::default().bold().yellow());
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
|
||||||
|
// G1 should have the bold red style
|
||||||
|
// bold: because of BarChart::label_style
|
||||||
|
// red: is included with the label itself
|
||||||
|
let mut expected = Buffer::with_lines(vec!["2████", "G1 "]);
|
||||||
|
let cell = expected.get_mut(0, 1).set_fg(Color::Red);
|
||||||
|
cell.modifier.insert(Modifier::BOLD);
|
||||||
|
let cell = expected.get_mut(1, 1).set_fg(Color::Red);
|
||||||
|
cell.modifier.insert(Modifier::BOLD);
|
||||||
|
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_group_label_center() {
|
||||||
|
let chart: BarChart<'_> = BarChart::default().data(
|
||||||
|
BarGroup::default()
|
||||||
|
.label(Line::from(Span::from("G")).alignment(Alignment::Center))
|
||||||
|
.bars(&[Bar::default().value(2), Bar::default().value(5)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
|
||||||
|
let expected = Buffer::with_lines(vec![" █", "▆ █", " G "]);
|
||||||
|
assert_buffer_eq!(buffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_group_label_right() {
|
||||||
|
let chart: BarChart<'_> = BarChart::default().data(
|
||||||
|
BarGroup::default()
|
||||||
|
.label(Line::from(Span::from("G")).alignment(Alignment::Right))
|
||||||
|
.bars(&[Bar::default().value(2), Bar::default().value(5)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
||||||
|
chart.render(buffer.area, &mut buffer);
|
||||||
|
|
||||||
|
let expected = Buffer::with_lines(vec![" █", "▆ █", " G"]);
|
||||||
assert_buffer_eq!(buffer, expected);
|
assert_buffer_eq!(buffer, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ fn widgets_barchart_group() {
|
|||||||
"│ ▄▄▄▄ ████ ████ ████ ████│",
|
"│ ▄▄▄▄ ████ ████ ████ ████│",
|
||||||
"│▆10▆ 20M█ █50█ █40█ █60█ █90█│",
|
"│▆10▆ 20M█ █50█ █40█ █60█ █90█│",
|
||||||
"│ C1 C1 C2 C1 C2 │",
|
"│ C1 C1 C2 C1 C2 │",
|
||||||
"│ Mar │",
|
"│Mar │",
|
||||||
"└─────────────────────────────────┘",
|
"└─────────────────────────────────┘",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user