diff --git a/.github/rust.yml b/.github/rust.yml new file mode 100644 index 0000000..73f6473 --- /dev/null +++ b/.github/rust.yml @@ -0,0 +1,21 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Test + uses: icepuma/rust-action@master + with: + args: cargo fmt -- --check && cargo test \ No newline at end of file diff --git a/examples/area_series_chart.rs b/examples/area_series_chart.rs index 4434f58..fe0389f 100644 --- a/examples/area_series_chart.rs +++ b/examples/area_series_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, ScaleLinear, MarkerType, PointLabelPosition, AreaSeriesView}; +use charts::{AreaSeriesView, Chart, MarkerType, PointLabelPosition, ScaleLinear}; fn main() { // Define chart related sizes. @@ -30,7 +30,8 @@ fn main() { .set_y_scale(&y) .set_marker_type(MarkerType::Circle) .set_label_position(PointLabelPosition::N) - .load_data(&area_data).unwrap(); + .load_data(&area_data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -43,5 +44,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Custom Y Axis Label") .add_bottom_axis_label("Custom X Axis Label") - .save("area-chart.svg").unwrap(); + .save("area-chart.svg") + .unwrap(); } diff --git a/examples/composite_bar_and_scatter_chart.rs b/examples/composite_bar_and_scatter_chart.rs index 27a301b..9d19875 100644 --- a/examples/composite_bar_and_scatter_chart.rs +++ b/examples/composite_bar_and_scatter_chart.rs @@ -1,4 +1,7 @@ -use charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear, ScatterView, MarkerType, Color, PointLabelPosition}; +use charts::{ + Chart, Color, MarkerType, PointLabelPosition, ScaleBand, ScaleLinear, ScatterView, + VerticalBarView, +}; fn main() { // Define chart related sizes. @@ -9,7 +12,11 @@ fn main() { // Create a band scale that maps ["A", "B", "C"] categories to values in the [0, availableWidth] // range (the width of the chart without the margins). let x = ScaleBand::new() - .set_domain(vec![String::from("A"), String::from("B"), String::from("C")]) + .set_domain(vec![ + String::from("A"), + String::from("B"), + String::from("C"), + ]) .set_range(vec![0, width - left - right]); // Create a linear scale that will interpolate values in [0, 100] range to corresponding @@ -25,13 +32,18 @@ fn main() { let bar_data = vec![("A", 70), ("B", 10), ("C", 30)]; // You can use your own iterable as data as long as its items implement the `PointDatum` trait. - let scatter_data = vec![(String::from("A"), 90.3), (String::from("B"), 20.1), (String::from("C"), 10.8)]; + let scatter_data = vec![ + (String::from("A"), 90.3), + (String::from("B"), 20.1), + (String::from("C"), 10.8), + ]; // Create VerticalBar view that is going to represent the data as vertical bars. let bar_view = VerticalBarView::new() .set_x_scale(&x) .set_y_scale(&y) - .load_data(&bar_data).unwrap(); + .load_data(&bar_data) + .unwrap(); // Create Scatter view that is going to represent the data as points. let scatter_view = ScatterView::new() @@ -40,7 +52,8 @@ fn main() { .set_label_position(PointLabelPosition::NE) .set_marker_type(MarkerType::Circle) .set_colors(Color::from_vec_of_hex_strings(vec!["#FF4700"])) - .load_data(&scatter_data).unwrap(); + .load_data(&scatter_data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -48,11 +61,12 @@ fn main() { .set_height(height) .set_margins(top, right, bottom, left) .add_title(String::from("Composite Bar + Scatter Chart")) - .add_view(&bar_view) // <-- add bar view - .add_view(&scatter_view) // <-- add scatter view + .add_view(&bar_view) // <-- add bar view + .add_view(&scatter_view) // <-- add scatter view .add_axis_bottom(&x) .add_axis_left(&y) .add_left_axis_label("Units of Measurement") .add_bottom_axis_label("Categories") - .save("composite-bar-and-scatter-chart.svg").unwrap(); -} \ No newline at end of file + .save("composite-bar-and-scatter-chart.svg") + .unwrap(); +} diff --git a/examples/horizontal_bar_chart.rs b/examples/horizontal_bar_chart.rs index 1c93973..a2bf760 100644 --- a/examples/horizontal_bar_chart.rs +++ b/examples/horizontal_bar_chart.rs @@ -15,7 +15,11 @@ fn main() { // Create a band scale that maps ["A", "B", "C"] categories to values in the [0, availableHeight] // range (the height of the chart without the margins). let y = ScaleBand::new() - .set_domain(vec![String::from("A"), String::from("B"), String::from("C")]) + .set_domain(vec![ + String::from("A"), + String::from("B"), + String::from("C"), + ]) .set_range(vec![0, height - top - bottom]); // You can use your own iterable as data as long as its items implement the `BarDatum` trait. @@ -25,7 +29,8 @@ fn main() { let view = HorizontalBarView::new() .set_x_scale(&x) .set_y_scale(&y) - .load_data(&data).unwrap(); + .load_data(&data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -39,5 +44,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Y Axis Custom Label") .add_bottom_axis_label("X Axis Custom Label") - .save("horizontal-bar-chart.svg").unwrap(); -} \ No newline at end of file + .save("horizontal-bar-chart.svg") + .unwrap(); +} diff --git a/examples/line_series_chart.rs b/examples/line_series_chart.rs index e11da44..36081ae 100644 --- a/examples/line_series_chart.rs +++ b/examples/line_series_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, ScaleLinear, MarkerType, PointLabelPosition, LineSeriesView}; +use charts::{Chart, LineSeriesView, MarkerType, PointLabelPosition, ScaleLinear}; fn main() { // Define chart related sizes. @@ -30,7 +30,8 @@ fn main() { .set_y_scale(&y) .set_marker_type(MarkerType::Circle) .set_label_position(PointLabelPosition::N) - .load_data(&line_data).unwrap(); + .load_data(&line_data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -43,5 +44,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Custom Y Axis Label") .add_bottom_axis_label("Custom X Axis Label") - .save("line-chart.svg").unwrap(); + .save("line-chart.svg") + .unwrap(); } diff --git a/examples/scatter_chart.rs b/examples/scatter_chart.rs index f7f2290..2fe2553 100644 --- a/examples/scatter_chart.rs +++ b/examples/scatter_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, ScaleLinear, ScatterView, MarkerType, PointLabelPosition}; +use charts::{Chart, MarkerType, PointLabelPosition, ScaleLinear, ScatterView}; fn main() { // Define chart related sizes. @@ -30,7 +30,8 @@ fn main() { .set_y_scale(&y) .set_label_position(PointLabelPosition::E) .set_marker_type(MarkerType::Square) - .load_data(&scatter_data).unwrap(); + .load_data(&scatter_data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -43,5 +44,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Custom X Axis Label") .add_bottom_axis_label("Custom Y Axis Label") - .save("scatter-chart.svg").unwrap(); -} \ No newline at end of file + .save("scatter-chart.svg") + .unwrap(); +} diff --git a/examples/scatter_chart_multiple_keys.rs b/examples/scatter_chart_multiple_keys.rs index 6ad8547..f724f4b 100644 --- a/examples/scatter_chart_multiple_keys.rs +++ b/examples/scatter_chart_multiple_keys.rs @@ -1,4 +1,4 @@ -use charts::{Chart, ScaleLinear, ScatterView, MarkerType, Color, PointLabelPosition}; +use charts::{Chart, Color, MarkerType, PointLabelPosition, ScaleLinear, ScatterView}; fn main() { // Define chart related sizes. @@ -22,7 +22,12 @@ fn main() { .set_range(vec![height - top - bottom, 0]); // You can use your own iterable as data as long as its items implement the `PointDatum` trait. - let scatter_data = vec![(120, 90, "foo"), (12, 54, "foo"), (100, 40, "bar"), (180, 10, "baz")]; + let scatter_data = vec![ + (120, 90, "foo"), + (12, 54, "foo"), + (100, 40, "bar"), + (180, 10, "baz"), + ]; // Create Scatter view that is going to represent the data as points. let scatter_view = ScatterView::new() @@ -31,7 +36,8 @@ fn main() { .set_label_position(PointLabelPosition::E) .set_marker_type(MarkerType::Circle) .set_colors(Color::color_scheme_dark()) - .load_data(&scatter_data).unwrap(); + .load_data(&scatter_data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -44,5 +50,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Custom X Axis Label") .add_bottom_axis_label("Custom Y Axis Label") - .save("scatter-chart-multiple-keys.svg").unwrap(); -} \ No newline at end of file + .save("scatter-chart-multiple-keys.svg") + .unwrap(); +} diff --git a/examples/scatter_chart_two_datasets.rs b/examples/scatter_chart_two_datasets.rs index 2c7051c..0ff5fef 100644 --- a/examples/scatter_chart_two_datasets.rs +++ b/examples/scatter_chart_two_datasets.rs @@ -1,4 +1,6 @@ -use charts::{Chart, ScaleLinear, ScatterView, MarkerType, PointLabelPosition, Color, AxisPosition}; +use charts::{ + AxisPosition, Chart, Color, MarkerType, PointLabelPosition, ScaleLinear, ScatterView, +}; fn main() { // Define chart related sizes. @@ -32,7 +34,8 @@ fn main() { .set_marker_type(MarkerType::Circle) .set_label_position(PointLabelPosition::N) .set_custom_data_label("Apples".to_owned()) - .load_data(&scatter_data_1).unwrap(); + .load_data(&scatter_data_1) + .unwrap(); // Create Scatter view that is going to represent the data as points. let scatter_view_2 = ScatterView::new() @@ -42,7 +45,8 @@ fn main() { .set_label_position(PointLabelPosition::N) .set_custom_data_label("Oranges".to_owned()) .set_colors(Color::from_vec_of_hex_strings(vec!["#aa0000"])) - .load_data(&scatter_data_2).unwrap(); + .load_data(&scatter_data_2) + .unwrap(); // Generate and save the chart. Chart::new() @@ -57,5 +61,6 @@ fn main() { .add_left_axis_label("Custom X Axis Label") .add_bottom_axis_label("Custom Y Axis Label") .add_legend_at(AxisPosition::Bottom) - .save("scatter-chart-two-datasets.svg").unwrap(); -} \ No newline at end of file + .save("scatter-chart-two-datasets.svg") + .unwrap(); +} diff --git a/examples/stacked_horizontal_bar_chart.rs b/examples/stacked_horizontal_bar_chart.rs index 224e3b4..98fcf94 100644 --- a/examples/stacked_horizontal_bar_chart.rs +++ b/examples/stacked_horizontal_bar_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, HorizontalBarView, ScaleBand, ScaleLinear, BarLabelPosition}; +use charts::{BarLabelPosition, Chart, HorizontalBarView, ScaleBand, ScaleLinear}; fn main() { // Define chart related sizes. @@ -15,18 +15,29 @@ fn main() { // Create a band scale that maps ["A", "B", "C"] categories to values in the [0, availableHeight] // range (the height of the chart without the margins). let y = ScaleBand::new() - .set_domain(vec![String::from("A"), String::from("B"), String::from("C")]) + .set_domain(vec![ + String::from("A"), + String::from("B"), + String::from("C"), + ]) .set_range(vec![0, height - top - bottom]); // You can use your own iterable as data as long as its items implement the `BarDatum` trait. - let data = vec![("A", 70, "foo"), ("B", 10, "foo"), ("C", 30, "foo"), ("A", 20, "bar"), ("A", 5, "baz")]; + let data = vec![ + ("A", 70, "foo"), + ("B", 10, "foo"), + ("C", 30, "foo"), + ("A", 20, "bar"), + ("A", 5, "baz"), + ]; // Create VerticalBar view that is going to represent the data as vertical bars. let view = HorizontalBarView::new() .set_x_scale(&x) .set_y_scale(&y) .set_label_position(BarLabelPosition::Center) - .load_data(&data).unwrap(); + .load_data(&data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -39,5 +50,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Y Axis Custom Label") .add_bottom_axis_label("X Axis Custom Label") - .save("stacked-horizontal-bar-chart.svg").unwrap(); -} \ No newline at end of file + .save("stacked-horizontal-bar-chart.svg") + .unwrap(); +} diff --git a/examples/stacked_vertical_bar_chart.rs b/examples/stacked_vertical_bar_chart.rs index 60160ec..c789fe3 100644 --- a/examples/stacked_vertical_bar_chart.rs +++ b/examples/stacked_vertical_bar_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear, BarLabelPosition}; +use charts::{BarLabelPosition, Chart, ScaleBand, ScaleLinear, VerticalBarView}; fn main() { // Define chart related sizes. @@ -9,7 +9,11 @@ fn main() { // Create a band scale that maps ["A", "B", "C"] categories to values in [0, availableWidth] // range (the width of the chart without the margins). let x = ScaleBand::new() - .set_domain(vec![String::from("A"), String::from("B"), String::from("C")]) + .set_domain(vec![ + String::from("A"), + String::from("B"), + String::from("C"), + ]) .set_range(vec![0, width - left - right]); // Create a linear scale that will interpolate values in [0, 100] range to corresponding @@ -22,7 +26,13 @@ fn main() { .set_range(vec![height - top - bottom, 0]); // You can use your own iterable as data as long as its items implement the `BarDatum` trait. - let data = vec![("A", 70, "foo"), ("B", 10, "foo"), ("C", 30, "foo"), ("A", 20, "bar"), ("A", 5, "baz")]; + let data = vec![ + ("A", 70, "foo"), + ("B", 10, "foo"), + ("C", 30, "foo"), + ("A", 20, "bar"), + ("A", 5, "baz"), + ]; // Create VerticalBar view that is going to represent the data as vertical bars. let view = VerticalBarView::new() @@ -30,7 +40,8 @@ fn main() { .set_y_scale(&y) // .set_label_visibility(false) // <-- uncomment this line to hide bar value labels .set_label_position(BarLabelPosition::Center) - .load_data(&data).unwrap(); + .load_data(&data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -43,5 +54,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Units of Measurement") .add_bottom_axis_label("Categories") - .save("stacked-vertical-bar-chart.svg").unwrap(); -} \ No newline at end of file + .save("stacked-vertical-bar-chart.svg") + .unwrap(); +} diff --git a/examples/vertical_bar_chart.rs b/examples/vertical_bar_chart.rs index 10a31e3..d4e6322 100644 --- a/examples/vertical_bar_chart.rs +++ b/examples/vertical_bar_chart.rs @@ -1,4 +1,4 @@ -use charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear}; +use charts::{Chart, ScaleBand, ScaleLinear, VerticalBarView}; fn main() { // Define chart related sizes. @@ -9,7 +9,11 @@ fn main() { // Create a band scale that maps ["A", "B", "C"] categories to values in the [0, availableWidth] // range (the width of the chart without the margins). let x = ScaleBand::new() - .set_domain(vec![String::from("A"), String::from("B"), String::from("C")]) + .set_domain(vec![ + String::from("A"), + String::from("B"), + String::from("C"), + ]) .set_range(vec![0, width - left - right]) .set_inner_padding(0.1) .set_outer_padding(0.1); @@ -30,7 +34,8 @@ fn main() { let view = VerticalBarView::new() .set_x_scale(&x) .set_y_scale(&y) - .load_data(&data).unwrap(); + .load_data(&data) + .unwrap(); // Generate and save the chart. Chart::new() @@ -43,5 +48,6 @@ fn main() { .add_axis_left(&y) .add_left_axis_label("Units of Measurement") .add_bottom_axis_label("Categories") - .save("vertical-bar-chart.svg").unwrap(); -} \ No newline at end of file + .save("vertical-bar-chart.svg") + .unwrap(); +} diff --git a/src/axis.rs b/src/axis.rs index 5338d6a..34532a8 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -1,12 +1,12 @@ +use crate::components::axis::{AxisLine, AxisTick}; +use crate::scales::ScaleType; +use crate::{Chart, Scale}; use std::string::ToString; use svg::node::element::Group; +use svg::node::element::Text; +use svg::node::Text as TextNode; use svg::parser::Error; use svg::Node; -use svg::node::Text as TextNode; -use svg::node::element::Text; -use crate::{Scale, Chart}; -use crate::components::axis::{AxisLine, AxisTick}; -use crate::scales::ScaleType; /// Enum of possible axis positions on the chart. #[derive(Copy, Clone, PartialEq)] @@ -30,7 +30,11 @@ pub struct Axis { impl Axis { /// Create a new instance of an axis for a chart based on the provided scale and position. - fn new<'a, T: ToString>(scale: &'a dyn Scale, position: AxisPosition, chart: &Chart<'a>) -> Self { + fn new<'a, T: ToString>( + scale: &'a dyn Scale, + position: AxisPosition, + chart: &Chart<'a>, + ) -> Self { Self { ticks: Self::generate_ticks(scale, position), position, @@ -70,14 +74,18 @@ impl Axis { /// Set tick label rotation. pub fn set_tick_label_rotation(&mut self, rotation: isize) { self.label_rotation = rotation; - self.ticks.iter_mut().for_each(|tick| tick.set_label_rotation(rotation)); + self.ticks + .iter_mut() + .for_each(|tick| tick.set_label_rotation(rotation)); } /// Set the label format. pub fn set_tick_label_format(&mut self, format: &str) { self.label_format = String::from(format); let label_format = self.label_format.as_str(); - self.ticks.iter_mut().for_each(|tick| tick.set_label_format(label_format)); + self.ticks + .iter_mut() + .for_each(|tick| tick.set_label_format(label_format)); } /// Return whether the axis has a label or not. @@ -134,7 +142,10 @@ impl Axis { } /// Generate ticks for the axis based on the scale and position. - fn generate_ticks<'a, T: ToString>(scale: &'a dyn Scale, position: AxisPosition) -> Vec { + fn generate_ticks<'a, T: ToString>( + scale: &'a dyn Scale, + position: AxisPosition, + ) -> Vec { let mut ticks = Vec::new(); let label_offset = { if position == AxisPosition::Top || position == AxisPosition::Bottom { @@ -146,13 +157,21 @@ impl Axis { for tick in scale.get_ticks() { let tick_offset = match position { - AxisPosition::Bottom if scale.get_type() == ScaleType::Band => scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32, + AxisPosition::Bottom if scale.get_type() == ScaleType::Band => { + scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32 + } AxisPosition::Bottom => scale.scale(&tick), - AxisPosition::Left if scale.get_type() == ScaleType::Band => scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32, + AxisPosition::Left if scale.get_type() == ScaleType::Band => { + scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32 + } AxisPosition::Left => scale.scale(&tick), - AxisPosition::Top if scale.get_type() == ScaleType::Band => scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32, + AxisPosition::Top if scale.get_type() == ScaleType::Band => { + scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32 + } AxisPosition::Top => scale.scale(&tick), - AxisPosition::Right if scale.get_type() == ScaleType::Band => scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32, + AxisPosition::Right if scale.get_type() == ScaleType::Band => { + scale.scale(&tick) + scale.bandwidth().unwrap() / 2_f32 + } AxisPosition::Right => scale.scale(&tick), }; let axis_tick = AxisTick::new(tick_offset, label_offset, 0, tick.to_string(), position); @@ -166,9 +185,15 @@ impl Axis { fn get_axis_line<'a>(position: AxisPosition, chart: &Chart<'a>) -> AxisLine { match position { AxisPosition::Top => AxisLine::new(0_f32, 0_f32, chart.get_view_width() as f32, 0_f32), - AxisPosition::Right => AxisLine::new(0_f32, 0_f32, 0_f32, chart.get_view_height() as f32), - AxisPosition::Bottom => AxisLine::new(0_f32, 0_f32, chart.get_view_width() as f32, 0_f32), - AxisPosition::Left => AxisLine::new(0_f32, 0_f32, 0_f32, chart.get_view_height() as f32), + AxisPosition::Right => { + AxisLine::new(0_f32, 0_f32, 0_f32, chart.get_view_height() as f32) + } + AxisPosition::Bottom => { + AxisLine::new(0_f32, 0_f32, chart.get_view_width() as f32, 0_f32) + } + AxisPosition::Left => { + AxisLine::new(0_f32, 0_f32, 0_f32, chart.get_view_height() as f32) + } } } } diff --git a/src/chart.rs b/src/chart.rs index 724a650..b97c65f 100644 --- a/src/chart.rs +++ b/src/chart.rs @@ -1,16 +1,16 @@ -use std::string::ToString; +use crate::axis::AxisPosition; +use crate::components::legend::LegendEntry; +use crate::legend::Legend; +use crate::views::View; +use crate::{Axis, Scale}; use std::ffi::OsStr; use std::path::Path; +use std::string::ToString; use svg; use svg::node::element::Group; -use svg::Node; -use svg::node::Text as TextNode; use svg::node::element::Text; -use crate::{Axis, Scale}; -use crate::views::View; -use crate::axis::AxisPosition; -use crate::legend::Legend; -use crate::components::legend::LegendEntry; +use svg::node::Text as TextNode; +use svg::Node; /// Define the orientation enum to aid in rendering and business logic. #[derive(Debug, Copy, Clone, PartialEq)] @@ -204,7 +204,7 @@ impl<'a> Chart<'a> { pub fn set_bottom_axis_tick_label_rotation(mut self, rotation: isize) -> Self { match &mut self.x_axis_bottom { Some(axis) => axis.set_tick_label_rotation(rotation), - None => {}, + None => {} } self } @@ -213,7 +213,7 @@ impl<'a> Chart<'a> { pub fn set_top_axis_tick_label_rotation(mut self, rotation: isize) -> Self { match &mut self.x_axis_top { Some(axis) => axis.set_tick_label_rotation(rotation), - None => {}, + None => {} } self } @@ -222,7 +222,7 @@ impl<'a> Chart<'a> { pub fn set_left_axis_tick_label_rotation(mut self, rotation: isize) -> Self { match &mut self.y_axis_left { Some(axis) => axis.set_tick_label_rotation(rotation), - None => {}, + None => {} } self } @@ -231,7 +231,7 @@ impl<'a> Chart<'a> { pub fn set_right_axis_tick_label_rotation(mut self, rotation: isize) -> Self { match &mut self.y_axis_right { Some(axis) => axis.set_tick_label_rotation(rotation), - None => {}, + None => {} } self } @@ -240,7 +240,7 @@ impl<'a> Chart<'a> { pub fn set_left_axis_tick_label_format(mut self, format: &str) -> Self { match &mut self.y_axis_left { Some(axis) => axis.set_tick_label_format(format), - None => {}, + None => {} } self } @@ -249,7 +249,7 @@ impl<'a> Chart<'a> { pub fn set_right_axis_tick_label_format(mut self, format: &str) -> Self { match &mut self.y_axis_right { Some(axis) => axis.set_tick_label_format(format), - None => {}, + None => {} } self } @@ -258,7 +258,7 @@ impl<'a> Chart<'a> { pub fn set_top_axis_tick_label_format(mut self, format: &str) -> Self { match &mut self.x_axis_top { Some(axis) => axis.set_tick_label_format(format), - None => {}, + None => {} } self } @@ -267,61 +267,82 @@ impl<'a> Chart<'a> { pub fn set_bottom_axis_tick_label_format(mut self, format: &str) -> Self { match &mut self.x_axis_bottom { Some(axis) => axis.set_tick_label_format(format), - None => {}, + None => {} } self } /// Generate the SVG for the chart and its components. fn to_svg(&self) -> Result { - let mut group = Group::new() - .set("class", "g-chart"); + let mut group = Group::new().set("class", "g-chart"); // Add chart title if self.title.len() > 0 { let title_group = Group::new() .set("class", "g-title") .set("transform", format!("translate({},{})", self.width / 2, 25)) - .add(Text::new() - .set("x", 0) - .set("y", 0) - .set("dy", ".35em") - .set("fill", "#777") - .set("text-anchor", "middle") - .set("font-size", "24px") - .set("font-family", "sans-serif") - .add(TextNode::new(&self.title)) + .add( + Text::new() + .set("x", 0) + .set("y", 0) + .set("dy", ".35em") + .set("fill", "#777") + .set("text-anchor", "middle") + .set("font-size", "24px") + .set("font-family", "sans-serif") + .add(TextNode::new(&self.title)), ); group.append(title_group); } if let Some(ref axis) = self.x_axis_top { let mut axis_group = axis.to_svg().unwrap(); - axis_group.assign("transform", format!("translate({},{})", self.margin_left, self.margin_top)); + axis_group.assign( + "transform", + format!("translate({},{})", self.margin_left, self.margin_top), + ); group.append(axis_group); }; if let Some(ref axis) = self.x_axis_bottom { let mut axis_group = axis.to_svg().unwrap(); - axis_group.assign("transform", format!("translate({},{})", self.margin_left, self.height - self.margin_bottom)); + axis_group.assign( + "transform", + format!( + "translate({},{})", + self.margin_left, + self.height - self.margin_bottom + ), + ); group.append(axis_group); }; if let Some(ref axis) = self.y_axis_left { let mut axis_group = axis.to_svg().unwrap(); - axis_group.assign("transform", format!("translate({},{})", self.margin_left, self.margin_top)); + axis_group.assign( + "transform", + format!("translate({},{})", self.margin_left, self.margin_top), + ); group.append(axis_group); }; if let Some(ref axis) = self.y_axis_right { let mut axis_group = axis.to_svg().unwrap(); - axis_group.assign("transform", format!("translate({},{})", self.width - self.margin_right, self.margin_top)); + axis_group.assign( + "transform", + format!( + "translate({},{})", + self.width - self.margin_right, + self.margin_top + ), + ); group.append(axis_group); }; - let mut view_group = Group::new() - .set("class", "g-view") - .set("transform", format!("translate({},{})", self.margin_left, self.margin_top)); + let mut view_group = Group::new().set("class", "g-view").set( + "transform", + format!("translate({},{})", self.margin_left, self.margin_top), + ); for view in self.views.iter() { view_group.append(view.to_svg()?); @@ -345,7 +366,7 @@ impl<'a> Chart<'a> { width = self.width - self.margin_right - self.margin_left; x_offset = self.margin_left; y_offset = axis_height; - }, + } AxisPosition::Bottom => { // Compute the height of the bottom axis that should serve // as an offset for the legend. @@ -366,7 +387,7 @@ impl<'a> Chart<'a> { width = self.width - self.margin_right - self.margin_left; x_offset = self.margin_left; y_offset = self.height - self.margin_bottom + axis_height; - }, + } AxisPosition::Left => { let axis_width = { if let Some(ref axis) = self.y_axis_left { @@ -382,7 +403,7 @@ impl<'a> Chart<'a> { width = self.margin_left - axis_width - 10; // 10 is described in the comment below x_offset = 10; // always have a 10px padding from the left of the chart y_offset = self.margin_top; - }, + } AxisPosition::Right => { let axis_width = { if let Some(ref axis) = self.y_axis_right { @@ -401,7 +422,12 @@ impl<'a> Chart<'a> { } }; - let legend_entries = self.views.iter().map(|view| view.get_legend_entries()).flatten().collect::>(); + let legend_entries = self + .views + .iter() + .map(|view| view.get_legend_entries()) + .flatten() + .collect::>(); let legend = Legend::new(legend_entries, width as usize); let mut legend_group = legend.to_svg()?; legend_group.assign("transform", format!("translate({},{})", x_offset, y_offset)); @@ -413,25 +439,29 @@ impl<'a> Chart<'a> { } /// Save the chart to a file - pub fn save

(self, path: P) -> Result<(), String> where - P: AsRef + pub fn save

(self, path: P) -> Result<(), String> + where + P: AsRef, { match path.as_ref().extension().and_then(OsStr::to_str) { - Some("svg") => { - match self.to_svg() { - Ok(svg_content) => { - let document = svg::Document::new() - .set("width", self.width) - .set("height", self.height) - .set("viewBox", (0, 0, self.width, self.height)) - .add(svg_content); - - svg::save(path, &document).unwrap() - }, - Err(e) => return Err(format!("Encountered an error while saving the chart: {:?}", e)), + Some("svg") => match self.to_svg() { + Ok(svg_content) => { + let document = svg::Document::new() + .set("width", self.width) + .set("height", self.height) + .set("viewBox", (0, 0, self.width, self.height)) + .add(svg_content); + + svg::save(path, &document).unwrap() + } + Err(e) => { + return Err(format!( + "Encountered an error while saving the chart: {:?}", + e + )) } }, - _ => {}, + _ => {} }; Ok(()) } diff --git a/src/colors/mod.rs b/src/colors/mod.rs index 88902c6..2fda17e 100644 --- a/src/colors/mod.rs +++ b/src/colors/mod.rs @@ -11,7 +11,9 @@ impl Color { let mut colors = Vec::new(); for color in color_strings.iter() { - colors.push(Color { hex: String::from(*color) }) + colors.push(Color { + hex: String::from(*color), + }) } colors @@ -19,49 +21,105 @@ impl Color { /// Generate a color scheme made of 10 colors. pub fn color_scheme_10() -> Vec { - vec!( - Color { hex: "#1f77b4".to_string() }, - Color { hex: "#ff7f0e".to_string() }, - Color { hex: "#2ca02c".to_string() }, - Color { hex: "#d62728".to_string() }, - Color { hex: "#9467bd".to_string() }, - Color { hex: "#8c564b".to_string() }, - Color { hex: "#e377c2".to_string() }, - Color { hex: "#7f7f7f".to_string() }, - Color { hex: "#bcbd22".to_string() }, - Color { hex: "#17becf".to_string() }, - ) + vec![ + Color { + hex: "#1f77b4".to_string(), + }, + Color { + hex: "#ff7f0e".to_string(), + }, + Color { + hex: "#2ca02c".to_string(), + }, + Color { + hex: "#d62728".to_string(), + }, + Color { + hex: "#9467bd".to_string(), + }, + Color { + hex: "#8c564b".to_string(), + }, + Color { + hex: "#e377c2".to_string(), + }, + Color { + hex: "#7f7f7f".to_string(), + }, + Color { + hex: "#bcbd22".to_string(), + }, + Color { + hex: "#17becf".to_string(), + }, + ] } /// An array of ten categorical colors authored by Tableau as part of /// [Tableau 10](https://www.tableau.com/about/blog/2016/7/colors-upgrade-tableau-10-56782). pub fn color_scheme_tableau_10() -> Vec { - vec!( - Color { hex: "#4e79a7".to_string() }, - Color { hex: "#f28e2c".to_string() }, - Color { hex: "#e15759".to_string() }, - Color { hex: "#76b7b2".to_string() }, - Color { hex: "#59a14f".to_string() }, - Color { hex: "#edc949".to_string() }, - Color { hex: "#af7aa1".to_string() }, - Color { hex: "#ff9da7".to_string() }, - Color { hex: "#9c755f".to_string() }, - Color { hex: "#bab0ab".to_string() }, - ) + vec![ + Color { + hex: "#4e79a7".to_string(), + }, + Color { + hex: "#f28e2c".to_string(), + }, + Color { + hex: "#e15759".to_string(), + }, + Color { + hex: "#76b7b2".to_string(), + }, + Color { + hex: "#59a14f".to_string(), + }, + Color { + hex: "#edc949".to_string(), + }, + Color { + hex: "#af7aa1".to_string(), + }, + Color { + hex: "#ff9da7".to_string(), + }, + Color { + hex: "#9c755f".to_string(), + }, + Color { + hex: "#bab0ab".to_string(), + }, + ] } /// An array of eight categorical colors pub fn color_scheme_dark() -> Vec { - vec!( - Color { hex: "#1b9e77".to_string() }, - Color { hex: "#d95f02".to_string() }, - Color { hex: "#7570b3".to_string() }, - Color { hex: "#e7298a".to_string() }, - Color { hex: "#66a61e".to_string() }, - Color { hex: "#e6ab02".to_string() }, - Color { hex: "#a6761d".to_string() }, - Color { hex: "#666666".to_string() }, - ) + vec![ + Color { + hex: "#1b9e77".to_string(), + }, + Color { + hex: "#d95f02".to_string(), + }, + Color { + hex: "#7570b3".to_string(), + }, + Color { + hex: "#e7298a".to_string(), + }, + Color { + hex: "#66a61e".to_string(), + }, + Color { + hex: "#e6ab02".to_string(), + }, + Color { + hex: "#a6761d".to_string(), + }, + Color { + hex: "#666666".to_string(), + }, + ] } /// Represent a color as a hex string. diff --git a/src/components/area.rs b/src/components/area.rs index cd57339..598e7f6 100644 --- a/src/components/area.rs +++ b/src/components/area.rs @@ -1,9 +1,9 @@ +use crate::components::scatter::ScatterPoint; +use crate::components::DatumRepresentation; use std::fmt::Display; -use svg::node::element::{Group, Path}; use svg::node::element::path::Data; +use svg::node::element::{Group, Path}; use svg::node::Node; -use crate::components::DatumRepresentation; -use crate::components::scatter::ScatterPoint; /// Represents a point in a scatter plot. #[derive(Debug)] @@ -13,22 +13,14 @@ pub struct AreaSeries { } impl AreaSeries { - pub fn new( - points: Vec>, - color: String - ) -> Self { - Self { - points, - color, - } + pub fn new(points: Vec>, color: String) -> Self { + Self { points, color } } } impl DatumRepresentation for AreaSeries { - fn to_svg(&self) -> Result { - let mut group = Group::new() - .set("class", "line"); + let mut group = Group::new().set("class", "line"); let mut data = Data::new(); diff --git a/src/components/axis.rs b/src/components/axis.rs index 7637746..940935d 100644 --- a/src/components/axis.rs +++ b/src/components/axis.rs @@ -1,9 +1,9 @@ +use crate::axis::AxisPosition; +use format_num::NumberFormat; +use svg::node::element::Text; use svg::node::element::{Group, Line}; use svg::node::Text as TextNode; -use svg::node::element::Text; use svg::Node; -use format_num::NumberFormat; -use crate::axis::AxisPosition; /// A simple struct that represents an axis line. pub(crate) struct AxisLine { @@ -41,12 +41,18 @@ pub struct AxisTick { label_rotation: isize, tick_offset: f32, label: String, - label_format: Option + label_format: Option, } impl AxisTick { /// Create a new instance of AxisTick. - pub fn new(tick_offset: f32, label_offset: usize, label_rotation: isize, label: String, axis_position: AxisPosition) -> Self { + pub fn new( + tick_offset: f32, + label_offset: usize, + label_rotation: isize, + label: String, + axis_position: AxisPosition, + ) -> Self { Self { label_offset, tick_offset, @@ -71,7 +77,12 @@ impl AxisTick { pub fn to_svg(&self) -> Result { let formatted_label = if self.label_format.is_some() { let formatter = NumberFormat::new(); - formatter.format(self.label_format.as_ref().unwrap(), self.label.parse::().unwrap()).replace('G', "B") + formatter + .format( + self.label_format.as_ref().unwrap(), + self.label.parse::().unwrap(), + ) + .replace('G', "B") } else { self.label.to_owned() }; @@ -86,30 +97,31 @@ impl AxisTick { tick_line_p2 = (-6, 0); tick_label_offset = (-(self.label_offset as isize), 0); tick_label_text_anchor = "end"; - }, + } AxisPosition::Bottom => { offsets = (self.tick_offset, 0_f32); tick_line_p2 = (0, 6); tick_label_offset = (0, self.label_offset as isize); tick_label_text_anchor = "middle"; - }, + } AxisPosition::Right => { offsets = (0_f32, self.tick_offset); tick_line_p2 = (6, 0); tick_label_offset = (self.label_offset as isize, 0); tick_label_text_anchor = "start"; - }, + } AxisPosition::Top => { offsets = (self.tick_offset, 0_f32); tick_line_p2 = (0, -6); tick_label_offset = (0, -(self.label_offset as isize)); tick_label_text_anchor = "middle"; - }, + } }; - let mut group = Group::new() - .set("class", "tick") - .set("transform", format!("translate({},{})", offsets.0, offsets.1)); + let mut group = Group::new().set("class", "tick").set( + "transform", + format!("translate({},{})", offsets.0, offsets.1), + ); let tick_line = Line::new() .set("x1", 0) @@ -121,7 +133,13 @@ impl AxisTick { .set("stroke-width", "1px"); let tick_label = Text::new() - .set("transform", format!("rotate({},{},{})", self.label_rotation, tick_label_offset.0, tick_label_offset.1)) + .set( + "transform", + format!( + "rotate({},{},{})", + self.label_rotation, tick_label_offset.0, tick_label_offset.1 + ), + ) .set("x", tick_label_offset.0) .set("y", tick_label_offset.1) .set("dy", ".35em") @@ -136,4 +154,4 @@ impl AxisTick { Ok(group) } -} \ No newline at end of file +} diff --git a/src/components/bar.rs b/src/components/bar.rs index 3e0aba1..e15277d 100644 --- a/src/components/bar.rs +++ b/src/components/bar.rs @@ -1,10 +1,10 @@ -use svg::node::Node; +use crate::chart::Orientation; +use crate::components::DatumRepresentation; use svg::node::element::Group; use svg::node::element::Rectangle; -use svg::node::Text as TextNode; use svg::node::element::Text; -use crate::components::DatumRepresentation; -use crate::chart::Orientation; +use svg::node::Node; +use svg::node::Text as TextNode; /// Set the position of a bar's label. #[derive(Copy, Clone, Debug)] @@ -49,7 +49,7 @@ impl Bar { label_visible: bool, rounding_precision: Option, bar_width: f32, - offset: f32 + offset: f32, ) -> Self { Self { blocks, @@ -65,7 +65,6 @@ impl Bar { } impl DatumRepresentation for Bar { - fn to_svg(&self) -> Result { let (bar_group_offset_x, bar_group_offset_y) = { match self.orientation { @@ -75,7 +74,10 @@ impl DatumRepresentation for Bar { }; let mut group = Group::new() - .set("transform", format!("translate({},{})", bar_group_offset_x, bar_group_offset_y)) + .set( + "transform", + format!("translate({},{})", bar_group_offset_x, bar_group_offset_y), + ) .set("class", "bar"); let (x_attr, y_attr, width_attr, height_attr) = match self.orientation { @@ -97,22 +99,50 @@ impl DatumRepresentation for Bar { // Display labels if needed. if self.label_visible { let (label_x_attr_value, text_anchor) = match self.label_position { - BarLabelPosition::StartOutside if self.orientation == Orientation::Horizontal => (block.0 - 12_f32, "end"), - BarLabelPosition::StartOutside if self.orientation == Orientation::Vertical => (block.1 + 16_f32, "middle"), - BarLabelPosition::StartInside if self.orientation == Orientation::Horizontal => (block.0 + 12_f32, "start"), - BarLabelPosition::StartInside if self.orientation == Orientation::Vertical => (block.1 - 16_f32, "middle"), - BarLabelPosition::Center if self.orientation == Orientation::Horizontal => (block.0 + (block.1 - block.0) / 2_f32, "middle"), - BarLabelPosition::Center if self.orientation == Orientation::Vertical => (block.0 + (block.1 - block.0) / 2_f32, "middle"), - BarLabelPosition::EndInside if self.orientation == Orientation::Horizontal => (block.1 - 12_f32, "end"), - BarLabelPosition::EndInside if self.orientation == Orientation::Vertical => (block.0 + 16_f32, "middle"), - BarLabelPosition::EndOutside if self.orientation == Orientation::Horizontal => (block.1 + 12_f32, "start"), - BarLabelPosition::EndOutside if self.orientation == Orientation::Vertical => (block.0 - 16_f32, "middle"), + BarLabelPosition::StartOutside + if self.orientation == Orientation::Horizontal => + { + (block.0 - 12_f32, "end") + } + BarLabelPosition::StartOutside if self.orientation == Orientation::Vertical => { + (block.1 + 16_f32, "middle") + } + BarLabelPosition::StartInside + if self.orientation == Orientation::Horizontal => + { + (block.0 + 12_f32, "start") + } + BarLabelPosition::StartInside if self.orientation == Orientation::Vertical => { + (block.1 - 16_f32, "middle") + } + BarLabelPosition::Center if self.orientation == Orientation::Horizontal => { + (block.0 + (block.1 - block.0) / 2_f32, "middle") + } + BarLabelPosition::Center if self.orientation == Orientation::Vertical => { + (block.0 + (block.1 - block.0) / 2_f32, "middle") + } + BarLabelPosition::EndInside if self.orientation == Orientation::Horizontal => { + (block.1 - 12_f32, "end") + } + BarLabelPosition::EndInside if self.orientation == Orientation::Vertical => { + (block.0 + 16_f32, "middle") + } + BarLabelPosition::EndOutside if self.orientation == Orientation::Horizontal => { + (block.1 + 12_f32, "start") + } + BarLabelPosition::EndOutside if self.orientation == Orientation::Vertical => { + (block.0 - 16_f32, "middle") + } _ => (0_f32, "middle"), // this is needed to get rid of compiler warning of exhaustively covering match pattern. }; let label_text = match &self.rounding_precision { None => block.2.to_string(), - Some(nr_of_digits) => format!("{:.1$}", block.2.to_string().parse::().unwrap(), nr_of_digits) + Some(nr_of_digits) => format!( + "{:.1$}", + block.2.to_string().parse::().unwrap(), + nr_of_digits + ), }; let label = Text::new() @@ -133,4 +163,4 @@ impl DatumRepresentation for Bar { Ok(group) } -} \ No newline at end of file +} diff --git a/src/components/legend.rs b/src/components/legend.rs index 534c845..dcd5263 100644 --- a/src/components/legend.rs +++ b/src/components/legend.rs @@ -1,8 +1,8 @@ -use svg::node::element::{Group, Circle, Rectangle, Line}; -use svg::Node; -use svg::node::Text as TextNode; -use svg::node::element::Text; use crate::MarkerType; +use svg::node::element::Text; +use svg::node::element::{Circle, Group, Line, Rectangle}; +use svg::node::Text as TextNode; +use svg::Node; /// Represents the possible marker types that a legend entry can have. pub enum LegendMarkerType { @@ -34,7 +34,12 @@ pub struct LegendEntry { impl LegendEntry { /// Create a new legend entry. - pub fn new(marker_type: LegendMarkerType, color: String, stroke_type: String, label: String) -> Self { + pub fn new( + marker_type: LegendMarkerType, + color: String, + stroke_type: String, + label: String, + ) -> Self { Self { marker_type, marker_size: 7, @@ -53,8 +58,7 @@ impl LegendEntry { } pub fn to_svg(&self) -> Result { - let mut group = Group::new() - .set("class", "legend-entry"); + let mut group = Group::new().set("class", "legend-entry"); match self.marker_type { LegendMarkerType::Circle => group.append( @@ -63,7 +67,7 @@ impl LegendEntry { .set("cy", self.marker_size) .set("r", self.marker_size) .set("fill", self.color.as_ref()) - .set("stroke", "none") + .set("stroke", "none"), ), LegendMarkerType::Square => group.append( Rectangle::new() @@ -72,7 +76,7 @@ impl LegendEntry { .set("width", 2 * self.marker_size) .set("height", 2 * self.marker_size) .set("fill", self.color.as_ref()) - .set("stroke", "none") + .set("stroke", "none"), ), LegendMarkerType::X => { group.append( @@ -82,7 +86,7 @@ impl LegendEntry { .set("x2", 2 * self.marker_size) .set("y2", 2 * self.marker_size) .set("stroke", self.color.as_ref()) - .set("stroke-width", "2px") + .set("stroke-width", "2px"), ); group.append( Line::new() @@ -91,9 +95,9 @@ impl LegendEntry { .set("x2", 0) .set("y2", 2 * self.marker_size) .set("stroke", self.color.as_ref()) - .set("stroke-width", "2px") + .set("stroke-width", "2px"), ) - }, + } LegendMarkerType::Line => group.append( Line::new() .set("x1", 0) @@ -102,7 +106,7 @@ impl LegendEntry { .set("y2", self.marker_size) .set("stroke", self.color.as_ref()) .set("stroke-width", "2px") - .set("stroke-dasharray", self.stroke_type.as_ref()) + .set("stroke-dasharray", self.stroke_type.as_ref()), ), } @@ -114,9 +118,9 @@ impl LegendEntry { .set("font-family", "sans-serif") .set("fill", "#777") .set("font-size", "12px") - .add(TextNode::new(self.label.clone())) + .add(TextNode::new(self.label.clone())), ); Ok(group) } -} \ No newline at end of file +} diff --git a/src/components/line.rs b/src/components/line.rs index 0bbbfe7..b856b67 100644 --- a/src/components/line.rs +++ b/src/components/line.rs @@ -1,9 +1,9 @@ +use crate::components::scatter::ScatterPoint; +use crate::components::DatumRepresentation; use std::fmt::Display; -use svg::node::element::{Group, Path}; use svg::node::element::path::Data; +use svg::node::element::{Group, Path}; use svg::node::Node; -use crate::components::DatumRepresentation; -use crate::components::scatter::ScatterPoint; /// Represents a point in a scatter plot. #[derive(Debug)] @@ -13,22 +13,14 @@ pub struct LineSeries { } impl LineSeries { - pub fn new( - points: Vec>, - color: String - ) -> Self { - Self { - points, - color, - } + pub fn new(points: Vec>, color: String) -> Self { + Self { points, color } } } impl DatumRepresentation for LineSeries { - fn to_svg(&self) -> Result { - let mut group = Group::new() - .set("class", "line"); + let mut group = Group::new().set("class", "line"); let mut data = Data::new(); diff --git a/src/components/mod.rs b/src/components/mod.rs index defee2b..ef03e7a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,13 +1,13 @@ use svg::node::element::Group; -pub(crate) mod bar; +pub(crate) mod area; pub(crate) mod axis; -pub(crate) mod scatter; -pub(crate) mod line; +pub(crate) mod bar; pub(crate) mod legend; -pub(crate) mod area; +pub(crate) mod line; +pub(crate) mod scatter; /// A trait that defines behavior of chart components. pub trait DatumRepresentation { fn to_svg(&self) -> Result; -} \ No newline at end of file +} diff --git a/src/components/scatter.rs b/src/components/scatter.rs index f9b632d..37b25e9 100644 --- a/src/components/scatter.rs +++ b/src/components/scatter.rs @@ -1,9 +1,9 @@ +use crate::components::DatumRepresentation; use std::fmt::Display; -use svg::node::element::{Group, Circle, Rectangle, Line}; +use svg::node::element::Text; +use svg::node::element::{Circle, Group, Line, Rectangle}; use svg::node::Node; use svg::node::Text as TextNode; -use svg::node::element::Text; -use crate::components::DatumRepresentation; /// Define the possible types of points in a scatter plot. #[derive(Debug, Copy, Clone)] @@ -23,7 +23,7 @@ pub enum PointLabelPosition { S, SW, W, - NW + NW, } /// Represents a point in a scatter plot. @@ -52,7 +52,7 @@ impl ScatterPoint { label_position: PointLabelPosition, label_visible: bool, point_visible: bool, - color: String + color: String, ) -> Self { Self { label_position, @@ -80,7 +80,6 @@ impl ScatterPoint { } impl DatumRepresentation for ScatterPoint { - fn to_svg(&self) -> Result { let mut group = Group::new() .set("transform", format!("translate({},{})", self.x, self.y)) @@ -93,9 +92,9 @@ impl DatumRepresentation for ScatterPoint { .set("cx", 0) .set("cy", 0) .set("r", self.marker_size) - .set("fill", self.color.as_ref()) + .set("fill", self.color.as_ref()), ); - }, + } MarkerType::Square if self.point_visible => { group.append( Rectangle::new() @@ -103,9 +102,9 @@ impl DatumRepresentation for ScatterPoint { .set("y", -(self.marker_size as i32)) .set("width", 2 * self.marker_size) .set("height", 2 * self.marker_size) - .set("fill", self.color.as_ref()) + .set("fill", self.color.as_ref()), ); - }, + } MarkerType::X if self.point_visible => { group.append( Group::new() @@ -116,7 +115,7 @@ impl DatumRepresentation for ScatterPoint { .set("x2", self.marker_size) .set("y2", self.marker_size) .set("stroke-width", "2px") - .set("stroke", self.color.as_ref()) + .set("stroke", self.color.as_ref()), ) .add( Line::new() @@ -125,11 +124,11 @@ impl DatumRepresentation for ScatterPoint { .set("x2", -(self.marker_size as i32)) .set("y2", self.marker_size) .set("stroke-width", "2px") - .set("stroke", self.color.as_ref()) - ) + .set("stroke", self.color.as_ref()), + ), ); - }, - _ => {}, + } + _ => {} }; if self.label_visible { @@ -138,7 +137,10 @@ impl DatumRepresentation for ScatterPoint { .set("font-family", "sans-serif") .set("fill", "#333") .set("font-size", "14px") - .add(TextNode::new(format!("({}, {})", self.x_label, self.y_label))); + .add(TextNode::new(format!( + "({}, {})", + self.x_label, self.y_label + ))); let label_offset = self.marker_size as isize; match self.label_position { @@ -146,46 +148,46 @@ impl DatumRepresentation for ScatterPoint { point_label.assign("x", 0); point_label.assign("y", -label_offset - 12); point_label.assign("text-anchor", "middle"); - }, + } PointLabelPosition::NE => { point_label.assign("x", label_offset + 4); point_label.assign("y", -label_offset - 8); point_label.assign("text-anchor", "start"); - }, + } PointLabelPosition::E => { point_label.assign("x", label_offset + 8); point_label.assign("y", 0); point_label.assign("text-anchor", "start"); - }, + } PointLabelPosition::SE => { point_label.assign("x", label_offset + 4); point_label.assign("y", label_offset + 8); point_label.assign("text-anchor", "start"); - }, + } PointLabelPosition::S => { point_label.assign("x", 0); point_label.assign("y", label_offset + 12); point_label.assign("text-anchor", "middle"); - }, + } PointLabelPosition::SW => { point_label.assign("x", -label_offset - 4); point_label.assign("y", label_offset + 8); point_label.assign("text-anchor", "end"); - }, + } PointLabelPosition::W => { point_label.assign("x", -label_offset - 8); point_label.assign("y", 0); point_label.assign("text-anchor", "end"); - }, + } PointLabelPosition::NW => { point_label.assign("x", -label_offset - 4); point_label.assign("y", -label_offset - 8); point_label.assign("text-anchor", "end"); - }, + } } group.append(point_label); } Ok(group) } -} \ No newline at end of file +} diff --git a/src/legend.rs b/src/legend.rs index e9e443b..97dee95 100644 --- a/src/legend.rs +++ b/src/legend.rs @@ -1,6 +1,6 @@ +use crate::components::legend::LegendEntry; use svg::node::element::Group; use svg::Node; -use crate::components::legend::LegendEntry; pub(crate) struct Legend { width: usize, @@ -10,10 +10,7 @@ pub(crate) struct Legend { impl Legend { /// Create a new legend instance. pub fn new(entries: Vec, width: usize) -> Self { - Self { - entries, - width, - } + Self { entries, width } } pub fn to_svg(&self) -> Result { @@ -34,7 +31,14 @@ impl Legend { } let mut entry_group = entry.to_svg()?; - entry_group.assign("transform", format!("translate({},{})", acc_row_width, current_row_offset * legend_row_height)); + entry_group.assign( + "transform", + format!( + "translate({},{})", + acc_row_width, + current_row_offset * legend_row_height + ), + ); group.append(entry_group); acc_row_width += max_entry_length + gap_between_legend_entries; @@ -42,4 +46,4 @@ impl Legend { Ok(group) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index c9d4815..ff252b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,28 +24,28 @@ mod chart; // mod view; -mod scales; -mod views; -mod components; -mod colors; mod axis; +mod colors; +mod components; mod legend; +mod scales; +mod views; +pub use crate::axis::{Axis, AxisPosition}; pub use crate::chart::Chart; +pub use crate::colors::Color; +pub use crate::components::bar::BarLabelPosition; +pub use crate::components::line::LineSeries; +pub use crate::components::scatter::{MarkerType, PointLabelPosition}; pub use crate::scales::band::ScaleBand; pub use crate::scales::linear::ScaleLinear; pub use crate::scales::Scale; -pub use crate::views::vertical_bar::VerticalBarView; -pub use crate::views::horizontal_bar::HorizontalBarView; -pub use crate::views::scatter::ScatterView; -pub use crate::views::line::LineSeriesView; pub use crate::views::area::AreaSeriesView; pub use crate::views::datum::{BarDatum, PointDatum}; -pub use crate::axis::{Axis, AxisPosition}; -pub use crate::components::bar::BarLabelPosition; -pub use crate::components::line::LineSeries; -pub use crate::components::scatter::{MarkerType, PointLabelPosition}; -pub use crate::colors::Color; +pub use crate::views::horizontal_bar::HorizontalBarView; +pub use crate::views::line::LineSeriesView; +pub use crate::views::scatter::ScatterView; +pub use crate::views::vertical_bar::VerticalBarView; #[cfg(test)] mod tests { diff --git a/src/scales/band.rs b/src/scales/band.rs index ae38522..6d5b591 100644 --- a/src/scales/band.rs +++ b/src/scales/band.rs @@ -1,6 +1,6 @@ +use crate::scales::{Scale, ScaleType}; use std::collections::HashMap; use std::collections::HashSet; -use crate::scales::{Scale, ScaleType}; /// The scale to represent categorical data. #[derive(Debug)] @@ -183,4 +183,4 @@ impl Scale for ScaleBand { fn get_ticks(&self) -> Vec { self.domain.clone() } -} \ No newline at end of file +} diff --git a/src/scales/linear.rs b/src/scales/linear.rs index 135d224..67b0adf 100644 --- a/src/scales/linear.rs +++ b/src/scales/linear.rs @@ -1,5 +1,5 @@ -use std::cmp::{max, Ordering}; use crate::scales::{Scale, ScaleType}; +use std::cmp::{max, Ordering}; /// The scale to represent categorical data. #[derive(Debug)] @@ -151,4 +151,4 @@ impl Scale for ScaleLinear { ticks } -} \ No newline at end of file +} diff --git a/src/scales/mod.rs b/src/scales/mod.rs index 5d08f0e..03c1eab 100644 --- a/src/scales/mod.rs +++ b/src/scales/mod.rs @@ -32,4 +32,4 @@ pub trait Scale { /// Get the list of ticks that represent the scale on a chart axis. fn get_ticks(&self) -> Vec; -} \ No newline at end of file +} diff --git a/src/views/area.rs b/src/views/area.rs index 1aa3a0b..30f1022 100644 --- a/src/views/area.rs +++ b/src/views/area.rs @@ -1,14 +1,14 @@ -use svg::node::Node; -use svg::node::element::Group; -use crate::components::scatter::{ScatterPoint, MarkerType, PointLabelPosition}; use crate::colors::Color; -use crate::Scale; +use crate::components::area::AreaSeries; +use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::components::scatter::{MarkerType, PointLabelPosition, ScatterPoint}; +use crate::components::DatumRepresentation; use crate::views::datum::PointDatum; use crate::views::View; -use crate::components::DatumRepresentation; +use crate::Scale; use std::fmt::Display; -use crate::components::legend::{LegendEntry, LegendMarkerType}; -use crate::components::area::AreaSeries; +use svg::node::element::Group; +use svg::node::Node; /// A View that represents data as a scatter plot. pub struct AreaSeriesView<'a, T: Display + Clone, U: Display + Clone> { @@ -85,12 +85,20 @@ impl<'a, T: Display + Clone, U: Display + Clone> AreaSeriesView<'a, T, U> { /// Load and process a dataset of BarDatum points. pub fn load_data(mut self, data: &Vec>) -> Result { match self.x_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the X dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the X dimension before loading data".to_string(), + ) + } } match self.y_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the Y dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the Y dimension before loading data".to_string(), + ) + } } // Compute corresponding offsets to apply in case there is a non-zero bandwidth. @@ -109,11 +117,25 @@ impl<'a, T: Display + Clone, U: Display + Clone> AreaSeriesView<'a, T, U> { } }; - let mut points = data.iter().map(|datum| { - let scaled_x = self.x_scale.unwrap().scale(&datum.get_x()); - let scaled_y = self.y_scale.unwrap().scale(&datum.get_y()); - ScatterPoint::new(scaled_x + x_bandwidth_offset, scaled_y + y_bandwidth_offset, self.marker_type, 5, datum.get_x(), datum.get_y(), self.label_position, self.labels_visible, true, self.colors[0].as_hex()) - }).collect::>>(); + let mut points = data + .iter() + .map(|datum| { + let scaled_x = self.x_scale.unwrap().scale(&datum.get_x()); + let scaled_y = self.y_scale.unwrap().scale(&datum.get_y()); + ScatterPoint::new( + scaled_x + x_bandwidth_offset, + scaled_y + y_bandwidth_offset, + self.marker_type, + 5, + datum.get_x(), + datum.get_y(), + self.label_position, + self.labels_visible, + true, + self.colors[0].as_hex(), + ) + }) + .collect::>>(); let y_origin = { if self.y_scale.unwrap().is_range_reversed() { @@ -124,10 +146,33 @@ impl<'a, T: Display + Clone, U: Display + Clone> AreaSeriesView<'a, T, U> { }; let first = data.first().unwrap(); let last = data.last().unwrap(); - points.push(ScatterPoint::new(self.x_scale.unwrap().scale(&last.get_x()) + x_bandwidth_offset, y_origin, self.marker_type, 5, data[0].get_x(), data[0].get_y(), self.label_position, false, false, "#fff".to_string())); - points.push(ScatterPoint::new(self.x_scale.unwrap().scale(&first.get_x()) + x_bandwidth_offset, y_origin, self.marker_type, 5, data[0].get_x(), data[0].get_y(), self.label_position, false, false, "#fff".to_string())); - - self.entries.push(AreaSeries::new(points, self.colors[0].as_hex())); + points.push(ScatterPoint::new( + self.x_scale.unwrap().scale(&last.get_x()) + x_bandwidth_offset, + y_origin, + self.marker_type, + 5, + data[0].get_x(), + data[0].get_y(), + self.label_position, + false, + false, + "#fff".to_string(), + )); + points.push(ScatterPoint::new( + self.x_scale.unwrap().scale(&first.get_x()) + x_bandwidth_offset, + y_origin, + self.marker_type, + 5, + data[0].get_x(), + data[0].get_y(), + self.label_position, + false, + false, + "#fff".to_string(), + )); + + self.entries + .push(AreaSeries::new(points, self.colors[0].as_hex())); Ok(self) } @@ -153,7 +198,12 @@ impl<'a, T: Display + Clone, U: Display + Clone> View<'a> for AreaSeriesView<'a, // Area series currently does not support multiple keys per dataset, // hence when displaying a legend, it will display the custom data label // as the legend label. - entries.push(LegendEntry::new(LegendMarkerType::Square, self.colors[0].as_hex(), String::from("none"), self.custom_data_label.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Square, + self.colors[0].as_hex(), + String::from("none"), + self.custom_data_label.clone(), + )); entries } diff --git a/src/views/horizontal_bar.rs b/src/views/horizontal_bar.rs index d26ef51..6de3d9d 100644 --- a/src/views/horizontal_bar.rs +++ b/src/views/horizontal_bar.rs @@ -1,14 +1,14 @@ -use std::collections::HashMap; -use svg::node::Node; -use svg::node::element::Group; -use crate::components::bar::{Bar, BarBlock, BarLabelPosition}; +use crate::chart::Orientation; use crate::colors::Color; -use crate::{Scale, BarDatum}; -use crate::scales::ScaleType; +use crate::components::bar::{Bar, BarBlock, BarLabelPosition}; +use crate::components::legend::{LegendEntry, LegendMarkerType}; use crate::components::DatumRepresentation; +use crate::scales::ScaleType; use crate::views::View; -use crate::chart::Orientation; -use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::{BarDatum, Scale}; +use std::collections::HashMap; +use svg::node::element::Group; +use svg::node::Node; /// A View that represents data as horizontal bars. pub struct HorizontalBarView<'a> { @@ -95,11 +95,11 @@ impl<'a> HorizontalBarView<'a> { /// Load and process a dataset of BarDatum points. pub fn load_data(mut self, data: &Vec) -> Result { match self.x_scale { - Some(scale) if scale.get_type() == ScaleType::Linear => {}, + Some(scale) if scale.get_type() == ScaleType::Linear => {} _ => return Err("The X axis scale should be a Band scale.".to_string()), } match self.y_scale { - Some(scale) if scale.get_type() == ScaleType::Band => {}, + Some(scale) if scale.get_type() == ScaleType::Band => {} _ => return Err("The Y axis scale should be a Linear scale.".to_string()), } @@ -116,7 +116,8 @@ impl<'a> HorizontalBarView<'a> { // should keep the order defined in the `keys` attribute. for (i, key) in self.keys.iter_mut().enumerate() { // Map the key to the corresponding color. - self.color_map.insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); + self.color_map + .insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); for entry in data.iter() { if entry.get_key() == *key { @@ -152,10 +153,24 @@ impl<'a> HorizontalBarView<'a> { stacked_start = stacked_end; stacked_end = self.x_scale.unwrap().scale(&value_acc); } - bar_blocks.push(BarBlock::new(stacked_start, stacked_end, *value, self.color_map.get(*key).unwrap().clone())); + bar_blocks.push(BarBlock::new( + stacked_start, + stacked_end, + *value, + self.color_map.get(*key).unwrap().clone(), + )); } - let bar = Bar::new(bar_blocks, Orientation::Horizontal, category.to_string(), self.label_position, self.labels_visible, self.rounding_precision, self.y_scale.unwrap().bandwidth().unwrap(), self.y_scale.unwrap().scale(category)); + let bar = Bar::new( + bar_blocks, + Orientation::Horizontal, + category.to_string(), + self.label_position, + self.labels_visible, + self.rounding_precision, + self.y_scale.unwrap().bandwidth().unwrap(), + self.y_scale.unwrap().scale(category), + ); bars.push(bar); } @@ -173,7 +188,7 @@ impl<'a> HorizontalBarView<'a> { for datum in data.iter() { match map.insert(datum.get_key(), 0) { - Some(_) => {}, + Some(_) => {} None => keys.push(datum.get_key()), } } @@ -208,10 +223,20 @@ impl<'a> View<'a> for HorizontalBarView<'a> { // the dataset consists only of X and Y dimension values), return // the custom data label. if self.keys.len() == 1 && self.keys[0].len() == 0 { - entries.push(LegendEntry::new(LegendMarkerType::Square, self.color_map.get(&self.keys[0]).unwrap().clone(), String::from("none"), self.custom_data_label.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Square, + self.color_map.get(&self.keys[0]).unwrap().clone(), + String::from("none"), + self.custom_data_label.clone(), + )); } else { for key in self.keys.iter() { - entries.push(LegendEntry::new(LegendMarkerType::Square, self.color_map.get(key).unwrap().clone(), String::from("none"), key.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Square, + self.color_map.get(key).unwrap().clone(), + String::from("none"), + key.clone(), + )); } } diff --git a/src/views/line.rs b/src/views/line.rs index b3c7c8e..c6992e3 100644 --- a/src/views/line.rs +++ b/src/views/line.rs @@ -1,14 +1,14 @@ -use std::collections::HashMap; -use std::fmt::Display; -use svg::node::Node; -use svg::node::element::Group; -use crate::components::scatter::{ScatterPoint, MarkerType, PointLabelPosition}; use crate::colors::Color; -use crate::{Scale, LineSeries}; +use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::components::scatter::{MarkerType, PointLabelPosition, ScatterPoint}; +use crate::components::DatumRepresentation; use crate::views::datum::PointDatum; use crate::views::View; -use crate::components::DatumRepresentation; -use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::{LineSeries, Scale}; +use std::collections::HashMap; +use std::fmt::Display; +use svg::node::element::Group; +use svg::node::Node; /// A View that represents data as a scatter plot. pub struct LineSeriesView<'a, T: Display, U: Display> { @@ -95,12 +95,20 @@ impl<'a, T: Display, U: Display> LineSeriesView<'a, T, U> { /// Load and process a dataset of BarDatum points. pub fn load_data(mut self, data: &Vec>) -> Result { match self.x_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the X dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the X dimension before loading data".to_string(), + ) + } } match self.y_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the Y dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the Y dimension before loading data".to_string(), + ) + } } // If no keys were explicitly provided, extract the keys from the data. @@ -112,32 +120,50 @@ impl<'a, T: Display, U: Display> LineSeriesView<'a, T, U> { // should keep the order defined in the `keys` attribute. for (i, key) in self.keys.iter_mut().enumerate() { // Map the key to the corresponding color. - self.color_map.insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); + self.color_map + .insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); } for key in self.keys.iter() { - - let points = data.iter().filter(|datum| &datum.get_key() == key).map(|datum| { - let scaled_x = self.x_scale.unwrap().scale(&datum.get_x()); - let scaled_y = self.y_scale.unwrap().scale(&datum.get_y()); - let y_bandwidth_offset = { - if self.y_scale.unwrap().is_range_reversed() { - -self.y_scale.unwrap().bandwidth().unwrap() / 2_f32 - } else { - self.y_scale.unwrap().bandwidth().unwrap() / 2_f32 - } - }; - let x_bandwidth_offset = { - if self.x_scale.unwrap().is_range_reversed() { - -self.x_scale.unwrap().bandwidth().unwrap() / 2_f32 - } else { - self.x_scale.unwrap().bandwidth().unwrap() / 2_f32 - } - }; - ScatterPoint::new(scaled_x + x_bandwidth_offset, scaled_y + y_bandwidth_offset, self.marker_type, 5, datum.get_x(), datum.get_y(), self.label_position, self.labels_visible, true,self.color_map.get(&datum.get_key()).unwrap().clone()) - }).collect::>>(); - - self.entries.push(LineSeries::new(points, self.color_map.get(key).unwrap().clone())); + let points = data + .iter() + .filter(|datum| &datum.get_key() == key) + .map(|datum| { + let scaled_x = self.x_scale.unwrap().scale(&datum.get_x()); + let scaled_y = self.y_scale.unwrap().scale(&datum.get_y()); + let y_bandwidth_offset = { + if self.y_scale.unwrap().is_range_reversed() { + -self.y_scale.unwrap().bandwidth().unwrap() / 2_f32 + } else { + self.y_scale.unwrap().bandwidth().unwrap() / 2_f32 + } + }; + let x_bandwidth_offset = { + if self.x_scale.unwrap().is_range_reversed() { + -self.x_scale.unwrap().bandwidth().unwrap() / 2_f32 + } else { + self.x_scale.unwrap().bandwidth().unwrap() / 2_f32 + } + }; + ScatterPoint::new( + scaled_x + x_bandwidth_offset, + scaled_y + y_bandwidth_offset, + self.marker_type, + 5, + datum.get_x(), + datum.get_y(), + self.label_position, + self.labels_visible, + true, + self.color_map.get(&datum.get_key()).unwrap().clone(), + ) + }) + .collect::>>(); + + self.entries.push(LineSeries::new( + points, + self.color_map.get(key).unwrap().clone(), + )); } Ok(self) @@ -150,14 +176,13 @@ impl<'a, T: Display, U: Display> LineSeriesView<'a, T, U> { for datum in data.iter() { match map.insert(datum.get_key(), 0) { - Some(_) => {}, + Some(_) => {} None => keys.push(datum.get_key()), } } keys } - } impl<'a, T: Display, U: Display> View<'a> for LineSeriesView<'a, T, U> { @@ -181,10 +206,20 @@ impl<'a, T: Display, U: Display> View<'a> for LineSeriesView<'a, T, U> { // the dataset consists only of X and Y dimension values), return // the custom data label. if self.keys.len() == 1 && self.keys[0].len() == 0 { - entries.push(LegendEntry::new(LegendMarkerType::Line, self.color_map.get(&self.keys[0]).unwrap().clone(), String::from("none"), self.custom_data_label.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Line, + self.color_map.get(&self.keys[0]).unwrap().clone(), + String::from("none"), + self.custom_data_label.clone(), + )); } else { for key in self.keys.iter() { - entries.push(LegendEntry::new(LegendMarkerType::Line, self.color_map.get(key).unwrap().clone(), String::from("none"), key.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Line, + self.color_map.get(key).unwrap().clone(), + String::from("none"), + key.clone(), + )); } } diff --git a/src/views/mod.rs b/src/views/mod.rs index 4f1f7a2..3966400 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,12 +1,12 @@ -use svg::node::element::Group; use crate::components::legend::LegendEntry; +use svg::node::element::Group; -pub mod vertical_bar; -pub mod horizontal_bar; -pub mod scatter; +pub mod area; pub mod datum; +pub mod horizontal_bar; pub mod line; -pub mod area; +pub mod scatter; +pub mod vertical_bar; /// A trait that defines a View of a dataset that can be rendered within a chart. pub trait View<'a> { diff --git a/src/views/scatter.rs b/src/views/scatter.rs index f6cd431..4710c7a 100644 --- a/src/views/scatter.rs +++ b/src/views/scatter.rs @@ -1,14 +1,14 @@ -use std::collections::HashMap; -use std::fmt::Display; -use svg::node::Node; -use svg::node::element::Group; -use crate::components::scatter::{ScatterPoint, MarkerType, PointLabelPosition}; use crate::colors::Color; -use crate::Scale; +use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::components::scatter::{MarkerType, PointLabelPosition, ScatterPoint}; +use crate::components::DatumRepresentation; use crate::views::datum::PointDatum; use crate::views::View; -use crate::components::DatumRepresentation; -use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::Scale; +use std::collections::HashMap; +use std::fmt::Display; +use svg::node::element::Group; +use svg::node::Node; /// A View that represents data as a scatter plot. pub struct ScatterView<'a, T: Display, U: Display> { @@ -95,12 +95,20 @@ impl<'a, T: Display, U: Display> ScatterView<'a, T, U> { /// Load and process a dataset of BarDatum points. pub fn load_data(mut self, data: &Vec>) -> Result { match self.x_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the X dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the X dimension before loading data".to_string(), + ) + } } match self.y_scale { - Some(_) => {}, - _ => return Err("Please provide a scale for the Y dimension before loading data".to_string()), + Some(_) => {} + _ => { + return Err( + "Please provide a scale for the Y dimension before loading data".to_string(), + ) + } } // If no keys were explicitly provided, extract the keys from the data. @@ -112,7 +120,8 @@ impl<'a, T: Display, U: Display> ScatterView<'a, T, U> { // should keep the order defined in the `keys` attribute. for (i, key) in self.keys.iter_mut().enumerate() { // Map the key to the corresponding color. - self.color_map.insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); + self.color_map + .insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); } for datum in data.iter() { @@ -132,7 +141,18 @@ impl<'a, T: Display, U: Display> ScatterView<'a, T, U> { self.x_scale.unwrap().bandwidth().unwrap() / 2_f32 } }; - self.entries.push(ScatterPoint::new(scaled_x + x_bandwidth_offset, scaled_y + y_bandwidth_offset, self.marker_type, 5, datum.get_x(), datum.get_y(), self.label_position, self.labels_visible, true, self.color_map.get(&datum.get_key()).unwrap().clone())); + self.entries.push(ScatterPoint::new( + scaled_x + x_bandwidth_offset, + scaled_y + y_bandwidth_offset, + self.marker_type, + 5, + datum.get_x(), + datum.get_y(), + self.label_position, + self.labels_visible, + true, + self.color_map.get(&datum.get_key()).unwrap().clone(), + )); } Ok(self) @@ -145,14 +165,13 @@ impl<'a, T: Display, U: Display> ScatterView<'a, T, U> { for datum in data.iter() { match map.insert(datum.get_key(), 0) { - Some(_) => {}, + Some(_) => {} None => keys.push(datum.get_key()), } } keys } - } impl<'a, T: Display, U: Display> View<'a> for ScatterView<'a, T, U> { @@ -176,10 +195,20 @@ impl<'a, T: Display, U: Display> View<'a> for ScatterView<'a, T, U> { // the dataset consists only of X and Y dimension values), return // the custom data label. if self.keys.len() == 1 && self.keys[0].len() == 0 { - entries.push(LegendEntry::new(LegendMarkerType::from(self.marker_type), self.color_map.get(&self.keys[0]).unwrap().clone(), String::from("none"), self.custom_data_label.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::from(self.marker_type), + self.color_map.get(&self.keys[0]).unwrap().clone(), + String::from("none"), + self.custom_data_label.clone(), + )); } else { for key in self.keys.iter() { - entries.push(LegendEntry::new(LegendMarkerType::from(self.marker_type), self.color_map.get(key).unwrap().clone(), String::from("none"), key.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::from(self.marker_type), + self.color_map.get(key).unwrap().clone(), + String::from("none"), + key.clone(), + )); } } diff --git a/src/views/vertical_bar.rs b/src/views/vertical_bar.rs index aa5dbc7..c0f458b 100644 --- a/src/views/vertical_bar.rs +++ b/src/views/vertical_bar.rs @@ -1,14 +1,14 @@ -use std::collections::HashMap; -use svg::node::Node; -use svg::node::element::Group; -use crate::components::bar::{Bar, BarBlock, BarLabelPosition}; +use crate::chart::Orientation; use crate::colors::Color; -use crate::{Scale, BarDatum}; -use crate::scales::ScaleType; +use crate::components::bar::{Bar, BarBlock, BarLabelPosition}; +use crate::components::legend::{LegendEntry, LegendMarkerType}; use crate::components::DatumRepresentation; +use crate::scales::ScaleType; use crate::views::View; -use crate::chart::Orientation; -use crate::components::legend::{LegendEntry, LegendMarkerType}; +use crate::{BarDatum, Scale}; +use std::collections::HashMap; +use svg::node::element::Group; +use svg::node::Node; /// A View that represents data as vertical bars. pub struct VerticalBarView<'a> { @@ -95,11 +95,11 @@ impl<'a> VerticalBarView<'a> { /// Load and process a dataset of BarDatum points. pub fn load_data(mut self, data: &Vec) -> Result { match self.x_scale { - Some(scale) if scale.get_type() == ScaleType::Band => {}, + Some(scale) if scale.get_type() == ScaleType::Band => {} _ => return Err("The X axis scale should be a Band scale.".to_string()), } match self.y_scale { - Some(scale) if scale.get_type() == ScaleType::Linear => {}, + Some(scale) if scale.get_type() == ScaleType::Linear => {} _ => return Err("The Y axis scale should be a Linear scale.".to_string()), } @@ -116,7 +116,8 @@ impl<'a> VerticalBarView<'a> { // should keep the order defined in the `keys` attribute. for (i, key) in self.keys.iter_mut().enumerate() { // Map the key to the corresponding color. - self.color_map.insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); + self.color_map + .insert(key.clone(), self.colors[i % self.colors.len()].as_hex()); for entry in data.iter() { if entry.get_key() == *key { @@ -153,10 +154,24 @@ impl<'a> VerticalBarView<'a> { stacked_start = stacked_end; stacked_end = self.y_scale.unwrap().scale(&value_acc); } - bar_blocks.push(BarBlock::new(stacked_start, stacked_end, *value, self.color_map.get(*key).unwrap().clone())); + bar_blocks.push(BarBlock::new( + stacked_start, + stacked_end, + *value, + self.color_map.get(*key).unwrap().clone(), + )); } - let bar = Bar::new(bar_blocks, Orientation::Vertical, category.to_string(), self.label_position, self.labels_visible, self.rounding_precision, self.x_scale.unwrap().bandwidth().unwrap(), self.x_scale.unwrap().scale(category)); + let bar = Bar::new( + bar_blocks, + Orientation::Vertical, + category.to_string(), + self.label_position, + self.labels_visible, + self.rounding_precision, + self.x_scale.unwrap().bandwidth().unwrap(), + self.x_scale.unwrap().scale(category), + ); bars.push(bar); } @@ -174,7 +189,7 @@ impl<'a> VerticalBarView<'a> { for datum in data.iter() { match map.insert(datum.get_key(), 0) { - Some(_) => {}, + Some(_) => {} None => keys.push(datum.get_key()), } } @@ -186,7 +201,6 @@ impl<'a> VerticalBarView<'a> { fn add_bar(&mut self, bar: Bar) { self.entries.push(bar); } - } impl<'a> View<'a> for VerticalBarView<'a> { @@ -210,10 +224,20 @@ impl<'a> View<'a> for VerticalBarView<'a> { // the dataset consists only of X and Y dimension values), return // the custom data label. if self.keys.len() == 1 && self.keys[0].len() == 0 { - entries.push(LegendEntry::new(LegendMarkerType::Square, self.color_map.get(&self.keys[0]).unwrap().clone(), String::from("none"), self.custom_data_label.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Square, + self.color_map.get(&self.keys[0]).unwrap().clone(), + String::from("none"), + self.custom_data_label.clone(), + )); } else { for key in self.keys.iter() { - entries.push(LegendEntry::new(LegendMarkerType::Square, self.color_map.get(key).unwrap().clone(), String::from("none"), key.clone())); + entries.push(LegendEntry::new( + LegendMarkerType::Square, + self.color_map.get(key).unwrap().clone(), + String::from("none"), + key.clone(), + )); } }