From 9ac2764a72585e9820b61aa73d89a54b9e0ae7ff Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 08:46:27 +1000 Subject: [PATCH 01/33] Add comment --- src/canvas.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvas.rs b/src/canvas.rs index 307fb5d..37abd64 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -154,6 +154,7 @@ pub struct Canvas { } impl Canvas { + /// Creates a new Canvas object pub fn new() -> Self { Canvas { // features From d63cd80654a74fa416737ae8c43331ef76835528 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 08:53:05 +1000 Subject: [PATCH 02/33] Draft Util struct --- .vscode/settings.json | 1 + data/bivariate_normal.npy | Bin 0 -> 1880 bytes src/lib.rs | 2 + src/util.rs | 81 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 data/bivariate_normal.npy create mode 100644 src/util.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 9af7baa..48b8a93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,7 @@ "CARETRIGHTBASE", "CARETUP", "CARETUPBASE", + "cbook", "clabel", "CLOSEPOLY", "cmap", diff --git a/data/bivariate_normal.npy b/data/bivariate_normal.npy new file mode 100644 index 0000000000000000000000000000000000000000..b6b8dacdd0daecd53ef685f2a2dc29c98d5c6861 GIT binary patch literal 1880 zcmX|AeLNJ{8m6?@w|k|AjJb2;&S58qN?3txDq(VtzzprVUv_Y441f^WSsc=Xu`qKIa^c$D#jt9aU9vR$&lB zLg^=HL=v9pK(!}Y;EB{|S}ZLnDj=E`5~|pD4vM6Q%J=DEK{26nY`)vp0>9gKhXwu& zer;)XC8WM!i3iBmwDbomR~N~ihZ~C3XZ4}%zny5C?zirrU0wAhA)iNf_0{U#pm~9u=Sn}O;-UsA2dr9o+Gdcy=6|PX zM-V^V8_WtMZjtUm5PtQz)3-ar;hf`EI|pVYgsfrgTD^3Q@4Keoyyh3|^4XvEm~iXp z>?m1!<8yz)Ldte#^A50Y^?RHW>jrO(zQ3SXZ~{a+ea~N*@DItNg(P>Z(4!;qaR#+uP@$%uz zLzbLh)UuQpAlC;xcy0aXzC2(Q{LkoAay&@aJKto@RPt) zH>Y6?wk~bbDA_22FMdB#WLqYL+Pq~#(b=0|RpD7G@n?cFZX4UDCIChzoO<^?`wE`U zlXzjAEJzDEa6kFCUtxyXi?DAVg7&FW45w!p#1y@9-#IB@2!-z>3+7-lvx~>wxd1h{ z-{q7g&H;Ogq*<6Mg=9YFO?t}^2(t=(jHT^>cPb_S;Cu_(nI?6)b_pOf<4^DXNJSL> z5!Iiaspw+tmO*w{8cf$uZ45T$LVJ>PQ)qq%l%Fuh9ZnjCO9K;?jaV6E7Yo%>tLNaO z53@n1a~`myTexrV@4)lf;(jOI1k95<>1LECAYpyb&g#!Zl5N~yr)v1CAYjZA1xRpz2V`x%qD=sc8E5;z#`oDJEd$B$aNwZO3kisnkW z2qvseE7_EnuyIfE6Vtc-U^r`r`^2~c@jcik%HR;n_mwnrzmuRs+$dKtAw`TRejHcz zHM-@Jv>=HRqpbP9;6ILtP}kE8_i%>}6xWn!(U?(zis?bQfiD`7V8riWn?@UYK4j=q zaIG7)x!U*M_3uUN{+dmfJoJ)YqbqhS_GHpK^yRlP1%`Bemo&6hC4D4dG7W|Kd-v_SavZ6Q z^K$!y7hygB_4_mV`&;hVSiLeI{oRJ<^}U_QF78vIQ++2o{Y7eW^Okl*v)mjzRMdn_ zp0-vv+SMUhsm-z1!@nRepFCj&)) Self { + Self { buffer: String::new() } + } + + /// Wraps Matplotlib's `cbook.get_sample_data` function. + /// + /// This function generates a command to retrieve a sample data file from Matplotlib's + /// `mpl-data/sample_data` directory. The command is stored in the buffer. + /// + /// See + /// + /// # Arguments + /// + /// * `handle` - The name of the **Python variable** to assign the result of `cbook.get_sample_data` + /// * `matplotlib_fname` - The filename of the sample data, relative to the `mpl-data/sample_data` directory. + /// * `as_file_obj` - If `true`, returns a file object; otherwise, returns a file path. + /// + /// # Returns + /// + /// A mutable reference to the `Util` instance, allowing for method chaining. + /// + /// # Example + /// + /// ``` + /// let mut util = Util::new(); + /// util.get_sample_data("data", "example.csv", false); + /// ``` + pub fn get_sample_data(&mut self, handle: &str, matplotlib_fname: &str, as_file_obj: bool) -> &mut Self { + write!( + &mut self.buffer, + "{} = cbook.get_sample_data('{}', as_file_obj={})", + handle, matplotlib_fname, as_file_obj + ) + .unwrap(); + self + } +} + +impl GraphMaker for Util { + /// Returns a reference to the buffer containing the generated commands. + /// + /// # Returns + /// + /// A reference to the buffer as a `String`. + fn get_buffer<'a>(&'a self) -> &'a String { + &self.buffer + } + + /// Clears the buffer, removing all stored commands. + fn clear_buffer(&mut self) { + self.buffer.clear(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::Util; + use crate::GraphMaker; + + #[test] + fn new_works() { + let util = Util::new(); + assert_eq!(util.get_buffer(), ""); + } +} From f5eafaea2e796653ee8e33e6f240ce127f0f835f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 08:54:05 +1000 Subject: [PATCH 03/33] Add test --- src/util.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/util.rs b/src/util.rs index 217a433..5454b9b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -78,4 +78,14 @@ mod tests { let util = Util::new(); assert_eq!(util.get_buffer(), ""); } + + #[test] + fn get_sample_data_works() { + let mut util = Util::new(); + util.get_sample_data("data", "example.csv", false); + assert_eq!( + util.get_buffer(), + "data = cbook.get_sample_data('example.csv', as_file_obj=false)" + ); + } } From 01d7dd5be2f4bc4d653d6df74e2a16acad725174 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 09:18:55 +1000 Subject: [PATCH 04/33] [wip] Impl InsetAxes --- src/inset_axes.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 2 files changed, 162 insertions(+) create mode 100644 src/inset_axes.rs diff --git a/src/inset_axes.rs b/src/inset_axes.rs new file mode 100644 index 0000000..089f893 --- /dev/null +++ b/src/inset_axes.rs @@ -0,0 +1,160 @@ +use super::GraphMaker; +use std::fmt::Write; + +/// Implements the capability to add inset Axes to existing Axes. +pub struct InsetAxes { + xlim: Option<(f64, f64)>, // range for x + ylim: Option<(f64, f64)>, // range for y + extra: String, // extra commands (comma separated) + buffer: String, // buffer +} + +impl InsetAxes { + /// Creates a new `InsetAxes` object with an empty buffer. + /// + /// # Returns + /// + /// A new instance of `InsetAxes`. + pub fn new() -> Self { + Self { + xlim: None, + ylim: None, + extra: String::new(), + buffer: String::new(), + } + } + + /// Draws the `InsetAxes` onto the current Axes. + /// + /// This function generates a command to create an inset Axes within the current Axes. + /// The command is stored in the buffer. + /// + /// # Arguments + /// + /// * `handle` - The name of the **Python variable** that will hold the inset Axes. + /// * `x0` - The x-coordinate of the lower-left corner of the inset Axes. + /// * `y0` - The y-coordinate of the lower-left corner of the inset Axes. + /// * `width` - The width of the inset Axes. + /// * `height` - The height of the inset Axes. + /// + /// The bounds are `(x0, y0, width, height)` where `x0`, `y0` are the lower-left corner of the inset Axes, + /// and `width`, `height` are the width and height of the inset Axes. + /// + /// # Returns + /// + /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. + pub fn draw(&mut self, handle: &str, x0: f64, y0: f64, width: f64, height: f64) -> &mut Self { + let opt = self.options(); + write!( + &mut self.buffer, + "{} = plt.gca().inset_axes([{}, {}, {}, {}]{})\n", + handle, x0, y0, width, height, opt + ) + .unwrap(); + self + } + + /// Sets the x-range for the inset Axes. + /// + /// # Arguments + /// + /// * `xmin` - The minimum x-value. + /// * `xmax` - The maximum x-value. + /// + /// # Returns + /// + /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. + pub fn set_xlim(&mut self, xmin: f64, xmax: f64) -> &mut Self { + self.xlim = Some((xmin, xmax)); + self + } + + /// Sets the y-range for the inset Axes. + /// + /// # Arguments + /// + /// * `ymin` - The minimum y-value. + /// * `ymax` - The maximum y-value. + /// + /// # Returns + /// + /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. + pub fn set_ylim(&mut self, ymin: f64, ymax: f64) -> &mut Self { + self.ylim = Some((ymin, ymax)); + self + } + + /// Sets extra Matplotlib commands (comma separated). + /// + /// # Arguments + /// + /// * `extra` - A string containing extra Matplotlib commands, separated by commas. + /// + /// **Important:** The extra commands must be comma separated. + /// + /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.inset_axes.html#matplotlib.axes.Axes.inset_axes) + /// + /// # Returns + /// + /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. + pub fn set_extra(&mut self, extra: &str) -> &mut Self { + self.extra = extra.to_string(); + self + } + + /// Returns options for the `InsetAxes`. + /// + /// This function generates a string containing the options for the `InsetAxes`, + /// including the x-range, y-range, and any extra commands. + /// + /// # Returns + /// + /// A string containing the options for the `InsetAxes`. + fn options(&self) -> String { + let mut opt = String::new(); + + if let Some((xmin, xmax)) = self.xlim { + write!(&mut opt, ", xlim=({}, {})", xmin, xmax).unwrap(); + } + + if let Some((ymin, ymax)) = self.ylim { + write!(&mut opt, ", ylim=({}, {})", ymin, ymax).unwrap(); + } + + if !self.extra.is_empty() { + write!(&mut opt, ", {}", self.extra).unwrap(); + } + + opt + } +} + +impl GraphMaker for InsetAxes { + /// Returns a reference to the buffer containing the generated commands. + /// + /// # Returns + /// + /// A reference to the buffer as a `String`. + fn get_buffer<'a>(&'a self) -> &'a String { + &self.buffer + } + + /// Clears the buffer, removing all stored commands. + fn clear_buffer(&mut self) { + self.buffer.clear(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::InsetAxes; + use crate::GraphMaker; + + #[test] + fn new_works() { + let inset = InsetAxes::new(); + assert_eq!(inset.get_buffer(), ""); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1c5e06f..e888dca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ mod curve; mod fileio; mod histogram; mod image; +mod inset_axes; mod legend; mod plot; mod slope_icon; @@ -109,6 +110,7 @@ pub use curve::*; use fileio::*; pub use histogram::*; pub use image::*; +pub use inset_axes::*; pub use legend::*; pub use plot::*; pub use slope_icon::*; From 32dc86de601e4ca4cf948fdd0a4dfcfdeaf8a071 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 09:22:24 +1000 Subject: [PATCH 05/33] Add tests --- src/inset_axes.rs | 54 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 089f893..926c56d 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -47,7 +47,7 @@ impl InsetAxes { let opt = self.options(); write!( &mut self.buffer, - "{} = plt.gca().inset_axes([{}, {}, {}, {}]{})\n", + "{} = plt.gca().inset_axes([{},{},{},{}]{})\n", handle, x0, y0, width, height, opt ) .unwrap(); @@ -114,15 +114,15 @@ impl InsetAxes { let mut opt = String::new(); if let Some((xmin, xmax)) = self.xlim { - write!(&mut opt, ", xlim=({}, {})", xmin, xmax).unwrap(); + write!(&mut opt, ",xlim=({},{})", xmin, xmax).unwrap(); } if let Some((ymin, ymax)) = self.ylim { - write!(&mut opt, ", ylim=({}, {})", ymin, ymax).unwrap(); + write!(&mut opt, ",ylim=({},{})", ymin, ymax).unwrap(); } if !self.extra.is_empty() { - write!(&mut opt, ", {}", self.extra).unwrap(); + write!(&mut opt, ",{}", self.extra).unwrap(); } opt @@ -157,4 +157,50 @@ mod tests { let inset = InsetAxes::new(); assert_eq!(inset.get_buffer(), ""); } + + #[test] + fn draw_works() { + let mut inset = InsetAxes::new(); + inset.draw("inset_ax", 0.1, 0.1, 0.4, 0.4); + assert_eq!( + inset.get_buffer(), + "inset_ax = plt.gca().inset_axes([0.1,0.1,0.4,0.4])\n" + ); + } + + #[test] + fn set_xlim_works() { + let mut inset = InsetAxes::new(); + inset.set_xlim(8.0, 9.0); + assert_eq!(inset.options(), ",xlim=(8,9)"); + } + + #[test] + fn set_ylim_works() { + let mut inset = InsetAxes::new(); + inset.set_ylim(3.0, 5.0); + assert_eq!(inset.options(), ",ylim=(3,5)"); + } + + #[test] + fn set_extra_works() { + let mut inset = InsetAxes::new(); + inset.set_extra("aspect='equal'"); + assert_eq!(inset.options(), ",aspect='equal'"); + } + + #[test] + fn options_combined_works() { + let mut inset = InsetAxes::new(); + inset.set_xlim(1.0, 2.0).set_ylim(3.0, 4.0).set_extra("aspect='equal'"); + assert_eq!(inset.options(), ",xlim=(1,2),ylim=(3,4),aspect='equal'"); + } + + #[test] + fn clear_buffer_works() { + let mut inset = InsetAxes::new(); + inset.draw("inset_ax", 0.1, 0.1, 0.4, 0.4); + inset.clear_buffer(); + assert_eq!(inset.get_buffer(), ""); + } } From ad996ad172796cca4a8f4f305d1cadc0648cf302 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 09:28:54 +1000 Subject: [PATCH 06/33] Remove Util --- data/bivariate_normal.npy | Bin 1880 -> 0 bytes src/lib.rs | 2 - src/util.rs | 91 -------------------------------------- 3 files changed, 93 deletions(-) delete mode 100644 data/bivariate_normal.npy delete mode 100644 src/util.rs diff --git a/data/bivariate_normal.npy b/data/bivariate_normal.npy deleted file mode 100644 index b6b8dacdd0daecd53ef685f2a2dc29c98d5c6861..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1880 zcmX|AeLNJ{8m6?@w|k|AjJb2;&S58qN?3txDq(VtzzprVUv_Y441f^WSsc=Xu`qKIa^c$D#jt9aU9vR$&lB zLg^=HL=v9pK(!}Y;EB{|S}ZLnDj=E`5~|pD4vM6Q%J=DEK{26nY`)vp0>9gKhXwu& zer;)XC8WM!i3iBmwDbomR~N~ihZ~C3XZ4}%zny5C?zirrU0wAhA)iNf_0{U#pm~9u=Sn}O;-UsA2dr9o+Gdcy=6|PX zM-V^V8_WtMZjtUm5PtQz)3-ar;hf`EI|pVYgsfrgTD^3Q@4Keoyyh3|^4XvEm~iXp z>?m1!<8yz)Ldte#^A50Y^?RHW>jrO(zQ3SXZ~{a+ea~N*@DItNg(P>Z(4!;qaR#+uP@$%uz zLzbLh)UuQpAlC;xcy0aXzC2(Q{LkoAay&@aJKto@RPt) zH>Y6?wk~bbDA_22FMdB#WLqYL+Pq~#(b=0|RpD7G@n?cFZX4UDCIChzoO<^?`wE`U zlXzjAEJzDEa6kFCUtxyXi?DAVg7&FW45w!p#1y@9-#IB@2!-z>3+7-lvx~>wxd1h{ z-{q7g&H;Ogq*<6Mg=9YFO?t}^2(t=(jHT^>cPb_S;Cu_(nI?6)b_pOf<4^DXNJSL> z5!Iiaspw+tmO*w{8cf$uZ45T$LVJ>PQ)qq%l%Fuh9ZnjCO9K;?jaV6E7Yo%>tLNaO z53@n1a~`myTexrV@4)lf;(jOI1k95<>1LECAYpyb&g#!Zl5N~yr)v1CAYjZA1xRpz2V`x%qD=sc8E5;z#`oDJEd$B$aNwZO3kisnkW z2qvseE7_EnuyIfE6Vtc-U^r`r`^2~c@jcik%HR;n_mwnrzmuRs+$dKtAw`TRejHcz zHM-@Jv>=HRqpbP9;6ILtP}kE8_i%>}6xWn!(U?(zis?bQfiD`7V8riWn?@UYK4j=q zaIG7)x!U*M_3uUN{+dmfJoJ)YqbqhS_GHpK^yRlP1%`Bemo&6hC4D4dG7W|Kd-v_SavZ6Q z^K$!y7hygB_4_mV`&;hVSiLeI{oRJ<^}U_QF78vIQ++2o{Y7eW^Okl*v)mjzRMdn_ zp0-vv+SMUhsm-z1!@nRepFCj&)) Self { - Self { buffer: String::new() } - } - - /// Wraps Matplotlib's `cbook.get_sample_data` function. - /// - /// This function generates a command to retrieve a sample data file from Matplotlib's - /// `mpl-data/sample_data` directory. The command is stored in the buffer. - /// - /// See - /// - /// # Arguments - /// - /// * `handle` - The name of the **Python variable** to assign the result of `cbook.get_sample_data` - /// * `matplotlib_fname` - The filename of the sample data, relative to the `mpl-data/sample_data` directory. - /// * `as_file_obj` - If `true`, returns a file object; otherwise, returns a file path. - /// - /// # Returns - /// - /// A mutable reference to the `Util` instance, allowing for method chaining. - /// - /// # Example - /// - /// ``` - /// let mut util = Util::new(); - /// util.get_sample_data("data", "example.csv", false); - /// ``` - pub fn get_sample_data(&mut self, handle: &str, matplotlib_fname: &str, as_file_obj: bool) -> &mut Self { - write!( - &mut self.buffer, - "{} = cbook.get_sample_data('{}', as_file_obj={})", - handle, matplotlib_fname, as_file_obj - ) - .unwrap(); - self - } -} - -impl GraphMaker for Util { - /// Returns a reference to the buffer containing the generated commands. - /// - /// # Returns - /// - /// A reference to the buffer as a `String`. - fn get_buffer<'a>(&'a self) -> &'a String { - &self.buffer - } - - /// Clears the buffer, removing all stored commands. - fn clear_buffer(&mut self) { - self.buffer.clear(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use super::Util; - use crate::GraphMaker; - - #[test] - fn new_works() { - let util = Util::new(); - assert_eq!(util.get_buffer(), ""); - } - - #[test] - fn get_sample_data_works() { - let mut util = Util::new(); - util.get_sample_data("data", "example.csv", false); - assert_eq!( - util.get_buffer(), - "data = cbook.get_sample_data('example.csv', as_file_obj=false)" - ); - } -} From ed7da4e972dd7c64fc6be816f76c296a6bb2ec53 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 09:47:53 +1000 Subject: [PATCH 07/33] [wip] Test InsetAxes --- src/inset_axes.rs | 39 +++++++++++++++++++--------------- tests/test_inset_axes.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 tests/test_inset_axes.rs diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 926c56d..38334db 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -3,6 +3,7 @@ use std::fmt::Write; /// Implements the capability to add inset Axes to existing Axes. pub struct InsetAxes { + handle: String, // Python's variable name xlim: Option<(f64, f64)>, // range for x ylim: Option<(f64, f64)>, // range for y extra: String, // extra commands (comma separated) @@ -12,11 +13,16 @@ pub struct InsetAxes { impl InsetAxes { /// Creates a new `InsetAxes` object with an empty buffer. /// + /// # Arguments + /// + /// * `handle` - The name of the **Python variable** that will hold the inset Axes. + /// /// # Returns /// /// A new instance of `InsetAxes`. - pub fn new() -> Self { + pub fn new(handle: &str) -> Self { Self { + handle: handle.to_string(), xlim: None, ylim: None, extra: String::new(), @@ -29,9 +35,10 @@ impl InsetAxes { /// This function generates a command to create an inset Axes within the current Axes. /// The command is stored in the buffer. /// + /// **Note::** The `handle` is the name of the **Python variable** that will hold the inset Axes. + /// /// # Arguments /// - /// * `handle` - The name of the **Python variable** that will hold the inset Axes. /// * `x0` - The x-coordinate of the lower-left corner of the inset Axes. /// * `y0` - The y-coordinate of the lower-left corner of the inset Axes. /// * `width` - The width of the inset Axes. @@ -43,12 +50,12 @@ impl InsetAxes { /// # Returns /// /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. - pub fn draw(&mut self, handle: &str, x0: f64, y0: f64, width: f64, height: f64) -> &mut Self { + pub fn draw(&mut self, x0: f64, y0: f64, width: f64, height: f64) -> &mut Self { let opt = self.options(); write!( &mut self.buffer, "{} = plt.gca().inset_axes([{},{},{},{}]{})\n", - handle, x0, y0, width, height, opt + self.handle, x0, y0, width, height, opt ) .unwrap(); self @@ -154,52 +161,50 @@ mod tests { #[test] fn new_works() { - let inset = InsetAxes::new(); + let inset = InsetAxes::new("zoom"); + assert_eq!(inset.handle, "zoom"); assert_eq!(inset.get_buffer(), ""); } #[test] fn draw_works() { - let mut inset = InsetAxes::new(); - inset.draw("inset_ax", 0.1, 0.1, 0.4, 0.4); - assert_eq!( - inset.get_buffer(), - "inset_ax = plt.gca().inset_axes([0.1,0.1,0.4,0.4])\n" - ); + let mut inset = InsetAxes::new("zoom"); + inset.draw(0.1, 0.1, 0.4, 0.4); + assert_eq!(inset.get_buffer(), "zoom = plt.gca().inset_axes([0.1,0.1,0.4,0.4])\n"); } #[test] fn set_xlim_works() { - let mut inset = InsetAxes::new(); + let mut inset = InsetAxes::new("zoom"); inset.set_xlim(8.0, 9.0); assert_eq!(inset.options(), ",xlim=(8,9)"); } #[test] fn set_ylim_works() { - let mut inset = InsetAxes::new(); + let mut inset = InsetAxes::new("zoom"); inset.set_ylim(3.0, 5.0); assert_eq!(inset.options(), ",ylim=(3,5)"); } #[test] fn set_extra_works() { - let mut inset = InsetAxes::new(); + let mut inset = InsetAxes::new("zoom"); inset.set_extra("aspect='equal'"); assert_eq!(inset.options(), ",aspect='equal'"); } #[test] fn options_combined_works() { - let mut inset = InsetAxes::new(); + let mut inset = InsetAxes::new("zoom"); inset.set_xlim(1.0, 2.0).set_ylim(3.0, 4.0).set_extra("aspect='equal'"); assert_eq!(inset.options(), ",xlim=(1,2),ylim=(3,4),aspect='equal'"); } #[test] fn clear_buffer_works() { - let mut inset = InsetAxes::new(); - inset.draw("inset_ax", 0.1, 0.1, 0.4, 0.4); + let mut inset = InsetAxes::new("zoom"); + inset.draw(0.1, 0.1, 0.4, 0.4); inset.clear_buffer(); assert_eq!(inset.get_buffer(), ""); } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs new file mode 100644 index 0000000..3791cb6 --- /dev/null +++ b/tests/test_inset_axes.rs @@ -0,0 +1,46 @@ +use plotpy::{Image, InsetAxes, Plot, StrError}; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +const OUT_DIR: &str = "/tmp/plotpy/integ_tests"; + +#[test] +fn test_inset_axes() -> Result<(), StrError> { + // object and options + let mut inset = InsetAxes::new("zoom"); + + // draw image + let mut plot = Plot::new(); + draw_image(&mut plot); + + // add inset axes + inset.set_xlim(0.0, 1.0).set_ylim(6.0, 5.0).draw(3.0, 4.0, 2.0, 2.0); + plot.add(&inset); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes.svg"); + plot.set_show_errors(true).save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + assert!(lines_iter.count() > 0); + Ok(()) +} + +fn draw_image(plot: &mut Plot) { + let data = [ + [0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0], + [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0], + [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0], + [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0], + [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0], + [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1], + [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3], + ]; + let mut img = Image::new(); + img.set_colormap_name("terrain").set_extra("alpha=0.8").draw(&data); + plot.add(&img); +} From 79d535ff5eedd49fb60e18f94c2115dffaccc3ca Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 10:54:06 +1000 Subject: [PATCH 08/33] Improve InsetAxes --- figures/integ_inset_axes_1.svg | 548 +++++++++++++++++++++++++++++++++ src/inset_axes.rs | 210 ++++--------- tests/test_inset_axes.rs | 49 +-- 3 files changed, 638 insertions(+), 169 deletions(-) create mode 100644 figures/integ_inset_axes_1.svg diff --git a/figures/integ_inset_axes_1.svg b/figures/integ_inset_axes_1.svg new file mode 100644 index 0000000..1fe3e75 --- /dev/null +++ b/figures/integ_inset_axes_1.svg @@ -0,0 +1,548 @@ + + + + + + + + 2025-02-09T10:48:33.069128 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 38334db..bbfb3ce 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -3,135 +3,105 @@ use std::fmt::Write; /// Implements the capability to add inset Axes to existing Axes. pub struct InsetAxes { - handle: String, // Python's variable name - xlim: Option<(f64, f64)>, // range for x - ylim: Option<(f64, f64)>, // range for y - extra: String, // extra commands (comma separated) - buffer: String, // buffer + xmin: f64, + xmax: f64, + ymin: f64, + ymax: f64, + extra_for_axes: String, + extra_for_indicator: String, + buffer: String, } impl InsetAxes { /// Creates a new `InsetAxes` object with an empty buffer. /// - /// # Arguments - /// - /// * `handle` - The name of the **Python variable** that will hold the inset Axes. - /// /// # Returns /// /// A new instance of `InsetAxes`. - pub fn new(handle: &str) -> Self { + pub fn new() -> Self { Self { - handle: handle.to_string(), - xlim: None, - ylim: None, - extra: String::new(), + xmin: 0.0, + xmax: 1.0, + ymin: 0.0, + ymax: 1.0, + extra_for_axes: String::new(), + extra_for_indicator: String::new(), buffer: String::new(), } } - /// Draws the `InsetAxes` onto the current Axes. - /// - /// This function generates a command to create an inset Axes within the current Axes. - /// The command is stored in the buffer. - /// - /// **Note::** The `handle` is the name of the **Python variable** that will hold the inset Axes. - /// - /// # Arguments - /// - /// * `x0` - The x-coordinate of the lower-left corner of the inset Axes. - /// * `y0` - The y-coordinate of the lower-left corner of the inset Axes. - /// * `width` - The width of the inset Axes. - /// * `height` - The height of the inset Axes. - /// - /// The bounds are `(x0, y0, width, height)` where `x0`, `y0` are the lower-left corner of the inset Axes, - /// and `width`, `height` are the width and height of the inset Axes. - /// - /// # Returns - /// - /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. - pub fn draw(&mut self, x0: f64, y0: f64, width: f64, height: f64) -> &mut Self { - let opt = self.options(); - write!( - &mut self.buffer, - "{} = plt.gca().inset_axes([{},{},{},{}]{})\n", - self.handle, x0, y0, width, height, opt - ) - .unwrap(); + /// Adds new graph entity + pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { + let buf0 = graph.get_buffer(); + let buf1 = buf0.replace("plt.gca()", "zoom"); + let buf2 = buf1.replace("plt.imshow", "zoom.imshow"); + self.buffer.push_str(&buf2); self } - /// Sets the x-range for the inset Axes. - /// - /// # Arguments + /// Draws the inset Axes. /// - /// * `xmin` - The minimum x-value. - /// * `xmax` - The maximum x-value. + /// Example of normalized coordinates: `(0.5, 0.5, 0.4, 0.3)`. /// - /// # Returns + /// # Arguments /// - /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. - pub fn set_xlim(&mut self, xmin: f64, xmax: f64) -> &mut Self { - self.xlim = Some((xmin, xmax)); + /// * `u0` -- The normalized (0 to 1) horizontal figure coordinate of the lower-left corner of the inset Axes. + /// * `v0` -- The normalized (0 to 1) vertical figure coordinate of the lower-left corner of the inset Axes. + /// * `width` -- The width of the inset Axes. + /// * `height` -- The height of the inset Axes. + pub fn draw(&mut self, u0: f64, v0: f64, width: f64, height: f64) { + let opt1 = self.options_for_axes(); + let opt2 = self.options_for_indicator(); + self.buffer.insert_str( + 0, + &format!( + "zoom=plt.gca().inset_axes([{},{},{},{}],xlim=({},{}),ylim=({},{}){})\n", + u0, v0, width, height, self.xmin, self.xmax, self.ymin, self.ymax, opt1, + ), + ); + write!(&mut self.buffer, "plt.gca().indicate_inset_zoom(zoom{})\n", opt2,).unwrap(); + } + + /// Sets the limits of axes + pub fn set_range(&mut self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> &mut Self { + self.xmin = xmin; + self.xmax = xmax; + self.ymin = ymin; + self.ymax = ymax; self } - /// Sets the y-range for the inset Axes. - /// - /// # Arguments - /// - /// * `ymin` - The minimum y-value. - /// * `ymax` - The maximum y-value. + /// Sets extra Matplotlib commands for the inset Axes (comma separated). /// - /// # Returns - /// - /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. - pub fn set_ylim(&mut self, ymin: f64, ymax: f64) -> &mut Self { - self.ylim = Some((ymin, ymax)); + /// [See Matplotlib's documentation for extra parameters]() + pub fn set_extra_for_axes(&mut self, extra: &str) -> &mut Self { + self.extra_for_axes = extra.to_string(); self } - /// Sets extra Matplotlib commands (comma separated). - /// - /// # Arguments - /// - /// * `extra` - A string containing extra Matplotlib commands, separated by commas. - /// - /// **Important:** The extra commands must be comma separated. - /// - /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.inset_axes.html#matplotlib.axes.Axes.inset_axes) + /// Sets extra Matplotlib commands for the indicator (comma separated). /// - /// # Returns - /// - /// A mutable reference to the `InsetAxes` instance, allowing for method chaining. - pub fn set_extra(&mut self, extra: &str) -> &mut Self { - self.extra = extra.to_string(); + /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.indicate_inset.html#matplotlib.axes.Axes.indicate_inset) + pub fn set_extra_for_indicator(&mut self, extra: &str) -> &mut Self { + self.extra_for_indicator = extra.to_string(); self } - /// Returns options for the `InsetAxes`. - /// - /// This function generates a string containing the options for the `InsetAxes`, - /// including the x-range, y-range, and any extra commands. - /// - /// # Returns - /// - /// A string containing the options for the `InsetAxes`. - fn options(&self) -> String { + /// Returns options for the inset Axes + fn options_for_axes(&self) -> String { let mut opt = String::new(); - - if let Some((xmin, xmax)) = self.xlim { - write!(&mut opt, ",xlim=({},{})", xmin, xmax).unwrap(); - } - - if let Some((ymin, ymax)) = self.ylim { - write!(&mut opt, ",ylim=({},{})", ymin, ymax).unwrap(); + if !self.extra_for_axes.is_empty() { + write!(&mut opt, ",{}", self.extra_for_axes).unwrap(); } + opt + } - if !self.extra.is_empty() { - write!(&mut opt, ",{}", self.extra).unwrap(); + /// Returns options for the indicator + fn options_for_indicator(&self) -> String { + let mut opt = String::new(); + if !self.extra_for_indicator.is_empty() { + write!(&mut opt, ",{}", self.extra_for_indicator).unwrap(); } - opt } } @@ -158,54 +128,4 @@ impl GraphMaker for InsetAxes { mod tests { use super::InsetAxes; use crate::GraphMaker; - - #[test] - fn new_works() { - let inset = InsetAxes::new("zoom"); - assert_eq!(inset.handle, "zoom"); - assert_eq!(inset.get_buffer(), ""); - } - - #[test] - fn draw_works() { - let mut inset = InsetAxes::new("zoom"); - inset.draw(0.1, 0.1, 0.4, 0.4); - assert_eq!(inset.get_buffer(), "zoom = plt.gca().inset_axes([0.1,0.1,0.4,0.4])\n"); - } - - #[test] - fn set_xlim_works() { - let mut inset = InsetAxes::new("zoom"); - inset.set_xlim(8.0, 9.0); - assert_eq!(inset.options(), ",xlim=(8,9)"); - } - - #[test] - fn set_ylim_works() { - let mut inset = InsetAxes::new("zoom"); - inset.set_ylim(3.0, 5.0); - assert_eq!(inset.options(), ",ylim=(3,5)"); - } - - #[test] - fn set_extra_works() { - let mut inset = InsetAxes::new("zoom"); - inset.set_extra("aspect='equal'"); - assert_eq!(inset.options(), ",aspect='equal'"); - } - - #[test] - fn options_combined_works() { - let mut inset = InsetAxes::new("zoom"); - inset.set_xlim(1.0, 2.0).set_ylim(3.0, 4.0).set_extra("aspect='equal'"); - assert_eq!(inset.options(), ",xlim=(1,2),ylim=(3,4),aspect='equal'"); - } - - #[test] - fn clear_buffer_works() { - let mut inset = InsetAxes::new("zoom"); - inset.draw(0.1, 0.1, 0.4, 0.4); - inset.clear_buffer(); - assert_eq!(inset.get_buffer(), ""); - } } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 3791cb6..cfa0cd6 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -6,31 +6,8 @@ use std::path::Path; const OUT_DIR: &str = "/tmp/plotpy/integ_tests"; #[test] -fn test_inset_axes() -> Result<(), StrError> { - // object and options - let mut inset = InsetAxes::new("zoom"); - +fn test_inset_axes_1() -> Result<(), StrError> { // draw image - let mut plot = Plot::new(); - draw_image(&mut plot); - - // add inset axes - inset.set_xlim(0.0, 1.0).set_ylim(6.0, 5.0).draw(3.0, 4.0, 2.0, 2.0); - plot.add(&inset); - - // save figure - let path = Path::new(OUT_DIR).join("integ_inset_axes.svg"); - plot.set_show_errors(true).save(&path)?; - - // check number of lines - let file = File::open(path).map_err(|_| "cannot open file")?; - let buffered = BufReader::new(file); - let lines_iter = buffered.lines(); - assert!(lines_iter.count() > 0); - Ok(()) -} - -fn draw_image(plot: &mut Plot) { let data = [ [0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0], [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0], @@ -41,6 +18,30 @@ fn draw_image(plot: &mut Plot) { [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3], ]; let mut img = Image::new(); + let mut plot = Plot::new(); img.set_colormap_name("terrain").set_extra("alpha=0.8").draw(&data); plot.add(&img); + + // inset axes + let mut inset = InsetAxes::new(); + inset + .set_extra_for_axes("xticklabels=[],yticklabels=[]") + .set_extra_for_indicator("edgecolor='red',linewidth=2") + .set_range(0.0, 1.0, 5.0, 6.0); + inset.add(&img).draw(0.5, 0.5, 0.4, 0.3); + + // add entities to plot + plot.add(&img).add(&inset); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_1.svg"); + plot.set_show_errors(true).save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + assert!(n > 510 && n < 600); + Ok(()) } From ec05a0c819cb443ef5b4e97dd2780781642f9178 Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 10:59:52 +1000 Subject: [PATCH 09/33] feat: add styling options for inset axes indicators and borders --- src/inset_axes.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index bbfb3ce..d853e5c 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -9,6 +9,11 @@ pub struct InsetAxes { ymax: f64, extra_for_axes: String, extra_for_indicator: String, + indicator_line_style: String, + indicator_line_color: String, + border_line_style: String, + border_line_color: String, + background_color: String, buffer: String, } @@ -26,10 +31,45 @@ impl InsetAxes { ymax: 1.0, extra_for_axes: String::new(), extra_for_indicator: String::new(), + indicator_line_style: String::new(), + indicator_line_color: String::new(), + border_line_style: String::new(), + border_line_color: String::new(), + background_color: String::new(), buffer: String::new(), } } + /// Sets the line style for the indicator (e.g. "--", ":", "-.") + pub fn set_indicator_line_style(&mut self, style: &str) -> &mut Self { + self.indicator_line_style = style.to_string(); + self + } + + /// Sets the line color for the indicator (e.g. "red", "#FF0000") + pub fn set_indicator_line_color(&mut self, color: &str) -> &mut Self { + self.indicator_line_color = color.to_string(); + self + } + + /// Sets the border line style for the inset axes (e.g. "--", ":", "-.") + pub fn set_border_line_style(&mut self, style: &str) -> &mut Self { + self.border_line_style = style.to_string(); + self + } + + /// Sets the border line color for the inset axes (e.g. "red", "#FF0000") + pub fn set_border_line_color(&mut self, color: &str) -> &mut Self { + self.border_line_color = color.to_string(); + self + } + + /// Sets the background color for the inset axes (e.g. "white", "#FFFFFF") + pub fn set_background_color(&mut self, color: &str) -> &mut Self { + self.background_color = color.to_string(); + self + } + /// Adds new graph entity pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { let buf0 = graph.get_buffer(); @@ -90,6 +130,15 @@ impl InsetAxes { /// Returns options for the inset Axes fn options_for_axes(&self) -> String { let mut opt = String::new(); + if !self.border_line_style.is_empty() { + write!(&mut opt, ",linestyle='{}'", self.border_line_style).unwrap(); + } + if !self.border_line_color.is_empty() { + write!(&mut opt, ",edgecolor='{}'", self.border_line_color).unwrap(); + } + if !self.background_color.is_empty() { + write!(&mut opt, ",facecolor='{}'", self.background_color).unwrap(); + } if !self.extra_for_axes.is_empty() { write!(&mut opt, ",{}", self.extra_for_axes).unwrap(); } @@ -99,6 +148,12 @@ impl InsetAxes { /// Returns options for the indicator fn options_for_indicator(&self) -> String { let mut opt = String::new(); + if !self.indicator_line_style.is_empty() { + write!(&mut opt, ",linestyle='{}'", self.indicator_line_style).unwrap(); + } + if !self.indicator_line_color.is_empty() { + write!(&mut opt, ",edgecolor='{}'", self.indicator_line_color).unwrap(); + } if !self.extra_for_indicator.is_empty() { write!(&mut opt, ",{}", self.extra_for_indicator).unwrap(); } From 32c35525a00ba843008070117ad15c797d003015 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 11:01:06 +1000 Subject: [PATCH 10/33] Ignore aider config --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 348c64b..acdea29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ rust_modules /target Cargo.lock -call_python3_works.py \ No newline at end of file +call_python3_works.py +.aider* From 000ad7571b97c445063074907045a30d95dde03b Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 11:12:55 +1000 Subject: [PATCH 11/33] refactor: remove border styling from InsetAxes --- src/inset_axes.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index d853e5c..bc35db1 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -11,8 +11,6 @@ pub struct InsetAxes { extra_for_indicator: String, indicator_line_style: String, indicator_line_color: String, - border_line_style: String, - border_line_color: String, background_color: String, buffer: String, } @@ -52,18 +50,6 @@ impl InsetAxes { self } - /// Sets the border line style for the inset axes (e.g. "--", ":", "-.") - pub fn set_border_line_style(&mut self, style: &str) -> &mut Self { - self.border_line_style = style.to_string(); - self - } - - /// Sets the border line color for the inset axes (e.g. "red", "#FF0000") - pub fn set_border_line_color(&mut self, color: &str) -> &mut Self { - self.border_line_color = color.to_string(); - self - } - /// Sets the background color for the inset axes (e.g. "white", "#FFFFFF") pub fn set_background_color(&mut self, color: &str) -> &mut Self { self.background_color = color.to_string(); @@ -130,12 +116,6 @@ impl InsetAxes { /// Returns options for the inset Axes fn options_for_axes(&self) -> String { let mut opt = String::new(); - if !self.border_line_style.is_empty() { - write!(&mut opt, ",linestyle='{}'", self.border_line_style).unwrap(); - } - if !self.border_line_color.is_empty() { - write!(&mut opt, ",edgecolor='{}'", self.border_line_color).unwrap(); - } if !self.background_color.is_empty() { write!(&mut opt, ",facecolor='{}'", self.background_color).unwrap(); } From d01e2a92da2079ab6fceb11234949f04e25c4f21 Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 11:14:08 +1000 Subject: [PATCH 12/33] refactor: remove background_color from InsetAxes --- src/inset_axes.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index bc35db1..d0f3c16 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -11,7 +11,6 @@ pub struct InsetAxes { extra_for_indicator: String, indicator_line_style: String, indicator_line_color: String, - background_color: String, buffer: String, } @@ -50,12 +49,6 @@ impl InsetAxes { self } - /// Sets the background color for the inset axes (e.g. "white", "#FFFFFF") - pub fn set_background_color(&mut self, color: &str) -> &mut Self { - self.background_color = color.to_string(); - self - } - /// Adds new graph entity pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { let buf0 = graph.get_buffer(); @@ -116,9 +109,6 @@ impl InsetAxes { /// Returns options for the inset Axes fn options_for_axes(&self) -> String { let mut opt = String::new(); - if !self.background_color.is_empty() { - write!(&mut opt, ",facecolor='{}'", self.background_color).unwrap(); - } if !self.extra_for_axes.is_empty() { write!(&mut opt, ",{}", self.extra_for_axes).unwrap(); } From c9f66aeb7326932ed2e7230f00e417c1c2e3b924 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 11:16:48 +1000 Subject: [PATCH 13/33] Fix aider's mistakes --- src/inset_axes.rs | 3 --- tests/test_inset_axes.rs | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index d0f3c16..185bdb2 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -30,9 +30,6 @@ impl InsetAxes { extra_for_indicator: String::new(), indicator_line_style: String::new(), indicator_line_color: String::new(), - border_line_style: String::new(), - border_line_color: String::new(), - background_color: String::new(), buffer: String::new(), } } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index cfa0cd6..c57c135 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -25,8 +25,10 @@ fn test_inset_axes_1() -> Result<(), StrError> { // inset axes let mut inset = InsetAxes::new(); inset + .set_indicator_line_color("red") + .set_indicator_line_style("--") .set_extra_for_axes("xticklabels=[],yticklabels=[]") - .set_extra_for_indicator("edgecolor='red',linewidth=2") + .set_extra_for_indicator("label='INDICATOR',linewidth=2") .set_range(0.0, 1.0, 5.0, 6.0); inset.add(&img).draw(0.5, 0.5, 0.4, 0.3); From c1a4938cb7f0f7b571324a69ecaac2806c71826d Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 11:20:23 +1000 Subject: [PATCH 14/33] feat: add indicator line width support to InsetAxes --- src/inset_axes.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 185bdb2..652f075 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -11,6 +11,7 @@ pub struct InsetAxes { extra_for_indicator: String, indicator_line_style: String, indicator_line_color: String, + indicator_line_width: f64, buffer: String, } @@ -30,6 +31,7 @@ impl InsetAxes { extra_for_indicator: String::new(), indicator_line_style: String::new(), indicator_line_color: String::new(), + indicator_line_width: 0.0, buffer: String::new(), } } @@ -46,6 +48,12 @@ impl InsetAxes { self } + /// Sets the line width for the indicator + pub fn set_indicator_line_width(&mut self, width: f64) -> &mut Self { + self.indicator_line_width = width; + self + } + /// Adds new graph entity pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { let buf0 = graph.get_buffer(); @@ -121,6 +129,9 @@ impl InsetAxes { if !self.indicator_line_color.is_empty() { write!(&mut opt, ",edgecolor='{}'", self.indicator_line_color).unwrap(); } + if self.indicator_line_width > 0.0 { + write!(&mut opt, ",linewidth={}", self.indicator_line_width).unwrap(); + } if !self.extra_for_indicator.is_empty() { write!(&mut opt, ",{}", self.extra_for_indicator).unwrap(); } From 5419c53071af118f550ab8ba772d7903645e5be9 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 11:27:08 +1000 Subject: [PATCH 15/33] Improve setting options for the indicator in InsetAxes --- src/inset_axes.rs | 28 ++++++++++++++++++++++++++++ tests/test_inset_axes.rs | 4 +++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 652f075..8274665 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -12,6 +12,7 @@ pub struct InsetAxes { indicator_line_style: String, indicator_line_color: String, indicator_line_width: f64, + indicator_hatch: String, buffer: String, } @@ -32,6 +33,7 @@ impl InsetAxes { indicator_line_style: String::new(), indicator_line_color: String::new(), indicator_line_width: 0.0, + indicator_hatch: String::new(), buffer: String::new(), } } @@ -54,6 +56,29 @@ impl InsetAxes { self } + /// Sets the hatch pattern for the indicator (e.g. "/", "\\", "|", "-", "+", "x", "o", "O", ".", "*") + /// + /// Common hatch patterns include: + /// + /// * "/" - diagonal hatching + /// * "\" - back diagonal hatching + /// * "|" - vertical hatching + /// * "-" - horizontal hatching + /// * "+" - crossed hatching + /// * "x" - crossed diagonal hatching + /// * "o" - small circle hatching + /// * "O" - large circle hatching + /// * "." - dot hatching + /// * "*" - star hatching + /// + /// [See options in ](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.indicate_inset.html#matplotlib.axes.Axes.indicate_inset) + /// + /// [See Matplotlib's documentation for more hatch patterns](https://matplotlib.org/stable/gallery/shapes_and_collections/hatch_demo.html) + pub fn set_indicator_hatch(&mut self, hatch: &str) -> &mut Self { + self.indicator_hatch = hatch.to_string(); + self + } + /// Adds new graph entity pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { let buf0 = graph.get_buffer(); @@ -132,6 +157,9 @@ impl InsetAxes { if self.indicator_line_width > 0.0 { write!(&mut opt, ",linewidth={}", self.indicator_line_width).unwrap(); } + if !self.indicator_hatch.is_empty() { + write!(&mut opt, ",hatch='{}'", self.indicator_hatch).unwrap(); + } if !self.extra_for_indicator.is_empty() { write!(&mut opt, ",{}", self.extra_for_indicator).unwrap(); } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index c57c135..f6f95dd 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -27,8 +27,10 @@ fn test_inset_axes_1() -> Result<(), StrError> { inset .set_indicator_line_color("red") .set_indicator_line_style("--") + .set_indicator_line_width(2.0) + .set_indicator_hatch("x") .set_extra_for_axes("xticklabels=[],yticklabels=[]") - .set_extra_for_indicator("label='INDICATOR',linewidth=2") + .set_extra_for_indicator("label='INDICATOR',visible=True") .set_range(0.0, 1.0, 5.0, 6.0); inset.add(&img).draw(0.5, 0.5, 0.4, 0.3); From fa96c1e1bf31822c023d9596cad88334c9947dcd Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 11:35:21 +1000 Subject: [PATCH 16/33] Impl set indicator alpha --- src/inset_axes.rs | 11 +++++++++++ tests/test_inset_axes.rs | 1 + 2 files changed, 12 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 8274665..f0d84f1 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -13,6 +13,7 @@ pub struct InsetAxes { indicator_line_color: String, indicator_line_width: f64, indicator_hatch: String, + indicator_alpha: Option, buffer: String, } @@ -34,6 +35,7 @@ impl InsetAxes { indicator_line_color: String::new(), indicator_line_width: 0.0, indicator_hatch: String::new(), + indicator_alpha: None, buffer: String::new(), } } @@ -56,6 +58,12 @@ impl InsetAxes { self } + /// Sets the alpha (opacity) for the indicator + pub fn set_indicator_alpha(&mut self, alpha: f64) -> &mut Self { + self.indicator_alpha = Some(alpha); + self + } + /// Sets the hatch pattern for the indicator (e.g. "/", "\\", "|", "-", "+", "x", "o", "O", ".", "*") /// /// Common hatch patterns include: @@ -160,6 +168,9 @@ impl InsetAxes { if !self.indicator_hatch.is_empty() { write!(&mut opt, ",hatch='{}'", self.indicator_hatch).unwrap(); } + if let Some(alpha) = self.indicator_alpha { + write!(&mut opt, ",alpha={}", alpha).unwrap(); + } if !self.extra_for_indicator.is_empty() { write!(&mut opt, ",{}", self.extra_for_indicator).unwrap(); } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index f6f95dd..1a9ddcb 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -28,6 +28,7 @@ fn test_inset_axes_1() -> Result<(), StrError> { .set_indicator_line_color("red") .set_indicator_line_style("--") .set_indicator_line_width(2.0) + .set_indicator_alpha(1.0) .set_indicator_hatch("x") .set_extra_for_axes("xticklabels=[],yticklabels=[]") .set_extra_for_indicator("label='INDICATOR',visible=True") From 17c111d0d032df7106b3fa690cf393f0c4643f6b Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 12:00:26 +1000 Subject: [PATCH 17/33] feat: Add hide_ticks method to InsetAxes to remove axis ticks --- src/inset_axes.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index f0d84f1..6db8f60 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -144,6 +144,12 @@ impl InsetAxes { self } + /// Hides both x and y axis ticks on the inset axes + pub fn hide_ticks(&mut self) -> &mut Self { + self.buffer.push_str("zoom.set_xticks([])\nzoom.set_yticks([])\n"); + self + } + /// Returns options for the inset Axes fn options_for_axes(&self) -> String { let mut opt = String::new(); From bdd7c51e39b40bd5b09e41b5a3e83a94629f5491 Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 12:12:18 +1000 Subject: [PATCH 18/33] feat: Add set_axes_visibility method with visibility state to InsetAxes --- src/inset_axes.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 6db8f60..35a6746 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -14,6 +14,7 @@ pub struct InsetAxes { indicator_line_width: f64, indicator_hatch: String, indicator_alpha: Option, + axes_visible: bool, buffer: String, } @@ -36,6 +37,7 @@ impl InsetAxes { indicator_line_width: 0.0, indicator_hatch: String::new(), indicator_alpha: None, + axes_visible: false, buffer: String::new(), } } @@ -144,9 +146,16 @@ impl InsetAxes { self } - /// Hides both x and y axis ticks on the inset axes - pub fn hide_ticks(&mut self) -> &mut Self { - self.buffer.push_str("zoom.set_xticks([])\nzoom.set_yticks([])\n"); + /// Sets the visibility of the axes ticks + /// + /// # Arguments + /// + /// * `visible` - If true, shows the axes ticks. If false, hides them. + pub fn set_axes_visibility(&mut self, visible: bool) -> &mut Self { + self.axes_visible = visible; + if !visible { + self.buffer.push_str("zoom.set_xticks([])\nzoom.set_yticks([])\n"); + } self } From 3195a04c43b6f393929a936810b5baebcd3ec649 Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 12:14:17 +1000 Subject: [PATCH 19/33] refactor: Move axes visibility logic from `set_axes_visibility` to `options_for_axes` --- src/inset_axes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 35a6746..f222e14 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -153,9 +153,6 @@ impl InsetAxes { /// * `visible` - If true, shows the axes ticks. If false, hides them. pub fn set_axes_visibility(&mut self, visible: bool) -> &mut Self { self.axes_visible = visible; - if !visible { - self.buffer.push_str("zoom.set_xticks([])\nzoom.set_yticks([])\n"); - } self } @@ -165,6 +162,9 @@ impl InsetAxes { if !self.extra_for_axes.is_empty() { write!(&mut opt, ",{}", self.extra_for_axes).unwrap(); } + if !self.axes_visible { + write!(&mut opt, "\nzoom.set_xticks([])\nzoom.set_yticks([])\n").unwrap(); + } opt } From 8119b46a17551921655ab0c79c5c9f9eed331c07 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 12:19:02 +1000 Subject: [PATCH 20/33] Fix setting of axes visibility --- src/inset_axes.rs | 10 +++++----- tests/test_inset_axes.rs | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index f222e14..8e17d42 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -118,6 +118,9 @@ impl InsetAxes { u0, v0, width, height, self.xmin, self.xmax, self.ymin, self.ymax, opt1, ), ); + if !self.axes_visible { + write!(&mut self.buffer, "zoom.set_xticks([])\nzoom.set_yticks([])\n").unwrap(); + } write!(&mut self.buffer, "plt.gca().indicate_inset_zoom(zoom{})\n", opt2,).unwrap(); } @@ -147,9 +150,9 @@ impl InsetAxes { } /// Sets the visibility of the axes ticks - /// + /// /// # Arguments - /// + /// /// * `visible` - If true, shows the axes ticks. If false, hides them. pub fn set_axes_visibility(&mut self, visible: bool) -> &mut Self { self.axes_visible = visible; @@ -162,9 +165,6 @@ impl InsetAxes { if !self.extra_for_axes.is_empty() { write!(&mut opt, ",{}", self.extra_for_axes).unwrap(); } - if !self.axes_visible { - write!(&mut opt, "\nzoom.set_xticks([])\nzoom.set_yticks([])\n").unwrap(); - } opt } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 1a9ddcb..c48af1e 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -30,7 +30,8 @@ fn test_inset_axes_1() -> Result<(), StrError> { .set_indicator_line_width(2.0) .set_indicator_alpha(1.0) .set_indicator_hatch("x") - .set_extra_for_axes("xticklabels=[],yticklabels=[]") + .set_axes_visibility(false) + .set_extra_for_axes("title='ZOOM'") .set_extra_for_indicator("label='INDICATOR',visible=True") .set_range(0.0, 1.0, 5.0, 6.0); inset.add(&img).draw(0.5, 0.5, 0.4, 0.3); @@ -47,6 +48,6 @@ fn test_inset_axes_1() -> Result<(), StrError> { let buffered = BufReader::new(file); let lines_iter = buffered.lines(); let n = lines_iter.count().clone(); - assert!(n > 510 && n < 600); + assert!(n > 540 && n < 640); Ok(()) } From 8f1e74b0fb85bbf42afa314fc65024ab4d92c4fb Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 12:20:14 +1000 Subject: [PATCH 21/33] feat: Add title setting functionality for inset axes --- src/inset_axes.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 8e17d42..55a3526 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -15,6 +15,7 @@ pub struct InsetAxes { indicator_hatch: String, indicator_alpha: Option, axes_visible: bool, + title: String, buffer: String, } @@ -38,6 +39,7 @@ impl InsetAxes { indicator_hatch: String::new(), indicator_alpha: None, axes_visible: false, + title: String::new(), buffer: String::new(), } } @@ -121,6 +123,9 @@ impl InsetAxes { if !self.axes_visible { write!(&mut self.buffer, "zoom.set_xticks([])\nzoom.set_yticks([])\n").unwrap(); } + if !self.title.is_empty() { + write!(&mut self.buffer, "zoom.set_title(r'{}')\n", self.title).unwrap(); + } write!(&mut self.buffer, "plt.gca().indicate_inset_zoom(zoom{})\n", opt2,).unwrap(); } @@ -159,6 +164,12 @@ impl InsetAxes { self } + /// Sets the title of the inset axes + pub fn set_title(&mut self, title: &str) -> &mut Self { + self.title = title.to_string(); + self + } + /// Returns options for the inset Axes fn options_for_axes(&self) -> String { let mut opt = String::new(); From 14a135f8f58ce8e3d0aed67090914bd4c43fcfce Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 12:26:11 +1000 Subject: [PATCH 22/33] Improve testing of InsetAxes --- src/inset_axes.rs | 2 +- tests/test_inset_axes.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 55a3526..d8b372e 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -159,7 +159,7 @@ impl InsetAxes { /// # Arguments /// /// * `visible` - If true, shows the axes ticks. If false, hides them. - pub fn set_axes_visibility(&mut self, visible: bool) -> &mut Self { + pub fn set_visibility(&mut self, visible: bool) -> &mut Self { self.axes_visible = visible; self } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index c48af1e..0ad32d1 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -25,13 +25,14 @@ fn test_inset_axes_1() -> Result<(), StrError> { // inset axes let mut inset = InsetAxes::new(); inset + .set_title("ZOOM") + .set_visibility(true) .set_indicator_line_color("red") .set_indicator_line_style("--") .set_indicator_line_width(2.0) .set_indicator_alpha(1.0) .set_indicator_hatch("x") - .set_axes_visibility(false) - .set_extra_for_axes("title='ZOOM'") + .set_extra_for_axes("xlabel='X',ylabel='Y'") .set_extra_for_indicator("label='INDICATOR',visible=True") .set_range(0.0, 1.0, 5.0, 6.0); inset.add(&img).draw(0.5, 0.5, 0.4, 0.3); @@ -48,6 +49,6 @@ fn test_inset_axes_1() -> Result<(), StrError> { let buffered = BufReader::new(file); let lines_iter = buffered.lines(); let n = lines_iter.count().clone(); - assert!(n > 540 && n < 640); + assert!(n > 680 && n < 800); Ok(()) } From 2a4de70de1f3489494aa75fbfb93dce787812440 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 12:26:32 +1000 Subject: [PATCH 23/33] Update test figure --- figures/integ_inset_axes_1.svg | 260 ++++++++++++++++++++++++++++----- 1 file changed, 227 insertions(+), 33 deletions(-) diff --git a/figures/integ_inset_axes_1.svg b/figures/integ_inset_axes_1.svg index 1fe3e75..060a89d 100644 --- a/figures/integ_inset_axes_1.svg +++ b/figures/integ_inset_axes_1.svg @@ -6,7 +6,7 @@ - 2025-02-09T10:48:33.069128 + 2025-02-09T12:25:33.542424 image/svg+xml @@ -37,20 +37,20 @@ L 20.5625 7.2 z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAAXIAAAFyCAYAAADoJFEJAAAGlElEQVR4nO3Wv4sQdBjHcc+7QIJAaLGmaxEcHMLIqZCGhqApWhpaG6NuiIRqELmCfoBEEDRlbqYg5XwEIQlBS4QNx221JJEUQRn9C0FfeHjX6/UXfOCBN8/Gyccu7h/6jzn20TfTE5a7/dzx6QlLff3WH9MTljv1yj3TE5Y7fPG76QnL3Tzx3vSE5Q5PDwDg3xFygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoC4jUOfXd6fHrHc78emFyx3avfb6Qn8D9348u70hOUuvf3j9ITlfOQAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhC3dd9Xp6c3LHfkmd3pCcsdnHlxesJSF85emZ6w3N6dO9MTlrv759npCcu9f/XS9ITlfOQAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhC3cf/Orf3pEatt7+1NT1ju5+3t6QlLHT04mJ6w3Bc3/5qesNzjj/r1ClwJIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHitn56eHN6w3Iv7/wwPWG5cx8/Oz1hqTce+XR6wnJnbt07PWG5F576ZXrCch9ePz09YTkfOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlA3Nb5Jz6Z3rDclacfmJ6w3LWd89MTlnryt3enJyz3+YMfTE9Y7vXrJ6Yn8A/4yAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIG7jyJu396dHrPba8xemJyx3dHNzesJSJ2/8Oj1huZfOPTQ9Ybndne+nJyz36jvHpycs5yMHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoA4IQeIE3KAOCEHiBNygDghB4gTcoC4vwF4qEn8fLiriAAAAABJRU5ErkJggg==" id="image1757c554c2" transform="scale(1 -1) translate(0 -266.4)" x="21" y="-6.790125" width="266.4" height="266.4"/> - - + @@ -86,7 +86,7 @@ z - + @@ -115,7 +115,7 @@ z - + @@ -154,7 +154,7 @@ z - + @@ -201,7 +201,7 @@ z - + @@ -235,7 +235,7 @@ z - + @@ -275,7 +275,7 @@ z - + @@ -322,12 +322,12 @@ z - - + @@ -340,7 +340,7 @@ L -3.5 0 - + @@ -353,7 +353,7 @@ L -3.5 0 - + @@ -366,7 +366,7 @@ L -3.5 0 - + @@ -379,7 +379,7 @@ L -3.5 0 - + @@ -392,7 +392,7 @@ L -3.5 0 - + @@ -405,7 +405,7 @@ L -3.5 0 - + @@ -441,19 +441,18 @@ L 286.6745 7.2 L 77.5865 216.288 L 77.5865 254.304 L 39.5705 254.304 -L 39.5705 216.288 z -" clip-path="url(#p1f719a8b85)" style="fill: none; opacity: 0.5; stroke: #ff0000; stroke-width: 2; stroke-linejoin: miter"/> +" clip-path="url(#p60e1959425)" style="fill: url(#hfd821a12a3); stroke-dasharray: 7.4,3.2; stroke-dashoffset: 0; stroke: #ff0000; stroke-width: 2; stroke-linejoin: miter"/> +" style="fill: none; stroke: #ff0000; stroke-linecap: round"/> +" style="fill: none; stroke: #ff0000; stroke-linecap: round"/> @@ -464,55 +463,155 @@ L 166.9241 60.4224 z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAAG8AAABvCAYAAADixZ5gAAABOklEQVR4nO3TwQnCUAAFwSgpIk1YqD2khIAXy0g/8WgPwuezMlPBg+XdlvdxLv/qs81eMNR99gB+J16YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl6YeGHihYkXJl7Y+nheszcMs+2v2ROG8rww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLECxMvTLww8cLEC/sCbC4HLJuSz9wAAAAASUVORK5CYII=" id="image142bb4c129" transform="scale(1 -1) translate(0 -79.92)" x="166.9241" y="-60.336" width="79.92" height="79.92"/> - + + + + + + + + + + + + - + + + + + + + + + - + + + + + + + + + + + + + + + + + + - + + + + + + + + + - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + From 114edbe10dc8759b0fea8c550224111a6d502fea Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 12:27:12 +1000 Subject: [PATCH 24/33] test: Add comprehensive unit tests for InsetAxes struct --- src/inset_axes.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index d8b372e..399621b 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -226,4 +226,75 @@ impl GraphMaker for InsetAxes { mod tests { use super::InsetAxes; use crate::GraphMaker; + + #[test] + fn test_new() { + let inset = InsetAxes::new(); + assert_eq!(inset.xmin, 0.0); + assert_eq!(inset.xmax, 1.0); + assert_eq!(inset.ymin, 0.0); + assert_eq!(inset.ymax, 1.0); + assert!(inset.buffer.is_empty()); + } + + #[test] + fn test_set_range() { + let mut inset = InsetAxes::new(); + inset.set_range(-1.0, 2.0, -3.0, 4.0); + assert_eq!(inset.xmin, -1.0); + assert_eq!(inset.xmax, 2.0); + assert_eq!(inset.ymin, -3.0); + assert_eq!(inset.ymax, 4.0); + } + + #[test] + fn test_set_title() { + let mut inset = InsetAxes::new(); + inset.set_title("Test Title"); + assert_eq!(inset.title, "Test Title"); + } + + #[test] + fn test_set_visibility() { + let mut inset = InsetAxes::new(); + inset.set_visibility(true); + assert!(inset.axes_visible); + inset.set_visibility(false); + assert!(!inset.axes_visible); + } + + #[test] + fn test_indicator_options() { + let mut inset = InsetAxes::new(); + inset.set_indicator_line_style("--") + .set_indicator_line_color("red") + .set_indicator_line_width(2.0) + .set_indicator_hatch("/") + .set_indicator_alpha(0.5); + + let options = inset.options_for_indicator(); + assert!(options.contains("linestyle='--'")); + assert!(options.contains("edgecolor='red'")); + assert!(options.contains("linewidth=2")); + assert!(options.contains("hatch='/'")); + assert!(options.contains("alpha=0.5")); + } + + #[test] + fn test_draw_basic() { + let mut inset = InsetAxes::new(); + inset.draw(0.5, 0.5, 0.4, 0.3); + let buffer = inset.get_buffer(); + assert!(buffer.contains("zoom=plt.gca().inset_axes([0.5,0.5,0.4,0.3]")); + assert!(buffer.contains("plt.gca().indicate_inset_zoom(zoom")); + } + + #[test] + fn test_clear_buffer() { + let mut inset = InsetAxes::new(); + inset.draw(0.5, 0.5, 0.4, 0.3); + assert!(!inset.buffer.is_empty()); + inset.clear_buffer(); + assert!(inset.buffer.is_empty()); + } } From 46909cb3b739b94bd9e18a9e7b80d470d1a9fdbd Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 14:58:29 +1000 Subject: [PATCH 25/33] test: add barplot test case to test_inset_axes.rs --- tests/test_inset_axes.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 0ad32d1..87cd6c1 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{Image, InsetAxes, Plot, StrError}; +use plotpy::{Barplot, Image, InsetAxes, Plot, StrError}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -52,3 +52,40 @@ fn test_inset_axes_1() -> Result<(), StrError> { assert!(n > 680 && n < 800); Ok(()) } + +#[test] +fn test_inset_axes_2() -> Result<(), StrError> { + // draw bar plot + let x = [0, 1, 2, 3, 4]; + let y = [5, 4, 3, 2, 1]; + let mut bar = Barplot::new(); + let mut plot = Plot::new(); + bar.set_label("Main Bars") + .set_colors(&["red", "green", "blue", "orange", "purple"]) + .draw(&x, &y); + plot.add(&bar); + + // inset axes + let mut inset = InsetAxes::new(); + inset.set_range(0.5, 2.5, 2.0, 4.5); + + // bar plot to the inset + let mut inset_bar = Barplot::new(); + inset_bar.set_colors(&["cyan", "magenta"]).draw(&[0, 1], &[2, 3]); + inset.add(&inset_bar).draw(0.65, 0.65, 0.335, 0.33); + + // add entities to plot + plot.add(&bar).add(&inset); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_2.svg"); + plot.set_show_errors(true).save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + // assert!(n > 680 && n < 800); + Ok(()) +} From a151864229d35252e95c3376889c34713b4eea9e Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 15:15:19 +1000 Subject: [PATCH 26/33] Handle Barplot in InsetAxes --- src/inset_axes.rs | 16 ++++++++----- tests/test_inset_axes.rs | 50 +++++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 399621b..c45464f 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -93,10 +93,13 @@ impl InsetAxes { /// Adds new graph entity pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { - let buf0 = graph.get_buffer(); - let buf1 = buf0.replace("plt.gca()", "zoom"); - let buf2 = buf1.replace("plt.imshow", "zoom.imshow"); - self.buffer.push_str(&buf2); + let buf = graph + .get_buffer() + .replace("plt.gca()", "zoom") + .replace("plt.barh", "zoom.barh") + .replace("plt.bar", "zoom.bar") + .replace("plt.imshow", "zoom.imshow"); + self.buffer.push_str(&buf); self } @@ -266,12 +269,13 @@ mod tests { #[test] fn test_indicator_options() { let mut inset = InsetAxes::new(); - inset.set_indicator_line_style("--") + inset + .set_indicator_line_style("--") .set_indicator_line_color("red") .set_indicator_line_width(2.0) .set_indicator_hatch("/") .set_indicator_alpha(0.5); - + let options = inset.options_for_indicator(); assert!(options.contains("linestyle='--'")); assert!(options.contains("edgecolor='red'")); diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 87cd6c1..0943bbe 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -55,37 +55,51 @@ fn test_inset_axes_1() -> Result<(), StrError> { #[test] fn test_inset_axes_2() -> Result<(), StrError> { - // draw bar plot + // data let x = [0, 1, 2, 3, 4]; let y = [5, 4, 3, 2, 1]; - let mut bar = Barplot::new(); - let mut plot = Plot::new(); - bar.set_label("Main Bars") - .set_colors(&["red", "green", "blue", "orange", "purple"]) - .draw(&x, &y); - plot.add(&bar); - // inset axes - let mut inset = InsetAxes::new(); - inset.set_range(0.5, 2.5, 2.0, 4.5); + // define a function to draw with vertical and horizontal bars + let draw = |plot: &mut Plot, horizontal: bool| { + // allocate the Barplot and InsetAxes instances + let mut bar = Barplot::new(); + let mut inset = InsetAxes::new(); - // bar plot to the inset - let mut inset_bar = Barplot::new(); - inset_bar.set_colors(&["cyan", "magenta"]).draw(&[0, 1], &[2, 3]); - inset.add(&inset_bar).draw(0.65, 0.65, 0.335, 0.33); + // configure the barplot + bar.set_horizontal(horizontal).draw(&x, &y); - // add entities to plot - plot.add(&bar).add(&inset); + // configure the inset axes + inset.set_range(0.5, 2.5, 2.0, 4.5); + + // add barplot to inset + inset.add(&bar).draw(0.65, 0.65, 0.335, 0.33); + + // add barplot and inset to plot + plot.add(&bar).add(&inset); + }; + + // allocate plot and add each type of figure to a subplot + let mut plot = Plot::new(); + + // vertical bars + plot.set_subplot(1, 2, 1); + draw(&mut plot, false); + + // horizontal bars + plot.set_subplot(1, 2, 2); + draw(&mut plot, true); // save figure let path = Path::new(OUT_DIR).join("integ_inset_axes_2.svg"); - plot.set_show_errors(true).save(&path)?; + plot.set_figure_size_points(650.0, 250.0) + .set_show_errors(true) + .save(&path)?; // check number of lines let file = File::open(path).map_err(|_| "cannot open file")?; let buffered = BufReader::new(file); let lines_iter = buffered.lines(); let n = lines_iter.count().clone(); - // assert!(n > 680 && n < 800); + assert!(n > 790 && n < 850); Ok(()) } From 0d49287e52bb68674f86338f3355274f6eef13e2 Mon Sep 17 00:00:00 2001 From: "Dorival Pedroso (aider)" Date: Sun, 9 Feb 2025 15:38:15 +1000 Subject: [PATCH 27/33] (no commit message provided) --- tests/test_inset_axes.rs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 0943bbe..f4942f9 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{Barplot, Image, InsetAxes, Plot, StrError}; +use plotpy::{Barplot, Canvas, Image, InsetAxes, Plot, StrError}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -103,3 +103,41 @@ fn test_inset_axes_2() -> Result<(), StrError> { assert!(n > 790 && n < 850); Ok(()) } + +#[test] +fn test_inset_axes_3() -> Result<(), StrError> { + // canvas + let mut canvas = Canvas::new(); + canvas + .set_face_color("None") + .set_edge_color("red") + .draw_circle(0.5, 0.5, 0.45); + + // inset axes + let mut inset = InsetAxes::new(); + inset + .set_indicator_alpha(1.0) + .set_indicator_line_color("blue") + .set_range(0.5, 1.0, 0.5, 1.0) + .add(&canvas) + .draw(0.65, 0.65, 0.335, 0.33); + + // add to plot + let mut plot = Plot::new(); + plot.add(&canvas).add(&inset); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_3.svg"); + plot.set_range(0.0, 2.0, 0.0, 2.0) + .set_equal_axes(true) + .set_show_errors(true) + .save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + assert!(n > 680 && n < 800); + Ok(()) +} From b816566777d92ae7417f93283a414c7c2563f408 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 16:21:58 +1000 Subject: [PATCH 28/33] Handle Curve in InsetAxes. Improve doc examples --- README.md | 70 ++- figures/doc_inset_axes_add.svg | 450 ++++++++++++++++++ figures/integ_inset_axes_2.svg | 819 +++++++++++++++++++++++++++++++++ figures/integ_inset_axes_3.svg | 560 ++++++++++++++++++++++ src/inset_axes.rs | 90 +++- tests/test_inset_axes.rs | 5 +- 6 files changed, 1971 insertions(+), 23 deletions(-) create mode 100644 figures/doc_inset_axes_add.svg create mode 100644 figures/integ_inset_axes_2.svg create mode 100644 figures/integ_inset_axes_3.svg diff --git a/README.md b/README.md index 9a34a32..a62b660 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [Curve](#curve) - [Histogram](#histogram) - [Image](#image) + - [InsetAxes](#insetaxes) - [Surface](#surface) - [Text](#text) @@ -126,8 +127,9 @@ fn main() -> Result<(), StrError> { plot.set_inv_y() .add(&bar) .set_title("Fruits") - .set_label_x("price") - .save("/tmp/plotpy/doc_tests/doc_barplot_3.svg")?; + .set_label_x("price"); + + // plot.save("/tmp/plotpy/doc_tests/doc_barplot_3.svg")?; Ok(()) } ``` @@ -166,8 +168,9 @@ fn main() -> Result<(), StrError> { let mut plot = Plot::new(); plot.add(&boxes) .set_title("boxplot documentation test") - .set_ticks_x_labels(&ticks, &labels) - .save("/tmp/plotpy/doc_tests/doc_boxplot_2.svg")?; + .set_ticks_x_labels(&ticks, &labels); + + // plot.save("/tmp/plotpy/doc_tests/doc_boxplot_2.svg")?; Ok(()) } ``` @@ -216,7 +219,8 @@ fn main() -> Result<(), StrError> { .set_hide_axes(true) .set_equal_axes(true) .set_show_errors(true); - plot.save("/tmp/plotpy/doc_tests/doc_canvas_polycurve.svg")?; + + // plot.save("/tmp/plotpy/doc_tests/doc_canvas_polycurve.svg")?; Ok(()) } ``` @@ -249,11 +253,10 @@ fn main() -> Result<(), StrError> { // add contour to plot let mut plot = Plot::new(); - plot.add(&contour); - plot.set_labels("x", "y"); + plot.add(&contour) + .set_labels("x", "y"); - // save figure - plot.save("/tmp/plotpy/readme_contour.svg")?; + // plot.save("/tmp/plotpy/readme_contour.svg")?; Ok(()) } ``` @@ -294,10 +297,11 @@ fn main() -> Result<(), StrError> { // add curve to plot let mut plot = Plot::new(); - plot.add(&curve).set_num_ticks_y(11).grid_labels_legend("x", "y"); + plot.add(&curve) + .set_num_ticks_y(11) + .grid_labels_legend("x", "y"); - // save figure - plot.save("/tmp/plotpy/doc_tests/doc_curve.svg")?; + // plot.save("/tmp/plotpy/doc_tests/doc_curve.svg")?; Ok(()) } ``` @@ -337,8 +341,7 @@ fn main() -> Result<(), StrError> { .set_frame_border(true, false, true, false) .grid_labels_legend("values", "count"); - // save figure - plot.save("/tmp/plotpy/doc_tests/doc_histogram.svg")?; + // plot.save("/tmp/plotpy/doc_tests/doc_histogram.svg")?; Ok(()) } ``` @@ -370,7 +373,8 @@ fn main() -> Result<(), StrError> { // save figure let mut plot = Plot::new(); plot.add(&img); - plot.save("/tmp/plotpy/doc_tests/doc_image_1.svg")?; + + // plot.save("/tmp/plotpy/doc_tests/doc_image_1.svg")?; Ok(()) } ``` @@ -378,6 +382,34 @@ fn main() -> Result<(), StrError> { ![image](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_image_1.svg) +### InsetAxes + +```rust +use plotpy::{Curve, InsetAxes, Plot, StrError}; + +fn main() -> Result<(), StrError> { + // draw curve + let mut curve = Curve::new(); + curve.draw(&[0.0, 1.0, 2.0], &[0.0, 1.0, 4.0]); + + // allocate inset and add curve to it + let mut inset = InsetAxes::new(); + inset + .add(&curve) // add curve to inset + .set_range(0.5, 1.5, 0.5, 1.5) // set the range of the inset + .draw(0.5, 0.5, 0.4, 0.3); + + // add curve and inset to plot + let mut plot = Plot::new(); + plot.add(&curve) + .set_range(0.0, 5.0, 0.0, 5.0) + .add(&inset); // IMPORTANT: add inset after setting the range + + // plot.save("/tmp/plotpy/doc_tests/doc_inset_axes_add.svg")?; + Ok(()) +} +``` + ### Surface @@ -432,8 +464,9 @@ fn main() -> Result<(), StrError> { // save figure plot.set_equal_axes(true) - .set_figure_size_points(600.0, 600.0) - .save("/tmp/plotpy/readme_superquadric.svg")?; + .set_figure_size_points(600.0, 600.0); + + // plot.save("/tmp/plotpy/readme_superquadric.svg")?; Ok(()) } ``` @@ -467,8 +500,7 @@ fn main() -> Result<(), StrError> { let mut plot = Plot::new(); plot.add(&text); - // save figure - plot.save("/tmp/plotpy/doc_tests/doc_text.svg")?; + // plot.save("/tmp/plotpy/doc_tests/doc_text.svg")?; Ok(()) } ``` diff --git a/figures/doc_inset_axes_add.svg b/figures/doc_inset_axes_add.svg new file mode 100644 index 0000000..1a56fd0 --- /dev/null +++ b/figures/doc_inset_axes_add.svg @@ -0,0 +1,450 @@ + + + + + + + + 2025-02-09T16:20:44.240614 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/figures/integ_inset_axes_2.svg b/figures/integ_inset_axes_2.svg new file mode 100644 index 0000000..e550ef8 --- /dev/null +++ b/figures/integ_inset_axes_2.svg @@ -0,0 +1,819 @@ + + + + + + + + 2025-02-09T16:11:02.892451 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/figures/integ_inset_axes_3.svg b/figures/integ_inset_axes_3.svg new file mode 100644 index 0000000..b9becf4 --- /dev/null +++ b/figures/integ_inset_axes_3.svg @@ -0,0 +1,560 @@ + + + + + + + + 2025-02-09T16:11:02.874881 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/inset_axes.rs b/src/inset_axes.rs index c45464f..fb275b9 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -2,6 +2,25 @@ use super::GraphMaker; use std::fmt::Write; /// Implements the capability to add inset Axes to existing Axes. +/// +/// # Warning +/// +/// **WARNING:** If the range of axes has been modified in [crate::Plot], e.g. by `plot.set_range(...)`, +/// then the inset must be added after the range has been set. Otherwise, the inset will not be displayed correctly. +/// Specifically the connector lines will not be drawn if the inset is added before `set_range`. +/// +/// For example, below is the correct procedure: +/// +/// ``` +/// use plotpy::{Plot, InsetAxes}; +/// +/// let mut inset = InsetAxes::new(); +/// inset.draw(0.5, 0.5, 0.4, 0.3); +/// +/// let mut plot = Plot::new(); +/// plot.set_range(0.0, 10.0, 0.0, 10.0) +/// .add(&inset); // IMPORTANT: add inset after setting the range +/// ``` pub struct InsetAxes { xmin: f64, xmax: f64, @@ -25,6 +44,22 @@ impl InsetAxes { /// # Returns /// /// A new instance of `InsetAxes`. + /// + /// # Warning + /// + /// **WARNING:** If the range of axes has been modified in [crate::Plot], e.g. by `plot.set_range(...)`, + /// then the inset must be added after the range has been set. Otherwise, the inset will not be displayed correctly. + /// Specifically the connector lines will not be drawn if the inset is added before `set_range`. + /// + /// For example, below is the correct procedure: + /// + /// ``` + /// use plotpy::{InsetAxes, Plot}; + /// let mut inset = InsetAxes::new(); + /// let mut plot = Plot::new(); + /// plot.set_range(0.0, 10.0, 0.0, 10.0) + /// .add(&inset); // IMPORTANT: add inset after setting the range + /// ``` pub fn new() -> Self { Self { xmin: 0.0, @@ -92,13 +127,30 @@ impl InsetAxes { } /// Adds new graph entity + /// + /// # Warning + /// + /// **WARNING:** If the range of axes has been modified in [crate::Plot], e.g. by `plot.set_range(...)`, + /// then the inset must be added after the range has been set. Otherwise, the inset will not be displayed correctly. + /// Specifically the connector lines will not be drawn if the inset is added before `set_range`. + /// + /// For example, below is the correct procedure: + /// + /// ``` + /// use plotpy::{InsetAxes, Plot}; + /// let mut inset = InsetAxes::new(); + /// let mut plot = Plot::new(); + /// plot.set_range(0.0, 10.0, 0.0, 10.0) + /// .add(&inset); // IMPORTANT: add inset after setting the range + /// ``` pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { let buf = graph .get_buffer() .replace("plt.gca()", "zoom") .replace("plt.barh", "zoom.barh") .replace("plt.bar", "zoom.bar") - .replace("plt.imshow", "zoom.imshow"); + .replace("plt.imshow", "zoom.imshow") + .replace("plt.plot", "zoom.plot"); self.buffer.push_str(&buf); self } @@ -113,6 +165,40 @@ impl InsetAxes { /// * `v0` -- The normalized (0 to 1) vertical figure coordinate of the lower-left corner of the inset Axes. /// * `width` -- The width of the inset Axes. /// * `height` -- The height of the inset Axes. + /// + /// # Warning + /// + /// **WARNING:** If the range of axes has been modified in [crate::Plot], e.g. by `plot.set_range(...)`, + /// then the inset must be added after the range has been set. Otherwise, the inset will not be displayed correctly. + /// Specifically the connector lines will not be drawn if the inset is added before `set_range`. + /// + /// For example, below is the correct procedure: + /// + /// ``` + /// use plotpy::{Curve, InsetAxes, Plot, StrError}; + /// + /// fn main() -> Result<(), StrError> { + /// // draw curve + /// let mut curve = Curve::new(); + /// curve.draw(&[0.0, 1.0, 2.0], &[0.0, 1.0, 4.0]); + /// + /// // allocate inset and add curve to it + /// let mut inset = InsetAxes::new(); + /// inset + /// .add(&curve) // add curve to inset + /// .set_range(0.5, 1.5, 0.5, 1.5) // set the range of the inset + /// .draw(0.5, 0.5, 0.4, 0.3); + /// + /// // add curve and inset to plot + /// let mut plot = Plot::new(); + /// plot.add(&curve) + /// .set_range(0.0, 5.0, 0.0, 5.0) + /// .add(&inset) // IMPORTANT: add inset after setting the range + /// .save("/tmp/plotpy/doc_tests/doc_inset_axes_add.svg") + /// } + /// ``` + /// + /// ![doc_inset_axes_add.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_inset_axes_add.svg) pub fn draw(&mut self, u0: f64, v0: f64, width: f64, height: f64) { let opt1 = self.options_for_axes(); let opt2 = self.options_for_indicator(); @@ -132,7 +218,7 @@ impl InsetAxes { write!(&mut self.buffer, "plt.gca().indicate_inset_zoom(zoom{})\n", opt2,).unwrap(); } - /// Sets the limits of axes + /// Sets the limits of axes in the inset. pub fn set_range(&mut self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> &mut Self { self.xmin = xmin; self.xmax = xmax; diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index f4942f9..6b2a1da 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -124,11 +124,12 @@ fn test_inset_axes_3() -> Result<(), StrError> { // add to plot let mut plot = Plot::new(); - plot.add(&canvas).add(&inset); + plot.add(&canvas); // save figure let path = Path::new(OUT_DIR).join("integ_inset_axes_3.svg"); plot.set_range(0.0, 2.0, 0.0, 2.0) + .add(&inset) // <<<<<<<<<<<<< IMPORTANT: thus must be after set_range .set_equal_axes(true) .set_show_errors(true) .save(&path)?; @@ -138,6 +139,6 @@ fn test_inset_axes_3() -> Result<(), StrError> { let buffered = BufReader::new(file); let lines_iter = buffered.lines(); let n = lines_iter.count().clone(); - assert!(n > 680 && n < 800); + assert!(n > 520 && n < 600); Ok(()) } From db0498e260b15eb7bb5f7ded5d9c1a61f34c98aa Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 16:32:48 +1000 Subject: [PATCH 29/33] Add test --- figures/integ_inset_axes_4.svg | 474 +++++++++++++++++++++++++++++++++ tests/test_inset_axes.rs | 37 ++- 2 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 figures/integ_inset_axes_4.svg diff --git a/figures/integ_inset_axes_4.svg b/figures/integ_inset_axes_4.svg new file mode 100644 index 0000000..c41d2e2 --- /dev/null +++ b/figures/integ_inset_axes_4.svg @@ -0,0 +1,474 @@ + + + + + + + + 2025-02-09T16:32:29.070233 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 6b2a1da..cddc538 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{Barplot, Canvas, Image, InsetAxes, Plot, StrError}; +use plotpy::{Barplot, Canvas, Curve, Image, InsetAxes, Plot, StrError}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -142,3 +142,38 @@ fn test_inset_axes_3() -> Result<(), StrError> { assert!(n > 520 && n < 600); Ok(()) } + +#[test] +fn test_inset_axes_4() -> Result<(), StrError> { + // canvas + let mut curve = Curve::new(); + let x = &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; + let y = &[1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0]; + curve.draw(x, y); + + // inset axes + let mut inset = InsetAxes::new(); + inset + .add(&curve) + .set_range(7.0, 9.0, 40.0, 70.0) + .draw(0.05, 0.25, 0.4, 0.7); + + // add to plot + let mut plot = Plot::new(); + plot.add(&curve); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_4.svg"); + plot.set_range(0.0, 10.0, 0.0, 100.0) + .add(&inset) // <<<<<<<<<<<<< IMPORTANT: thus must be after set_range + .set_show_errors(true) + .save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + assert!(n > 430 && n < 500); + Ok(()) +} From da1b0c970705df56b9d88a20ea4439d6226f20dd Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 16:58:58 +1000 Subject: [PATCH 30/33] Handle Contour in InsetAxes --- .vscode/settings.json | 1 + figures/integ_inset_axes_5.svg | 2453 ++++++++++++++++++++++++++++++++ src/constants.rs | 6 +- src/inset_axes.rs | 6 + tests/test_inset_axes.rs | 36 +- 5 files changed, 2500 insertions(+), 2 deletions(-) create mode 100644 figures/integ_inset_axes_5.svg diff --git a/.vscode/settings.json b/.vscode/settings.json index 48b8a93..d53e8ef 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,6 +52,7 @@ "hspace", "imshow", "joinstyle", + "kwargs", "labelcolor", "labelpad", "labelsize", diff --git a/figures/integ_inset_axes_5.svg b/figures/integ_inset_axes_5.svg new file mode 100644 index 0000000..54a89d9 --- /dev/null +++ b/figures/integ_inset_axes_5.svg @@ -0,0 +1,2453 @@ + + + + + + + + 2025-02-09T16:58:04.371011 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/constants.rs b/src/constants.rs index 5c6795a..1a0a030 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -103,6 +103,10 @@ def set_equal_axes(): print('VERSION of MATPLOTLIB = {}'.format(matplotlib.__version__)) print('ERROR: set_box_aspect is missing in this version of Matplotlib') +# Function to ignore calls to plt such as the colorbar in an inset Axes +def ignore_this(*args, **kwargs): + pass + ################## plotting commands follow after this line ############################ "; @@ -146,6 +150,6 @@ mod tests { #[test] fn constants_are_correct() { - assert_eq!(PYTHON_HEADER.len(), 2770); + assert_eq!(PYTHON_HEADER.len(), 2886); } } diff --git a/src/inset_axes.rs b/src/inset_axes.rs index fb275b9..310d88d 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -144,11 +144,17 @@ impl InsetAxes { /// .add(&inset); // IMPORTANT: add inset after setting the range /// ``` pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self { + // Note: the order of replacements is important let buf = graph .get_buffer() .replace("plt.gca()", "zoom") .replace("plt.barh", "zoom.barh") .replace("plt.bar", "zoom.bar") + .replace("plt.contourf", "zoom.contourf") + .replace("plt.contour", "zoom.contour") + .replace("plt.clabel", "zoom.clabel") + .replace("plt.colorbar", "ignore_this") + .replace("cb.ax.set_ylabel", "ignore_this") .replace("plt.imshow", "zoom.imshow") .replace("plt.plot", "zoom.plot"); self.buffer.push_str(&buf); diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index cddc538..67364ba 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{Barplot, Canvas, Curve, Image, InsetAxes, Plot, StrError}; +use plotpy::{generate3d, Barplot, Canvas, Contour, Curve, Image, InsetAxes, Plot, StrError}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -177,3 +177,37 @@ fn test_inset_axes_4() -> Result<(), StrError> { assert!(n > 430 && n < 500); Ok(()) } + +#[test] +fn test_inset_axes_5() -> Result<(), StrError> { + // contour + let mut contour = Contour::new(); + contour.set_colorbar_label("TEMPERATURE").set_number_format_cb("%.1f"); + let n = 9; + let (x, y, z) = generate3d(-2.0, 2.0, -2.0, 2.0, n, n, |x, y| x * x + y * y); + contour.draw(&x, &y, &z); + + // inset axes + let mut inset = InsetAxes::new(); + inset + .set_indicator_line_color("yellow") + .add(&contour) + .set_range(-1.0, 1.0, -1.0, 1.0) + .draw(0.78, 0.78, 0.2, 0.2); + + // add to plot + let mut plot = Plot::new(); + plot.add(&contour); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_5.svg"); + plot.add(&inset).set_show_errors(true).save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + assert!(n > 2400 && n < 2500); + Ok(()) +} From 3b4ee0365afeaaa618351ee1968fff80bc51f2e5 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 17:08:29 +1000 Subject: [PATCH 31/33] Handle Histogram in InsetAxes --- figures/integ_inset_axes_6.svg | 978 +++++++++++++++++++++++++++++++++ src/inset_axes.rs | 1 + tests/test_inset_axes.rs | 38 +- 3 files changed, 1016 insertions(+), 1 deletion(-) create mode 100644 figures/integ_inset_axes_6.svg diff --git a/figures/integ_inset_axes_6.svg b/figures/integ_inset_axes_6.svg new file mode 100644 index 0000000..b07d5ca --- /dev/null +++ b/figures/integ_inset_axes_6.svg @@ -0,0 +1,978 @@ + + + + + + + + 2025-02-09T17:07:49.564328 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 310d88d..128bbc1 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -156,6 +156,7 @@ impl InsetAxes { .replace("plt.colorbar", "ignore_this") .replace("cb.ax.set_ylabel", "ignore_this") .replace("plt.imshow", "zoom.imshow") + .replace("plt.hist", "zoom.hist") .replace("plt.plot", "zoom.plot"); self.buffer.push_str(&buf); self diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 67364ba..886dfb5 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{generate3d, Barplot, Canvas, Contour, Curve, Image, InsetAxes, Plot, StrError}; +use plotpy::{generate3d, Barplot, Canvas, Contour, Curve, Histogram, Image, InsetAxes, Plot, StrError}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -211,3 +211,39 @@ fn test_inset_axes_5() -> Result<(), StrError> { assert!(n > 2400 && n < 2500); Ok(()) } + +#[test] +fn test_inset_axes_6() -> Result<(), StrError> { + // histogram + let mut histogram = Histogram::new(); + let values = vec![ + vec![1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6], // first series + vec![-1, -1, 0, 1, 2, 3], // second series + vec![5, 6, 7, 8], // third series + ]; + let labels = ["first", "second", "third"]; + histogram.draw(&values, &labels); + + // inset axes + let mut inset = InsetAxes::new(); + inset + .add(&histogram) + .set_range(1.5, 2.5, 0.5, 1.2) + .draw(0.6, 0.55, 0.35, 0.4); + + // add to plot + let mut plot = Plot::new(); + plot.add(&histogram); + + // save figure + let path = Path::new(OUT_DIR).join("integ_inset_axes_6.svg"); + plot.add(&inset).set_show_errors(true).save(&path)?; + + // check number of lines + let file = File::open(path).map_err(|_| "cannot open file")?; + let buffered = BufReader::new(file); + let lines_iter = buffered.lines(); + let n = lines_iter.count().clone(); + assert!(n > 920 && n < 1010); + Ok(()) +} From e255f83e1e2b6832e38ca5539a198d9a15a2fa11 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 17:23:32 +1000 Subject: [PATCH 32/33] Handle text in InsetAxes --- figures/integ_inset_axes_4.svg | 121 +++++++++++++++++++++++++++------ src/inset_axes.rs | 3 +- tests/test_inset_axes.rs | 13 ++-- 3 files changed, 112 insertions(+), 25 deletions(-) diff --git a/figures/integ_inset_axes_4.svg b/figures/integ_inset_axes_4.svg index c41d2e2..ed80227 100644 --- a/figures/integ_inset_axes_4.svg +++ b/figures/integ_inset_axes_4.svg @@ -6,7 +6,7 @@ - 2025-02-09T16:32:29.070233 + 2025-02-09T17:23:02.772219 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,7 +82,7 @@ z - + @@ -121,7 +121,7 @@ z - + @@ -155,7 +155,7 @@ z - + @@ -200,7 +200,7 @@ z - + @@ -254,7 +254,7 @@ z - + @@ -286,12 +286,12 @@ z - - + @@ -304,7 +304,7 @@ L -3.5 0 - + @@ -318,7 +318,7 @@ L -3.5 0 - + @@ -332,7 +332,7 @@ L -3.5 0 - + @@ -346,7 +346,7 @@ L -3.5 0 - + @@ -360,7 +360,7 @@ L -3.5 0 - + @@ -382,7 +382,7 @@ L 211.8475 210.583219 L 247.5595 181.310899 L 283.2715 146.716339 L 318.9835 106.799539 -" clip-path="url(#pb744112899)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p5a0fe6772f)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> + + + + + + + + + + + + + + + + +" clip-path="url(#p5a0fe6772f)" style="fill: none; opacity: 0.5; stroke: #808080; stroke-linejoin: miter"/> +" clip-path="url(#pf496cd73e8)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> + + + + + + + + + + - + - + diff --git a/src/inset_axes.rs b/src/inset_axes.rs index 128bbc1..4b72fda 100644 --- a/src/inset_axes.rs +++ b/src/inset_axes.rs @@ -157,7 +157,8 @@ impl InsetAxes { .replace("cb.ax.set_ylabel", "ignore_this") .replace("plt.imshow", "zoom.imshow") .replace("plt.hist", "zoom.hist") - .replace("plt.plot", "zoom.plot"); + .replace("plt.plot", "zoom.plot") + .replace("plt.text", "zoom.text"); self.buffer.push_str(&buf); self } diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs index 886dfb5..b6cc590 100644 --- a/tests/test_inset_axes.rs +++ b/tests/test_inset_axes.rs @@ -1,4 +1,4 @@ -use plotpy::{generate3d, Barplot, Canvas, Contour, Curve, Histogram, Image, InsetAxes, Plot, StrError}; +use plotpy::{generate3d, Barplot, Canvas, Contour, Curve, Histogram, Image, InsetAxes, Plot, StrError, Text}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -145,22 +145,27 @@ fn test_inset_axes_3() -> Result<(), StrError> { #[test] fn test_inset_axes_4() -> Result<(), StrError> { - // canvas + // curve let mut curve = Curve::new(); let x = &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; let y = &[1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0]; curve.draw(x, y); + // text + let mut text = Text::new(); + text.set_align_horizontal("center").draw(8.0, 64.0, "LOOK!"); + // inset axes let mut inset = InsetAxes::new(); inset + .add(&text) .add(&curve) .set_range(7.0, 9.0, 40.0, 70.0) .draw(0.05, 0.25, 0.4, 0.7); // add to plot let mut plot = Plot::new(); - plot.add(&curve); + plot.add(&curve).add(&text); // save figure let path = Path::new(OUT_DIR).join("integ_inset_axes_4.svg"); @@ -174,7 +179,7 @@ fn test_inset_axes_4() -> Result<(), StrError> { let buffered = BufReader::new(file); let lines_iter = buffered.lines(); let n = lines_iter.count().clone(); - assert!(n > 430 && n < 500); + assert!(n > 500 && n < 600); Ok(()) } From d3558b2e3490a32eac677a53cb81aabd432c3687 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 9 Feb 2025 17:28:07 +1000 Subject: [PATCH 33/33] Improve examples Readme --- examples/README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 667a100..5a9f872 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,9 +1,26 @@ -# Examples +# Examples Please check out [the documentation](https://docs.rs/plotpy) and the integration tests in the [tests directory](https://github.com/cpmech/plotpy/tree/main/tests) Some output of integration tests are shown below. +## Contents + +- [Barplot](#barplot) +- [Boxplot](#boxplot) +- [Canvas](#canvas) +- [Contour](#contour) +- [Curve](#curve) +- [Histogram](#histogram) +- [Image](#image) +- [InsetAxes](#insetaxes) +- [Legend](#legend) +- [Plot](#plot) +- [Subplot and GridSpec](#subplot-and-gridspec) +- [Slope icon](#slope-icon) +- [Surface and wireframe](#surface-and-wireframe) +- [Text](#text) + ## Barplot [test_barplot.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_barplot.rs) @@ -81,6 +98,18 @@ Some output of integration tests are shown below. ![image](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_image_1.svg) +## InsetAxes + +[test_inset_axes.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_inset_axes.rs) + +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_1.svg) +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_2.svg) +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_3.svg) +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_4.svg) +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_5.svg) +![inset_axes](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_inset_axes_6.svg) + + ## Legend [test_legend.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_legend.rs)