From 41d1e492c0a9d5bff85781ccfabb7e9d20f00eca Mon Sep 17 00:00:00 2001 From: Ed Rogers Date: Tue, 25 Feb 2025 19:31:57 +0000 Subject: [PATCH 1/2] Add setting of fixed box_width --- gnuplot/examples/variable_color.rs | 46 +++++++++++++++--------------- gnuplot/src/axes_common.rs | 20 +++++++++++++ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/gnuplot/examples/variable_color.rs b/gnuplot/examples/variable_color.rs index 824a6455..84215953 100644 --- a/gnuplot/examples/variable_color.rs +++ b/gnuplot/examples/variable_color.rs @@ -87,34 +87,34 @@ fn example(c: Common) ax.set_title( &format!("variable color boxerror, points, xyerrorbars, and boxxyerror.\nColor used is a {label}"), &[], - ); - ax.set_y_range(Fix(-4.0), Fix(11.5)); - ax.box_error_low_high_set_width( + ) + .set_y_range(Fix(-4.0), Fix(11.5)) + .set_box_width(0.5, true) + .box_error_low_high( &d1, &d5, &d2, &d6, - iter::repeat(0.2), &[ color.clone(), FillAlpha(0.5), BorderColor(RGBString("black")), ], - ); - ax.points(&d1, iter::repeat(1), &[color.clone(), PointSymbol('D')]); - ax.xy_error_bars( + ) + .points(&d1, iter::repeat(1), &[color.clone(), PointSymbol('D')]) + .xy_error_bars( &d1, iter::repeat(8), d1.iter().map(by3), d1.iter().map(by4), &[color.clone()], - ); - ax.points( + ) + .points( &d1, d2.iter().map(|v| -v / 2.0), &[color.clone(), PointSymbol('O'), PointSize(3.0)], - ); - ax.box_xy_error_delta( + ) + .box_xy_error_delta( &d1, iter::repeat(10), d1.iter().map(by3), @@ -153,21 +153,21 @@ fn example(c: Common) ax.set_title( &format!("variable color boxerror, points, xyerrorbars, and boxxyerror.\nColor used is a SavedColormap"), &[], - ); - ax.set_y_range(Fix(-4.0), Fix(11.5)); - ax.box_error_low_high_set_width( + ) + .set_y_range(Fix(-4.0), Fix(11.5)) + .set_box_width(0.8, false) + .box_error_low_high( &d1, &d5, &d2, &d6, - iter::repeat(0.2), &[ Color(SavedColorMap("magma", color_values.clone())), FillAlpha(0.5), BorderColor(RGBString("black")), ], - ); - ax.points( + ) + .points( &d1, iter::repeat(1), &[ @@ -177,15 +177,15 @@ fn example(c: Common) )), PointSymbol('D'), ], - ); - ax.xy_error_bars( + ) + .xy_error_bars( &d1, iter::repeat(8), d1.iter().map(by3), d1.iter().map(by4), &[Color(SavedColorMap("ocean", color_values.clone()))], - ); - ax.points( + ) + .points( &d1, d2.iter().map(|v| -v / 2.0), &[ @@ -193,8 +193,8 @@ fn example(c: Common) PointSymbol('O'), PointSize(3.0), ], - ); - ax.box_xy_error_delta( + ) + .box_xy_error_delta( &d1, iter::repeat(10), d1.iter().map(by3), diff --git a/gnuplot/src/axes_common.rs b/gnuplot/src/axes_common.rs index d7ae2786..c1c2455f 100644 --- a/gnuplot/src/axes_common.rs +++ b/gnuplot/src/axes_common.rs @@ -1107,6 +1107,7 @@ pub struct AxesCommonData pub margins: Margins, pub palette: PaletteType>, pub colormaps: Vec<(String, PaletteType>)>, + pub box_width: Option<(f64, bool)>, } impl AxesCommonData @@ -1131,6 +1132,7 @@ impl AxesCommonData margins: Margins::new(), palette: COLOR.to_one_way_owned(), colormaps: Vec::new(), + box_width: None, }; ret.x2_axis.tick_type = TickType::None; ret.y2_axis.tick_type = TickType::None; @@ -1277,6 +1279,12 @@ impl AxesCommonData } self.palette.write_out_commands(w); + if let Some((width, is_relative)) = self.box_width + { + let scale = if is_relative { "relative" } else { "absolute" }; + writeln!(w, "set boxwidth {width} {scale}"); + } + self.x_axis.write_out_commands(w, version); self.y_axis.write_out_commands(w, version); self.x2_axis.write_out_commands(w, version); @@ -1408,6 +1416,18 @@ pub trait AxesCommon: AxesCommonPrivate self } + /// Set the width of boxes in any box plots on the axes (for example [boxes()](crate::Axes2D::boxes), + /// [box_and_whisker()](crate::Axes2D::box_and_whisker)) + /// # Arguments + /// * `width` - Width of boxes. + /// * `is_relative` - if `true`, `width` is interpreted as a fraction of the default box width. + /// if `false` width is an absolute value in the units of the x axis + fn set_box_width(&mut self, width: f64, is_relative: bool) -> &mut Self + { + self.get_common_data_mut().box_width = Some((width, is_relative)); + self + } + /// Set the aspect ratio of the axes /// # Arguments /// * `ratio` - The aspect ratio. Set to Auto to return the ratio to default From 49bc4b42f2fda17c12791ba13a7c8048f761a089 Mon Sep 17 00:00:00 2001 From: Ed Rogers Date: Tue, 25 Feb 2025 20:04:19 +0000 Subject: [PATCH 2/2] Remove box_???_set_width functions in favour of BoxWidth PlotOption --- gnuplot/examples/box_and_whisker.rs | 4 +- gnuplot/examples/example2.rs | 6 +- gnuplot/examples/inverse_api.rs | 1 + gnuplot/examples/patterns.rs | 6 +- gnuplot/src/axes2d.rs | 203 ++++------------------------ gnuplot/src/options.rs | 3 + gnuplot/src/util.rs | 55 ++++++-- 7 files changed, 78 insertions(+), 200 deletions(-) diff --git a/gnuplot/examples/box_and_whisker.rs b/gnuplot/examples/box_and_whisker.rs index 38b2f427..fcdcbce1 100644 --- a/gnuplot/examples/box_and_whisker.rs +++ b/gnuplot/examples/box_and_whisker.rs @@ -10,14 +10,14 @@ fn example(c: Common) fg.axes2d() .set_title("Box and whisker", &[]) - .box_and_whisker_set_width( + .box_and_whisker( [-0.6f32, 1.5, 2.5].iter(), [-1.0f32, 0.0, 1.0].iter(), [-2.0f32, -1.0, 0.0].iter(), [2.0f32, 3.0, 4.0].iter(), [1.0f32, 2.0, 3.0].iter(), - [0.5f32, 0.25, 0.125].iter(), &[ + BoxWidth([0.5f64, 0.25, 0.125].into()), WhiskerBars(0.5), Color("blue".into()), LineWidth(2.0), diff --git a/gnuplot/examples/example2.rs b/gnuplot/examples/example2.rs index a30d6037..de0385d3 100644 --- a/gnuplot/examples/example2.rs +++ b/gnuplot/examples/example2.rs @@ -2,7 +2,6 @@ use crate::common::*; use gnuplot::*; -use std::iter::repeat; mod common; @@ -17,7 +16,6 @@ fn example(c: Common) let x2 = x2.iter2(); let y2: Vec = x2.map(|&v| v * v).collect(); let y2 = y2.iter2(); - let w = repeat(0.5f32); let x3 = &[1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; let x3 = x3.iter2(); @@ -83,14 +81,14 @@ fn example(c: Common) LineStyle(DotDash), ], ) - .boxes_set_width( + .boxes( x, y1, - w, &[ LineWidth(2.0), Color("gray".into()), BorderColor("black".into()), + BoxWidth([0.5, 0.4, 0.55, 0.7, 0.2].into()), ], ); diff --git a/gnuplot/examples/inverse_api.rs b/gnuplot/examples/inverse_api.rs index aad49f79..a3aaaf63 100644 --- a/gnuplot/examples/inverse_api.rs +++ b/gnuplot/examples/inverse_api.rs @@ -42,6 +42,7 @@ impl PlotElement for Lines WhiskerBars(v) => WhiskerBars(*v), FillPattern(v) => FillPattern(*v), Axes(v1, v2) => Axes(*v1, *v2), + BoxWidth(v) => BoxWidth(v.to_vec()), }); } diff --git a/gnuplot/examples/patterns.rs b/gnuplot/examples/patterns.rs index 6d5289f1..9a5454f3 100644 --- a/gnuplot/examples/patterns.rs +++ b/gnuplot/examples/patterns.rs @@ -12,9 +12,10 @@ fn example(c: Common) ax.set_title("Patterns", &[]); ax.set_legend(Graph(1.), Graph(0.95), &[MaxRows(3)], &[]); ax.set_y_range(Auto, Fix(8.)); + ax.set_box_width(0.5, false); for i in 0..=8 { - ax.boxes_set_width(&[i], &[5], &[0.5], &[FillPattern(Auto)]); + ax.boxes(&[i], &[5], &[FillPattern(Auto)]); } for (i, &pattern) in [ @@ -31,10 +32,9 @@ fn example(c: Common) .iter() .enumerate() { - ax.boxes_set_width( + ax.boxes( &[i], &[-5], - &[0.5], &[ FillPattern(Fix(pattern)), Caption(&format!("{:?}", pattern)), diff --git a/gnuplot/src/axes2d.rs b/gnuplot/src/axes2d.rs index 98c579e8..1018e4fd 100644 --- a/gnuplot/src/axes2d.rs +++ b/gnuplot/src/axes2d.rs @@ -667,8 +667,12 @@ impl Axes2D self } - /// Plot a 2D scatter-plot using boxes of automatic width. Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Plot a 2D box-plot. + /// Box widths are, by default set so that there are no gaps between successive boxes + /// (i.e. each box may have a different width). This may be adjusted with (set_box_width())[Axes2D::set_box_width()] + /// or by using the `BoxWidth` option. /// Boxes start at the x-axis and go towards the y value of the datapoint. + /// /// # Arguments /// * `x` - x values (center of the box) /// * `y` - y values @@ -679,6 +683,8 @@ impl Axes2D /// * `BorderColor` - Sets the color of the border /// * `Color` - Sets the color of the box fill /// * `FillAlpha` - Sets the transparency of the box fill + /// * `BoxWidth` - Sets the width of each box. If not supplied, the width will use the + /// previously set box width, or be set to the spacing of the boxes pub fn boxes< 'l, Tx: DataType, @@ -696,42 +702,13 @@ impl Axes2D self } - /// Plot a 2D scatter-plot using boxes of set (per box) width. - /// Boxes start at the x-axis and go towards the y value of the datapoint. - /// # Arguments - /// * `x` - x values (center of the box) - /// * `y` - y values - /// * `w` - Box width values - /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: - /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). - /// * `LineWidth` - Sets the width of the border - /// * `LineStyle` - Sets the style of the border - /// * `BorderColor` - Sets the color of the border - /// * `Color` - Sets the color of the box fill - /// * `FillAlpha` - Sets the transparency of the box fill - pub fn boxes_set_width< - 'l, - Tx: DataType, - X: IntoIterator, - Ty: DataType, - Y: IntoIterator, - Tw: DataType, - W: IntoIterator, - >( - &'l mut self, x: X, y: Y, w: W, options: &[PlotOption<&str>], - ) -> &'l mut Self - { - let (data, num_rows, num_cols) = generate_data!(options, x, y, w); - self.common.elems.push(PlotElement::new_plot( - Boxes, data, num_rows, num_cols, options, - )); - self - } - /// Plot a 2D box-plot with error bars using boxes of automatic width. - /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Box widths are, by default set so that there are no gaps between successive boxes + /// (i.e. each box may have a different width). This may be adjusted with (set_box_width())[Axes2D::set_box_width()] + /// or by using the `BoxWidth` option. /// Boxes start at the x-axis and go towards the y value of the datapoint. /// Each box has an error bar from y - y_delta to y + y_delta. + /// /// # Arguments /// * `x` - x values (center of the box) /// * `y` - y values @@ -743,6 +720,8 @@ impl Axes2D /// * `BorderColor` - Sets the color of the border /// * `Color` - Sets the color of the box fill /// * `FillAlpha` - Sets the transparency of the box fill + /// * `BoxWidth` - Sets the width of each box. If not supplied, the width will use the + /// previously set box width, or be set to the spacing of the boxes pub fn box_error_delta< 'l, Tx: DataType, @@ -766,49 +745,10 @@ impl Axes2D self } - /// Plot a 2D box-plot with error bars using boxes of specified width. - /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). - /// Boxes start at the x-axis and go towards the y value of the datapoint. - /// Each box has an error bar from y - y_delta to y + y_delta. - /// # Arguments - /// * `x` - x values (center of the box) - /// * `y` - y values - /// * `y_delta` - errors in y (error bars are plotted from y - y_delta to y + y_delta) - /// * `x_delta` - errors in x (interpreted as box width) - /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: - /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). - /// * `LineWidth` - Sets the width of the border - /// * `LineStyle` - Sets the style of the border - /// * `BorderColor` - Sets the color of the border - /// * `Color` - Sets the color of the box fill - /// * `FillAlpha` - Sets the transparency of the box fill - pub fn box_error_delta_set_width< - 'l, - Tx: DataType, - X: IntoIterator, - Ty: DataType, - Y: IntoIterator, - Tye: DataType, - YE: IntoIterator, - Tw: DataType, - W: IntoIterator, - >( - &'l mut self, x: X, y: Y, y_error: YE, x_delta: W, options: &[PlotOption<&str>], - ) -> &'l mut Self - { - let (data, num_rows, num_cols) = generate_data!(options, x, y, y_error, x_delta); - self.common.elems.push(PlotElement::new_plot( - BoxErrorBars, - data, - num_rows, - num_cols, - options, - )); - self - } - - /// Plot a 2D box-plot with error bars using boxes of automatic width. - /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Plot a 2D box-plot with error bars. + /// Box widths are, by default set so that there are no gaps between successive boxes + /// (i.e. each box may have a different width). This may be adjusted with (set_box_width())[Axes2D::set_box_width()] + /// or by using the `BoxWidth` option. /// Boxes start at the x-axis and go towards the y value of the datapoint. /// Each box has an error bar from y - y_low to y + y_high. /// # Arguments @@ -823,6 +763,8 @@ impl Axes2D /// * `BorderColor` - Sets the color of the border /// * `Color` - Sets the color of the box fill /// * `FillAlpha` - Sets the transparency of the box fill + /// * `BoxWidth` - Sets the width of each box. If not supplied, the width will use the + /// previously set box width, or be set to the spacing of the boxes pub fn box_error_low_high< 'l, Tx: DataType, @@ -851,51 +793,11 @@ impl Axes2D self } - /// Plot a 2D box-plot with error bars using boxes of specified width. - /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). - /// Boxes start at the x-axis and go towards the y value of the datapoint. - /// Each box has an error bar from y - y_low to y + y_high. - /// # Arguments - /// * `x` - x values (center of the box) - /// * `y` - y values - /// * `y_low` - minimum of error bar - /// * `y_high` - maximum of error bar - /// * `x_delta` - errors in x (interpreted as box width) - /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: - /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). - /// * `LineWidth` - Sets the width of the border - /// * `LineStyle` - Sets the style of the border - /// * `BorderColor` - Sets the color of the border - /// * `Color` - Sets the color of the box fill - /// * `FillAlpha` - Sets the transparency of the box fill - pub fn box_error_low_high_set_width< - 'l, - Tx: DataType, - X: IntoIterator, - Ty: DataType, - Y: IntoIterator, - Tyl: DataType, - YL: IntoIterator, - Tyh: DataType, - YH: IntoIterator, - Tw: DataType, - W: IntoIterator, - >( - &'l mut self, x: X, y: Y, y_low: YL, y_high: YH, x_delta: W, options: &[PlotOption<&str>], - ) -> &'l mut Self - { - let (data, num_rows, num_cols) = generate_data!(options, x, y, y_low, y_high, x_delta); - self.common.elems.push(PlotElement::new_plot( - BoxErrorBars, - data, - num_rows, - num_cols, - options, - )); - self - } - - /// Plot a 2D box-and-whisker plot using boxes of automatic width. + /// Plot a 2D box-and-whisker plot. + /// + /// Box widths are, by default set so that there are no gaps between successive boxes + /// (i.e. each box may have a different width). This may be adjusted with (set_box_width())[Axes2D::set_box_width()] + /// or by using the `BoxWidth` option. /// /// # Arguments /// * `x` - x values (center of the box) @@ -911,6 +813,8 @@ impl Axes2D /// * `Color` - Sets the color of the box fill /// * `FillAlpha` - Sets the transparency of the box fill /// * `WhiskerBars` - Sets the width of the whisker bars + /// * `BoxWidth` - Sets the width of each box. If not supplied, the width will use the + /// previously set box width, or be set to the spacing of the boxes pub fn box_and_whisker< 'l, Tx: DataType, @@ -940,61 +844,6 @@ impl Axes2D self } - /// Plot a 2D box-and-whisker plot using boxes of set width. - /// - /// # Arguments - /// * `x` - x values (center of the box) - /// * `box_min` - minimum box y value - /// * `whisker_min` - minimum whisker y value - /// * `whisker_max` - maximum whisker y value - /// * `box_max` - maximum box y value - /// * `box_width` - width of the box (in x axis units) - /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: - /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). - /// * `LineWidth` - Sets the width of the border - /// * `LineStyle` - Sets the style of the border - /// * `BorderColor` - Sets the color of the border - /// * `Color` - Sets the color of the box fill - /// * `FillAlpha` - Sets the transparency of the box fill - /// * `WhiskerBars` - Sets the width of the whisker bars - pub fn box_and_whisker_set_width< - 'l, - Tx: DataType, - X: IntoIterator, - TBoxMin: DataType, - BoxMin: IntoIterator, - TWhiskerMin: DataType, - WhiskerMin: IntoIterator, - TWhiskerMax: DataType, - WhiskerMax: IntoIterator, - TBoxMax: DataType, - BoxMax: IntoIterator, - TBoxWidth: DataType, - BoxWidth: IntoIterator, - >( - &'l mut self, x: X, box_min: BoxMin, whisker_min: WhiskerMin, whisker_max: WhiskerMax, - box_max: BoxMax, box_width: BoxWidth, options: &[PlotOption<&str>], - ) -> &'l mut Self - { - let (data, num_rows, num_cols) = generate_data!( - options, - x, - box_min, - whisker_min, - whisker_max, - box_max, - box_width - ); - self.common.elems.push(PlotElement::new_plot( - BoxAndWhisker, - data, - num_rows, - num_cols, - options, - )); - self - } - /// Plot 2D rectangular boxes - usually used for error bars - using specified by width (x_delta) and height (y_delta). /// /// # Arguments diff --git a/gnuplot/src/options.rs b/gnuplot/src/options.rs index 7bf77dae..18905bad 100644 --- a/gnuplot/src/options.rs +++ b/gnuplot/src/options.rs @@ -74,6 +74,8 @@ pub enum PlotOption WhiskerBars(f64), /// Which axis pair to use for the plot element. Axes(XAxis, YAxis), + /// Box width set per box for box plots: each element is the width of one box + BoxWidth(Vec), } impl<'l> OneWayOwned for PlotOption<&'l str> @@ -97,6 +99,7 @@ impl<'l> OneWayOwned for PlotOption<&'l str> WhiskerBars(v) => WhiskerBars(v), FillPattern(v) => FillPattern(v), Axes(x, y) => Axes(x, y), + BoxWidth(ref d) => BoxWidth(d.clone()), } } } diff --git a/gnuplot/src/util.rs b/gnuplot/src/util.rs index 4858402f..4ee3c0f3 100644 --- a/gnuplot/src/util.rs +++ b/gnuplot/src/util.rs @@ -52,8 +52,15 @@ macro_rules! first_opt_default macro_rules! generate_data { ($options: ident, $( $d:ident ),*) => { { - let mut c_data = None; + let mut widths = None; + first_opt! {$options, + BoxWidth(ref w) => + { + widths = Some(w); + } + } + let mut c_data = None; first_opt! {$options, Color(ref color) => { @@ -62,22 +69,42 @@ macro_rules! generate_data { } } } - if let Some(c_values) = c_data { - generate_data_inner!( - $( - $d, - )* - c_values - ) + + if let Some(widths) = widths { + if let Some(c_values) = c_data { + generate_data_inner!( + $( + $d, + )* + widths, + c_values + ) + } else { + generate_data_inner!( + $( + $d, + )* + widths + ) + } } else { - generate_data_inner!( - $( - $d, - )* - ) + if let Some(c_values) = c_data { + generate_data_inner!( + $( + $d, + )* + c_values + ) + } else { + generate_data_inner!( + $( + $d, + )* + ) + } } } - }; + } } // returns (data, num_rows, num_cols)