Skip to content

Commit bab198b

Browse files
committedMay 4, 2024·
Add contour functions
1 parent 1266320 commit bab198b

File tree

3 files changed

+128
-7
lines changed

3 files changed

+128
-7
lines changed
 

‎Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ categories = ["science", "visualization", "mathematics", "graphics"]
1313

1414
[dependencies]
1515
numpy = "0.21"
16+
ndarray = "0.15.6"
1617
curve-sampling = { version = "0.5", optional = true, git = "https://github.com/Chris00/rust-curve-sampling.git" }
1718
lazy_static = "1.4.0"
1819

‎src/colors/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88

99
// https://matplotlib.org/stable/users/explain/colors/colors.html
1010

11+
use pyo3::{prelude::*, types::PyTuple};
12+
1113
/// Trait that color representations must satisfy.
1214
pub trait Color {
1315
/// Return the RGBA components of the color as numbers in
1416
/// \[0., 1.\].
1517
fn rgba(&self) -> [f64; 4];
1618
}
1719

20+
/// Return the Python tuple corresponding to a color.
21+
#[inline]
22+
pub(crate) fn py(py: Python<'_>, c: impl Color) -> Bound<PyTuple> {
23+
PyTuple::new_bound(py, c.rgba())
24+
}
25+
1826
impl Color for [f64; 3] {
1927
fn rgba(&self) -> [f64; 4] {
2028
[self[0].clamp(0., 1.), self[1].clamp(0., 1.),

‎src/lib.rs

+119-7
Original file line numberDiff line numberDiff line change
@@ -433,18 +433,58 @@ impl Axes {
433433
}
434434

435435

436-
pub fn contour<'a>(&'a mut self, ) -> Contour<'a> {
436+
/// Draw the contour lines for the data `z[j,i]` as points
437+
/// (`x[i]`, `y[j]`).
438+
///
439+
/// # Example
440+
///
441+
/// ```
442+
/// use matplotlib as plt;
443+
/// use ndarray::{Array1, Array2};
444+
/// let x: Array1<f64> = Array1::linspace(-1., 1., 30);
445+
/// let y: Array1<f64> = Array1::linspace(-1., 1., 30);
446+
/// let mut z = Array2::zeros((30, 30));
447+
/// for (j, &y) in y.iter().enumerate() {
448+
/// for (i, &x) in x.iter().enumerate() {
449+
/// z[(j, i)] = (0.5 * x).powi(2) + y.powi(2);
450+
/// }
451+
/// }
452+
/// let (fig, [[mut ax]]) = plt::subplots()?;
453+
/// ax.contour(x.as_slice().unwrap(), y.as_slice().unwrap(), &z).plot();
454+
/// fig.save().to_file("target/contour.pdf")?;
455+
/// # Ok::<(), matplotlib::Error>(())
456+
/// ```
457+
pub fn contour<'a, D>(
458+
&'a mut self, x: D, y: D, z: &'a ndarray::Array2<f64>,
459+
) -> Contour<'a, D>
460+
where D: AsRef<[f64]> {
437461
Contour {
438462
axes: self,
439463
options: PlotOptions::new(),
464+
x, y, z,
465+
levels: None,
440466
}
441467
}
442468

469+
/// Draw the contour lines for function `f` in the rectangle `ab`×`cd`.
470+
///
471+
/// # Example
472+
///
473+
/// ```
474+
/// use matplotlib as plt;
475+
/// let (fig, [[mut ax]]) = plt::subplots()?;
476+
/// ax.contour_fun([-1., 1.], [-1., 1.], |x, y| {
477+
/// (0.5 * x).powi(2) + y.powi(2)
478+
/// })
479+
/// .plot();
480+
/// fig.save().to_file("target/contour_fun.pdf")?;
481+
/// # Ok::<(), matplotlib::Error>(())
482+
/// ```
443483
pub fn contour_fun<'a, F>(
444484
&'a mut self,
445-
f: F,
446485
ab: [f64; 2],
447486
cd: [f64; 2],
487+
f: F,
448488
) -> ContourFun<'a, F>
449489
where F: FnMut(f64, f64) -> f64 {
450490
ContourFun {
@@ -453,6 +493,7 @@ impl Axes {
453493
f, ab, cd,
454494
n1: 100,
455495
n2: 100,
496+
levels: None,
456497
}
457498
}
458499

@@ -822,16 +863,51 @@ where F: FnMut(f64) -> Y,
822863
}
823864
}
824865

866+
pub struct QuadContourSet {
867+
contours: PyObject,
868+
}
869+
870+
impl QuadContourSet {
871+
pub fn set_color(&mut self, c: impl Color) -> &mut Self {
872+
Python::with_gil(|py| {
873+
meth!(self.contours, set_color, (colors::py(py, c),)).unwrap()
874+
});
875+
self
876+
}
877+
}
825878

826879
#[must_use]
827-
pub struct Contour<'a> {
880+
pub struct Contour<'a, D> {
828881
axes: &'a Axes,
829882
options: PlotOptions<'a>,
883+
x: D,
884+
y: D,
885+
z: &'a ndarray::Array2<f64>,
886+
levels: Option<&'a [f64]>,
830887
}
831888

832-
impl<'a> Contour<'a> {
889+
impl<'a, D> Contour<'a, D>
890+
where D: AsRef<[f64]> {
833891
set_plotoptions!();
834892

893+
pub fn plot(&self) -> QuadContourSet {
894+
Python::with_gil(|py| {
895+
let x = self.x.as_ref().to_pyarray_bound(py);
896+
let y = self.y.as_ref().to_pyarray_bound(py);
897+
let z = self.z.to_pyarray_bound(py);
898+
let opt = self.options.kwargs(py);
899+
if let Some(levels) = self.levels {
900+
let levels = levels.to_pyarray_bound(py);
901+
opt.set_item("levels", levels).unwrap();
902+
}
903+
let contours = self.axes.ax
904+
.call_method_bound(py, intern!(py, "contour"),
905+
(x, y, z),
906+
Some(&opt))
907+
.unwrap();
908+
QuadContourSet { contours }
909+
})
910+
}
835911
}
836912

837913

@@ -842,14 +918,51 @@ pub struct ContourFun<'a, F> {
842918
f: F,
843919
ab: [f64; 2],
844920
cd: [f64; 2],
845-
n1: usize,
921+
n1: usize, // FIXME: want to be more versatile than an equispaced grid?
846922
n2: usize,
923+
levels: Option<&'a [f64]>,
847924
}
848925

849926
impl<'a, F> ContourFun<'a, F>
850927
where F: FnMut(f64, f64) -> f64 {
851928
set_plotoptions!();
852929

930+
pub fn plot(&mut self) -> QuadContourSet {
931+
let mut x = Vec::with_capacity(self.n1);
932+
let mut y = Vec::with_capacity(self.n2);
933+
let mut z = ndarray::Array2::zeros((self.n2, self.n1));
934+
let a = self.ab[0];
935+
let dx = (self.ab[1] - a) / (self.n1 - 1) as f64;
936+
for i in 0 .. self.n1 {
937+
x.push(a + dx * i as f64);
938+
}
939+
let c = self.cd[0];
940+
let dy = (self.cd[1] - c) / (self.n2 - 1) as f64;
941+
for j in 0 .. self.n2 {
942+
y.push(c + dy * j as f64);
943+
}
944+
for (j, &y) in y.iter().enumerate() {
945+
for (i, &x) in x.iter().enumerate() {
946+
z[(j, i)] = (self.f)(x, y);
947+
}
948+
}
949+
Python::with_gil(|py| {
950+
let x = x.to_pyarray_bound(py);
951+
let y = y.to_pyarray_bound(py);
952+
let z = z.to_pyarray_bound(py);
953+
let opt = self.options.kwargs(py);
954+
if let Some(levels) = self.levels {
955+
let levels = levels.to_pyarray_bound(py);
956+
opt.set_item("levels", levels).unwrap();
957+
}
958+
let contours = self.axes.ax
959+
.call_method_bound(py, intern!(py, "contour"),
960+
(x, y, z),
961+
Some(&opt))
962+
.unwrap();
963+
QuadContourSet { contours }
964+
})
965+
}
853966
}
854967

855968

@@ -871,8 +984,7 @@ impl Line2D {
871984
/// Set the color of the line to `c`.
872985
pub fn set_color(&mut self, c: impl Color) -> &mut Self {
873986
Python::with_gil(|py| {
874-
let c = PyTuple::new_bound(py, c.rgba());
875-
meth!(self.line2d, set_color, (c,)).unwrap();
987+
meth!(self.line2d, set_color, (colors::py(py, c),)).unwrap();
876988
self
877989
})
878990
}

0 commit comments

Comments
 (0)
Please sign in to comment.