Skip to content

Commit

Permalink
feat(widgets/chart): add option to set the position of legend
Browse files Browse the repository at this point in the history
  • Loading branch information
lyuha committed Aug 14, 2023
1 parent 8c55158 commit 927c41a
Show file tree
Hide file tree
Showing 4 changed files with 897 additions and 23 deletions.
3 changes: 2 additions & 1 deletion examples/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
.style(Style::default().fg(Color::Gray))
.bounds([0.0, 5.0])
.labels(vec!["0".bold(), "2.5".into(), "5".bold()]),
);
)
.legend_position(LegendPosition::TopLeft);
f.render_widget(chart, chunks[2]);
}
151 changes: 131 additions & 20 deletions src/widgets/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,55 @@ pub enum GraphType {
Line,
}

#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]

Check warning on line 88 in src/widgets/chart.rs

View check run for this annotation

Codecov / codecov/patch

src/widgets/chart.rs#L88

Added line #L88 was not covered by tests
pub enum LegendPosition {
Top,
#[default]
TopRight,
TopLeft,
Left,
Right,
Bottom,
BottomRight,
BottomLeft,
}

impl LegendPosition {
fn layout(
&self,
area: Rect,
width: u16,
height: u16,
is_x_axis_title: bool,
is_y_axis_title: bool,
) -> Rect {
let (x, y) = match self {
Self::TopRight => (area.right() - width, area.top()),
Self::TopLeft => (
area.left(),
area.top() + if is_y_axis_title { 1 } else { 0 },
),
Self::Top => (area.left() + (area.width - width) / 2, area.top()),
Self::Left => (area.left(), area.top() + (area.height - height) / 2),
Self::Right => (
area.right() - width,
area.top() + (area.height - height) / 2,
), //(area.top() + height) / 2),
Self::Bottom => (
area.left() + (area.width - width) / 2,
area.bottom() - height,
),

Check warning on line 125 in src/widgets/chart.rs

View check run for this annotation

Codecov / codecov/patch

src/widgets/chart.rs#L116-L125

Added lines #L116 - L125 were not covered by tests
Self::BottomLeft => (area.left(), area.bottom() - height),
Self::BottomRight => (
area.right() - width,
area.bottom() - height - if is_x_axis_title { 1 } else { 0 },
),
};

Rect::new(x, y, width, height)
}
}

/// A group of data points
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Dataset<'a> {
Expand Down Expand Up @@ -202,6 +251,8 @@ pub struct Chart<'a> {
style: Style,
/// Constraints used to determine whether the legend should be shown or not
hidden_legend_constraints: (Constraint, Constraint),
/// The position of a legend
legend_position: LegendPosition,
}

impl<'a> Chart<'a> {
Expand All @@ -213,6 +264,7 @@ impl<'a> Chart<'a> {
style: Style::default(),
datasets,
hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)),
legend_position: LegendPosition::TopRight,
}
}

Expand Down Expand Up @@ -257,6 +309,20 @@ impl<'a> Chart<'a> {
self
}

/// Set the position of a legend.
///
/// # Examples
///
/// ```
/// # use ratatui::widgets::{Chart, LegendPosition};
/// let _chart: Chart = Chart::new(vec![])
/// .legend_position(LegendPosition::TopLeft);
/// ```
pub fn legend_position(mut self, position: LegendPosition) -> Chart<'a> {
self.legend_position = position;
self
}

/// Compute the internal layout of the chart given the area. If the area is too small some
/// elements may be automatically hidden
fn layout(&self, area: Rect) -> ChartLayout {
Expand Down Expand Up @@ -318,11 +384,12 @@ impl<'a> Chart<'a> {
&& legend_width < max_legend_width
&& legend_height < max_legend_height
{
layout.legend_area = Some(Rect::new(
layout.graph_area.right() - legend_width,
layout.graph_area.top(),
layout.legend_area = Some(self.legend_position.layout(
layout.graph_area,
legend_width,
legend_height,
self.x_axis.title.is_some(),
self.y_axis.title.is_some(),
));
}
}
Expand Down Expand Up @@ -542,24 +609,12 @@ impl<'a> Widget for Chart<'a> {
.render(graph_area, buf);
}

if let Some(legend_area) = layout.legend_area {
buf.set_style(legend_area, original_style);
Block::default()
.borders(Borders::ALL)
.render(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.style,
);
}
}

if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap();
let width = graph_area.right().saturating_sub(x);
let width = graph_area
.right()
.saturating_sub(x)
.min(title.width() as u16);
buf.set_style(
Rect {
x,
Expand All @@ -574,7 +629,10 @@ impl<'a> Widget for Chart<'a> {

if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap();
let width = graph_area.right().saturating_sub(x);
let width = graph_area
.right()
.saturating_sub(x)
.min(title.width() as u16);
buf.set_style(
Rect {
x,
Expand All @@ -586,6 +644,21 @@ impl<'a> Widget for Chart<'a> {
);
buf.set_line(x, y, &title, width);
}

if let Some(legend_area) = layout.legend_area {
buf.set_style(legend_area, original_style);
Block::default()
.borders(Borders::ALL)
.render(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.style,
);
}
}
}
}

Expand Down Expand Up @@ -702,4 +775,42 @@ mod tests {
.remove_modifier(Modifier::DIM)
)
}

#[test]
fn chart_with_legend_potition() {

Check warning on line 780 in src/widgets/chart.rs

View workflow job for this annotation

GitHub Actions / lint

"potition" should be "position".
let data = [(0.0, 0.0), (5.0, 5.0), (10.0, 10.0)];
let chart = Chart::new(vec![Dataset::default().name("Ds1").data(&data)])
.block(Block::default().borders(Borders::ALL))
.legend_position(LegendPosition::TopLeft);

let area = Rect::new(0, 0, 30, 20);
let mut buffer = Buffer::empty(area);

chart.render(buffer.area, &mut buffer);

let expected = Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│┌───┐ │",
"││Ds1│ │",
"│└───┘ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"│ │",
"└────────────────────────────┘",
]);

assert_eq!(buffer, expected);
}
}
2 changes: 1 addition & 1 deletion src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use bitflags::bitflags;
pub use self::{
barchart::{Bar, BarChart, BarGroup},
block::{Block, BorderType, Padding},
chart::{Axis, Chart, Dataset, GraphType},
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
clear::Clear,
gauge::{Gauge, LineGauge},
list::{List, ListItem, ListState},
Expand Down

0 comments on commit 927c41a

Please sign in to comment.