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*
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9af7baa..d53e8ef 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -27,6 +27,7 @@
"CARETRIGHTBASE",
"CARETUP",
"CARETUPBASE",
+ "cbook",
"clabel",
"CLOSEPOLY",
"cmap",
@@ -51,6 +52,7 @@
"hspace",
"imshow",
"joinstyle",
+ "kwargs",
"labelcolor",
"labelpad",
"labelsize",
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> {

+### 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/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.

+## InsetAxes
+
+[test_inset_axes.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_inset_axes.rs)
+
+
+
+
+
+
+
+
+
## Legend
[test_legend.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_legend.rs)
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 @@
+
+
+
diff --git a/figures/integ_inset_axes_1.svg b/figures/integ_inset_axes_1.svg
new file mode 100644
index 0000000..060a89d
--- /dev/null
+++ b/figures/integ_inset_axes_1.svg
@@ -0,0 +1,742 @@
+
+
+
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 @@
+
+
+
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 @@
+
+
+
diff --git a/figures/integ_inset_axes_4.svg b/figures/integ_inset_axes_4.svg
new file mode 100644
index 0000000..ed80227
--- /dev/null
+++ b/figures/integ_inset_axes_4.svg
@@ -0,0 +1,555 @@
+
+
+
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 @@
+
+
+
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 @@
+
+
+
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
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
new file mode 100644
index 0000000..4b72fda
--- /dev/null
+++ b/src/inset_axes.rs
@@ -0,0 +1,398 @@
+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,
+ ymin: f64,
+ ymax: f64,
+ extra_for_axes: String,
+ extra_for_indicator: String,
+ indicator_line_style: String,
+ indicator_line_color: String,
+ indicator_line_width: f64,
+ indicator_hatch: String,
+ indicator_alpha: Option,
+ axes_visible: bool,
+ title: String,
+ buffer: String,
+}
+
+impl InsetAxes {
+ /// Creates a new `InsetAxes` object with an empty buffer.
+ ///
+ /// # 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,
+ xmax: 1.0,
+ ymin: 0.0,
+ ymax: 1.0,
+ extra_for_axes: String::new(),
+ extra_for_indicator: String::new(),
+ indicator_line_style: String::new(),
+ indicator_line_color: String::new(),
+ indicator_line_width: 0.0,
+ indicator_hatch: String::new(),
+ indicator_alpha: None,
+ axes_visible: false,
+ title: 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 line width for the indicator
+ pub fn set_indicator_line_width(&mut self, width: f64) -> &mut Self {
+ self.indicator_line_width = width;
+ 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:
+ ///
+ /// * "/" - 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
+ ///
+ /// # 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 {
+ // 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.hist", "zoom.hist")
+ .replace("plt.plot", "zoom.plot")
+ .replace("plt.text", "zoom.text");
+ self.buffer.push_str(&buf);
+ self
+ }
+
+ /// Draws the inset Axes.
+ ///
+ /// Example of normalized coordinates: `(0.5, 0.5, 0.4, 0.3)`.
+ ///
+ /// # Arguments
+ ///
+ /// * `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.
+ ///
+ /// # 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")
+ /// }
+ /// ```
+ ///
+ /// 
+ 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,
+ ),
+ );
+ 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();
+ }
+
+ /// 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;
+ self.ymin = ymin;
+ self.ymax = ymax;
+ self
+ }
+
+ /// Sets extra Matplotlib commands for the inset Axes (comma separated).
+ ///
+ /// [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 for the indicator (comma separated).
+ ///
+ /// [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
+ }
+
+ /// Sets the visibility of the axes ticks
+ ///
+ /// # Arguments
+ ///
+ /// * `visible` - If true, shows the axes ticks. If false, hides them.
+ pub fn set_visibility(&mut self, visible: bool) -> &mut Self {
+ self.axes_visible = visible;
+ 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();
+ if !self.extra_for_axes.is_empty() {
+ write!(&mut opt, ",{}", self.extra_for_axes).unwrap();
+ }
+ opt
+ }
+
+ /// 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.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 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();
+ }
+ 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 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());
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 95fa812..970814c 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;
@@ -108,6 +109,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::*;
diff --git a/tests/test_inset_axes.rs b/tests/test_inset_axes.rs
new file mode 100644
index 0000000..b6cc590
--- /dev/null
+++ b/tests/test_inset_axes.rs
@@ -0,0 +1,254 @@
+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;
+
+const OUT_DIR: &str = "/tmp/plotpy/integ_tests";
+
+#[test]
+fn test_inset_axes_1() -> Result<(), StrError> {
+ // draw image
+ 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();
+ 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_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_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);
+
+ // 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 > 680 && n < 800);
+ Ok(())
+}
+
+#[test]
+fn test_inset_axes_2() -> Result<(), StrError> {
+ // data
+ let x = [0, 1, 2, 3, 4];
+ let y = [5, 4, 3, 2, 1];
+
+ // 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();
+
+ // configure the barplot
+ bar.set_horizontal(horizontal).draw(&x, &y);
+
+ // 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_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 > 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);
+
+ // 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)?;
+
+ // 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 > 520 && n < 600);
+ Ok(())
+}
+
+#[test]
+fn test_inset_axes_4() -> Result<(), StrError> {
+ // 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).add(&text);
+
+ // 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 > 500 && n < 600);
+ 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(())
+}
+
+#[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(())
+}