From 40448b4b8c54b1d85150e3a8f4557520cb5d10f0 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Fri, 26 Dec 2025 14:27:59 +1000 Subject: [PATCH 1/2] Impl quiver and streamlines --- src/lib.rs | 2 + src/stream.rs | 202 +++++++++++++++++++++++++++++++++++++++++++ tests/test_stream.rs | 56 ++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 src/stream.rs create mode 100644 tests/test_stream.rs diff --git a/src/lib.rs b/src/lib.rs index e6b590c..cec1ed0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,7 @@ mod inset_axes; mod legend; mod plot; mod slope_icon; +mod stream; mod super_title_params; mod surface; mod surface_geometry; @@ -115,6 +116,7 @@ pub use inset_axes::*; pub use legend::*; pub use plot::*; pub use slope_icon::*; +pub use stream::*; pub use super_title_params::*; pub use surface::*; pub use text::*; diff --git a/src/stream.rs b/src/stream.rs new file mode 100644 index 0000000..6ea9a61 --- /dev/null +++ b/src/stream.rs @@ -0,0 +1,202 @@ +use super::GraphMaker; +use crate::conversions::matrix_to_array; +use crate::AsMatrix; +use num_traits::Num; +use std::fmt::Write; + +/// Implements functions to illustrate vector fields using streamlines and quiver plots +pub struct Stream { + // common options + color: String, + + // streamplot options + streamplot_linewidth: f64, + streamplot_arrow_style: String, + streamplot_density: f64, + streamplot_extra: String, + + // quiver options + quiver_scale: f64, + quiver_pivot: String, + quiver_extra: String, + + // buffer + buffer: String, +} + +impl Stream { + /// Creates a new Stream object + pub fn new() -> Self { + Stream { + // common options + color: String::new(), + // streamplot options + streamplot_linewidth: 0.0, + streamplot_arrow_style: String::new(), + streamplot_density: 0.0, + streamplot_extra: String::new(), + // quiver options + quiver_scale: 0.0, + quiver_pivot: String::new(), + quiver_extra: String::new(), + // extra options + // buffer + buffer: String::new(), + } + } + + /// Draws streamlines (stream plot) + pub fn draw<'a, T, U>(&mut self, xx: &'a T, yy: &'a T, dx: &'a T, dy: &'a T) + where + T: AsMatrix<'a, U>, + U: 'a + std::fmt::Display + Num, + { + matrix_to_array(&mut self.buffer, "xx", xx); + matrix_to_array(&mut self.buffer, "yy", yy); + matrix_to_array(&mut self.buffer, "dx", dx); + matrix_to_array(&mut self.buffer, "dy", dy); + let opt = self.options_streamplot(); + write!(&mut self.buffer, "plt.streamplot(xx,yy,dx,dy{})\n", &opt).unwrap(); + } + + /// Draws arrows (quiver plot) + pub fn draw_arrows<'a, T, U>(&mut self, xx: &'a T, yy: &'a T, dx: &'a T, dy: &'a T) + where + T: AsMatrix<'a, U>, + U: 'a + std::fmt::Display + Num, + { + matrix_to_array(&mut self.buffer, "xx", xx); + matrix_to_array(&mut self.buffer, "yy", yy); + matrix_to_array(&mut self.buffer, "dx", dx); + matrix_to_array(&mut self.buffer, "dy", dy); + let opt = self.options_quiver(); + write!(&mut self.buffer, "plt.quiver(xx,yy,dx,dy{})\n", &opt).unwrap(); + } + + /// Sets the line color (quiver or streamlines) + pub fn set_color(&mut self, color: &str) -> &mut Self { + self.color = String::from(color); + self + } + + /// Sets the line width of streamlines + pub fn set_streamline_linewidth(&mut self, width: f64) -> &mut Self { + self.streamplot_linewidth = width; + self + } + + /// Sets the arrow style + /// + /// Options: + /// + /// * "`-`" -- Curve : None + /// * "`->`" -- CurveB : head_length=0.4,head_width=0.2 + /// * "`-[`" -- BracketB : widthB=1.0,lengthB=0.2,angleB=None + /// * "`-|>`" -- CurveFilledB : head_length=0.4,head_width=0.2 + /// * "`<-`" -- CurveA : head_length=0.4,head_width=0.2 + /// * "`<->`" -- CurveAB : head_length=0.4,head_width=0.2 + /// * "`<|-`" -- CurveFilledA : head_length=0.4,head_width=0.2 + /// * "`<|-|>`" -- CurveFilledAB : head_length=0.4,head_width=0.2 + /// * "`]-`" -- BracketA : widthA=1.0,lengthA=0.2,angleA=None + /// * "`]-[`" -- BracketAB : widthA=1.0,lengthA=0.2,angleA=None,widthB=1.0,lengthB=0.2,angleB=None + /// * "`fancy`" -- Fancy : head_length=0.4,head_width=0.4,tail_width=0.4 + /// * "`simple`" -- Simple : head_length=0.5,head_width=0.5,tail_width=0.2 + /// * "`wedge`" -- Wedge : tail_width=0.3,shrink_factor=0.5 + /// * "`|-|`" -- BarAB : widthA=1.0,angleA=None,widthB=1.0,angleB=None + /// * As defined in + pub fn set_streamplot_arrow_style(&mut self, style: &str) -> &mut Self { + self.streamplot_arrow_style = String::from(style); + self + } + + /// Sets the density of streamlines + pub fn set_streamplot_density(&mut self, density: f64) -> &mut Self { + self.streamplot_density = density; + self + } + + /// Sets extra options for streamlines + /// + /// See + pub fn set_streamplot_extra(&mut self, extra: &str) -> &mut Self { + self.streamplot_extra = extra.to_string(); + self + } + + /// Sets the quiver inverse scale + pub fn set_quiver_inv_scale(&mut self, scale: f64) -> &mut Self { + self.quiver_scale = scale; + self + } + + /// Sets the quiver pivot + /// + /// Options: 'tail', 'mid', 'middle', 'tip' + /// + /// Default = 'tail' + pub fn set_quiver_pivot(&mut self, pivot: &str) -> &mut Self { + self.quiver_pivot = String::from(pivot); + self + } + + /// Sets extra options for quiver + /// + /// See + pub fn set_quiver_extra(&mut self, extra: &str) -> &mut Self { + self.quiver_extra = extra.to_string(); + self + } + + /// Returns options for streamplot + fn options_streamplot(&self) -> String { + let mut opt = String::new(); + if self.color != "" { + write!(&mut opt, ",color='{}'", self.color).unwrap(); + } + if self.streamplot_linewidth > 0.0 { + write!(&mut opt, ",linewidth={}", self.streamplot_linewidth).unwrap(); + } + if self.streamplot_arrow_style != "" { + write!(&mut opt, ",arrowstyle='{}'", self.streamplot_arrow_style).unwrap(); + } + if self.streamplot_density > 0.0 { + write!(&mut opt, ",density={}", self.streamplot_density).unwrap(); + } + if self.streamplot_extra != "" { + write!(&mut opt, ",{}", self.streamplot_extra).unwrap(); + } + opt + } + + /// Returns options for quiver + fn options_quiver(&self) -> String { + let mut opt = String::new(); + if self.color != "" { + write!(&mut opt, ",color='{}'", self.color).unwrap(); + } + if self.quiver_scale > 0.0 { + write!(&mut opt, ",scale={}", self.quiver_scale).unwrap(); + } + if self.quiver_pivot != "" { + write!(&mut opt, ",pivot='{}'", self.quiver_pivot).unwrap(); + } + if self.quiver_extra != "" { + write!(&mut opt, ",{}", self.quiver_extra).unwrap(); + } + opt + } +} + +impl GraphMaker for Stream { + fn get_buffer<'a>(&'a self) -> &'a String { + &self.buffer + } + fn clear_buffer(&mut self) { + self.buffer.clear(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests {} diff --git a/tests/test_stream.rs b/tests/test_stream.rs new file mode 100644 index 0000000..0b7944d --- /dev/null +++ b/tests/test_stream.rs @@ -0,0 +1,56 @@ +use plotpy::{generate2d, Plot, StrError, Stream}; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +const OUT_DIR: &str = "/tmp/plotpy/integ_tests"; + +#[test] +fn test_stream_arrows_1() -> Result<(), StrError> { + // object and options + let mut stream = Stream::new(); + let mut quiver = Stream::new(); + + // data + let (nx, ny) = (10, 10); + let (xx, yy) = generate2d(-2.0, 2.0, -2.0, 2.0, nx, ny); + let (mut dx, mut dy) = generate2d(0.0, 1.0, 0.0, 1.0, nx, ny); + for j in 0..ny { + for i in 0..nx { + let x = xx[j][i]; + let y = yy[j][i]; + dx[j][i] = -y; + dy[j][i] = x; + } + } + + // draw arrows + stream + .set_color("#dfa629ff") + .set_streamline_linewidth(0.75) + .set_streamplot_arrow_style("fancy") + .set_streamplot_density(0.8) + .set_streamplot_extra("broken_streamlines=False") + .draw(&xx, &yy, &dx, &dy); + quiver + .set_color("#4752c7ff") + .set_quiver_inv_scale(15.0) + .set_quiver_pivot("mid") + .draw_arrows(&xx, &yy, &dx, &dy); + + // add contour to plot + let mut plot = Plot::new(); + plot.add(&stream).add(&quiver); + + // save figure + let path = Path::new(OUT_DIR).join("integ_stream_arrows_1.svg"); + plot.set_equal_axes(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(); + assert!(n > 8650 && n < 8730); + Ok(()) +} From d33f1eea8c1464f18c4954fa891cb5fc42dd11eb Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Fri, 26 Dec 2025 14:30:34 +1000 Subject: [PATCH 2/2] Add figure --- examples/README.md | 7 + figures/integ_stream_arrows_1.svg | 8701 +++++++++++++++++++++++++++++ 2 files changed, 8708 insertions(+) create mode 100644 figures/integ_stream_arrows_1.svg diff --git a/examples/README.md b/examples/README.md index 37da89a..7bafeb6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,7 @@ Some output of integration tests are shown below. - [Plot](#plot) - [Subplot and GridSpec](#subplot-and-gridspec) - [Slope icon](#slope-icon) +- [Streamplot and quiver](#streamplot-and-quiver) - [Surface and wireframe](#surface-and-wireframe) - [Text](#text) @@ -168,6 +169,12 @@ Some output of integration tests are shown below. ![slope_icon](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_example.svg) ![slope_icon](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_logx_liny.svg) +## Streamplot and quiver + +[test_stream.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_stream.rs) + +![streamplot_quiver](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_stream_arrows_1.svg) + ## Surface and wireframe [test_surface_geometry.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_surface_geometry.rs) diff --git a/figures/integ_stream_arrows_1.svg b/figures/integ_stream_arrows_1.svg new file mode 100644 index 0000000..4e260b3 --- /dev/null +++ b/figures/integ_stream_arrows_1.svg @@ -0,0 +1,8701 @@ + + + + + + + + 2025-12-26T14:26:55.863666 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +