feat(chart): add GraphType::Bar (#1205)

![Demo](https://vhs.charm.sh/vhs-50v7I5n7lQF7tHCb1VCmFc.gif)
This commit is contained in:
Josh McKinney 2024-07-15 20:47:50 -07:00 committed by GitHub
parent 7bab9f0d80
commit 5b51018501
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 19 deletions

View file

@ -153,17 +153,18 @@ fn run_app<B: Backend>(
fn ui(frame: &mut Frame, app: &App) {
let area = frame.size();
let vertical = Layout::vertical([Constraint::Percentage(40), Constraint::Percentage(60)]);
let horizontal = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
let [chart1, bottom] = vertical.areas(area);
let [line_chart, scatter] = horizontal.areas(bottom);
let [top, bottom] = Layout::vertical([Constraint::Fill(1); 2]).areas(area);
let [animated_chart, bar_chart] =
Layout::horizontal([Constraint::Fill(1), Constraint::Length(29)]).areas(top);
let [line_chart, scatter] = Layout::horizontal([Constraint::Fill(1); 2]).areas(bottom);
render_chart1(frame, chart1, app);
render_animated_chart(frame, animated_chart, app);
render_barchart(frame, bar_chart);
render_line_chart(frame, line_chart);
render_scatter(frame, scatter);
}
fn render_chart1(f: &mut Frame, area: Rect, app: &App) {
fn render_animated_chart(f: &mut Frame, area: Rect, app: &App) {
let x_labels = vec![
Span::styled(
format!("{}", app.window[0]),
@ -189,7 +190,7 @@ fn render_chart1(f: &mut Frame, area: Rect, app: &App) {
];
let chart = Chart::new(datasets)
.block(Block::bordered().title("Chart 1".cyan().bold()))
.block(Block::bordered())
.x_axis(
Axis::default()
.title("X Axis")
@ -208,6 +209,51 @@ fn render_chart1(f: &mut Frame, area: Rect, app: &App) {
f.render_widget(chart, area);
}
fn render_barchart(frame: &mut Frame, bar_chart: Rect) {
let dataset = Dataset::default()
.marker(symbols::Marker::HalfBlock)
.style(Style::new().fg(Color::Blue))
.graph_type(GraphType::Bar)
// a bell curve
.data(&[
(0., 0.4),
(10., 2.9),
(20., 13.5),
(30., 41.1),
(40., 80.1),
(50., 100.0),
(60., 80.1),
(70., 41.1),
(80., 13.5),
(90., 2.9),
(100., 0.4),
]);
let chart = Chart::new(vec![dataset])
.block(
Block::bordered().title(
Title::default()
.content("Bar chart".cyan().bold())
.alignment(Alignment::Center),
),
)
.x_axis(
Axis::default()
.style(Style::default().gray())
.bounds([0.0, 100.0])
.labels(vec!["0".bold(), "50".into(), "100.0".bold()]),
)
.y_axis(
Axis::default()
.style(Style::default().gray())
.bounds([0.0, 100.0])
.labels(vec!["0".bold(), "50".into(), "100.0".bold()]),
)
.hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
frame.render_widget(chart, bar_chart);
}
fn render_line_chart(f: &mut Frame, area: Rect) {
let datasets = vec![Dataset::default()
.name("Line from only 2 points".italic())

View file

@ -144,11 +144,15 @@ pub enum GraphType {
/// Draw each point. This is the default.
#[default]
Scatter,
/// Draw a line between each following point.
///
/// The order of the lines will be the same as the order of the points in the dataset, which
/// allows this widget to draw lines both left-to-right and right-to-left
Line,
/// Draw a bar chart. This will draw a bar for each point in the dataset.
Bar,
}
/// Allow users to specify the position of a legend in a [`Chart`]
@ -362,9 +366,10 @@ impl<'a> Dataset<'a> {
/// Sets how the dataset should be drawn
///
/// [`Chart`] can draw either a [scatter](GraphType::Scatter) or [line](GraphType::Line) charts.
/// A scatter will draw only the points in the dataset while a line will also draw a line
/// between them. See [`GraphType`] for more details
/// [`Chart`] can draw [scatter](GraphType::Scatter), [line](GraphType::Line) or
/// [bar](GraphType::Bar) charts. A scatter chart draws only the points in the dataset, a line
/// char draws a line between each point, and a bar chart draws a line from the x axis to the
/// point. See [`GraphType`] for more details
///
/// This is a fluent setter method which must be chained or used as it consumes self
#[must_use = "method moves the value of self and returns the modified value"]
@ -998,16 +1003,30 @@ impl WidgetRef for Chart<'_> {
coords: dataset.data,
color: dataset.style.fg.unwrap_or(Color::Reset),
});
if dataset.graph_type == GraphType::Line {
for data in dataset.data.windows(2) {
ctx.draw(&CanvasLine {
x1: data[0].0,
y1: data[0].1,
x2: data[1].0,
y2: data[1].1,
color: dataset.style.fg.unwrap_or(Color::Reset),
});
match dataset.graph_type {
GraphType::Line => {
for data in dataset.data.windows(2) {
ctx.draw(&CanvasLine {
x1: data[0].0,
y1: data[0].1,
x2: data[1].0,
y2: data[1].1,
color: dataset.style.fg.unwrap_or(Color::Reset),
});
}
}
GraphType::Bar => {
for (x, y) in dataset.data {
ctx.draw(&CanvasLine {
x1: *x,
y1: 0.0,
x2: *x,
y2: *y,
color: dataset.style.fg.unwrap_or(Color::Reset),
});
}
}
GraphType::Scatter => {}
}
})
.render(graph_area, buf);
@ -1194,12 +1213,14 @@ mod tests {
fn graph_type_to_string() {
assert_eq!(GraphType::Scatter.to_string(), "Scatter");
assert_eq!(GraphType::Line.to_string(), "Line");
assert_eq!(GraphType::Bar.to_string(), "Bar");
}
#[test]
fn graph_type_from_str() {
assert_eq!("Scatter".parse::<GraphType>(), Ok(GraphType::Scatter));
assert_eq!("Line".parse::<GraphType>(), Ok(GraphType::Line));
assert_eq!("Bar".parse::<GraphType>(), Ok(GraphType::Bar));
assert_eq!("".parse::<GraphType>(), Err(ParseError::VariantNotFound));
}
@ -1460,4 +1481,39 @@ mod tests {
chart.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(expected));
}
#[test]
fn bar_chart() {
let data = [
(0.0, 0.0),
(2.0, 1.0),
(4.0, 4.0),
(6.0, 8.0),
(8.0, 9.0),
(10.0, 10.0),
];
let chart = Chart::new(vec![Dataset::default()
.data(&data)
.marker(symbols::Marker::Dot)
.graph_type(GraphType::Bar)])
.x_axis(Axis::default().bounds([0.0, 10.0]))
.y_axis(Axis::default().bounds([0.0, 10.0]));
let area = Rect::new(0, 0, 11, 11);
let mut buffer = Buffer::empty(area);
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines([
"",
" • •",
" • • •",
" • • •",
" • • •",
" • • •",
" • • • •",
" • • • •",
" • • • •",
" • • • • •",
"• • • • • •",
]);
assert_eq!(buffer, expected);
}
}