Skip to content

Commit 242e508

Browse files
committed
Add a basic implementation of RcParams
1 parent 3f96436 commit 242e508

2 files changed

Lines changed: 98 additions & 19 deletions

File tree

examples/circle.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Inspired by https://gitlab.com/whooie/mpl/-/blob/master/README.md?ref_type=heads#example
2+
3+
use std::{error, f64::consts::TAU};
4+
use matplotlib::{self as mpl, pyplot as plt};
5+
6+
fn main() -> Result<(), Box<dyn error::Error>> {
7+
// Remark: `ax.fun` is easier (and more efficient) for plotting functions.
8+
let dx: f64 = TAU / 50.0;
9+
let x = (0..50_u32).map(|k| f64::from(k) * dx);
10+
let y1 = x.clone().map(f64::sin);
11+
let y2 = x.clone().map(f64::cos);
12+
13+
mpl::rc_params().set("axes.linewidth", 0.65)?;
14+
mpl::rc_params().set("lines.linewidth", 0.8)?;
15+
let fig = plt::figure()?;
16+
let [[mut ax]] = fig.subplots()?;
17+
ax.grid().set_xlabel("$x$");
18+
ax.xy_from(x.clone().zip(y1))
19+
.fmt("ob-")
20+
.label("$\\sin(x)$")
21+
.plot();
22+
ax.xy_from(x.zip(y2))
23+
.fmt("Dr-")
24+
.label("$\\cos(x)$")
25+
.plot();
26+
ax.legend([]);
27+
plt::show();
28+
29+
Ok(())
30+
}

src/lib.rs

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ use std::{
1313
fmt::{Display, Formatter},
1414
};
1515
use pyo3::{
16-
exceptions::{PyFileNotFoundError, PyPermissionError},
17-
intern, prelude::*, sync::PyOnceLock,
16+
exceptions::{PyFileNotFoundError, PyValueError, PyPermissionError},
17+
intern,
18+
prelude::*,
19+
types::PyDict,
20+
sync::PyOnceLock,
1821
};
1922

2023
pub mod colors;
@@ -35,6 +38,8 @@ pub enum Error {
3538
FileNotFoundError,
3639
/// Permission denied to access or create the filesystem path.
3740
PermissionError,
41+
/// Indicate that the argument is an inappropriate value.
42+
ValueError(String),
3843
/// Other Python errors.
3944
Python(PyErr),
4045
}
@@ -53,6 +58,8 @@ If you use Anaconda, see https://github.com/PyO3/pyo3/issues/1554"),
5358
Error::PermissionError =>
5459
write!(f, "Permission denied to access or create the \
5560
filesystem path"),
61+
Error::ValueError(msg) =>
62+
write!(f, "ValueError: {}", msg),
5663
Error::Python(e) =>
5764
write!(f, "Python error: {}", e),
5865
}
@@ -61,7 +68,8 @@ If you use Anaconda, see https://github.com/PyO3/pyo3/issues/1554"),
6168

6269
impl std::error::Error for Error {}
6370

64-
/// Conversion from `PyErr` to `Error` (requires the Python handle).
71+
/// Conversion from `PyErr` to `Error`.
72+
// It requires the Python handle, so the `Into` trait cannot be used.
6573
trait IntoError {
6674
type T;
6775
fn into_error(self, py: Python<'_>) -> Result<Self::T, Error>;
@@ -76,6 +84,9 @@ impl<T> IntoError for Result<T, PyErr> {
7684
Error::FileNotFoundError
7785
} else if e.is_instance_of::<PyPermissionError>(py) {
7886
Error::PermissionError
87+
} else if e.is_instance_of::<PyValueError>(py) {
88+
let msg = e.value(py).str().unwrap();
89+
Error::ValueError(msg.to_string_lossy().into_owned())
7990
} else {
8091
Error::Python(e)
8192
}
@@ -99,38 +110,76 @@ impl From<&ImportError> for Error {
99110
/// in [`rcsetup`].
100111
#[derive(Debug)]
101112
pub struct RcParams {
102-
dict: Py<PyAny>,
113+
rc: Py<PyDict>,
103114
}
104115

116+
pub trait RcParamsValue {
117+
fn into_py<'py>(&self, py: Python<'py>) -> impl IntoPyObject<'py>;
118+
}
119+
120+
impl RcParamsValue for &str {
121+
fn into_py<'py>(&self, _py: Python<'py>) -> impl IntoPyObject<'py> {
122+
self
123+
}
124+
}
125+
126+
impl RcParamsValue for usize {
127+
fn into_py<'py>(&self, _py: Python<'py>) -> impl IntoPyObject<'py> {
128+
self
129+
}
130+
}
131+
132+
impl RcParamsValue for f64 {
133+
fn into_py<'py>(&self, _py: Python<'py>) -> impl IntoPyObject<'py> {
134+
self
135+
}
136+
}
137+
138+
// TODO: Make more types implement `RcParamsValue`.
139+
105140
impl RcParams {
141+
pub fn get(&self, key: &str) -> Option<Py<PyAny>> {
142+
Python::attach(|py| {
143+
self.rc.bind(py).get_item(key)
144+
.unwrap()
145+
.map(|o| o.unbind())
146+
})
147+
}
148+
149+
pub fn set<'py>(&self, key: &str, value: impl RcParamsValue) -> Result<(), Error> {
150+
Python::attach(|py| -> Result<_, Error> {
151+
self.rc.bind(py).set_item(key, value.into_py(py))
152+
.into_error(py)
153+
})
154+
}
155+
106156
/// Return the subset of the `self` dictionary whose keys match
107157
/// ([`using
108158
/// re.search()`](https://docs.python.org/3/library/re.html#re.search)) the
109159
/// given pattern.
110160
pub fn find_all(&self, pat: &str) -> Vec<String> {
111161
Python::attach(|py| {
112-
self.dict.bind(py)
162+
self.rc.bind(py)
113163
.call_method1(intern!(py, "find_all"), (pat,)).unwrap()
114164
.cast().unwrap()
115165
.extract().unwrap()
116166
})
117167
}
118168
}
119169

120-
pub fn rcsetup() {
121-
todo!()
122-
}
123-
124-
#[derive(Debug)]
125-
pub struct RcParamsRef<'a> {
126-
dict: &'a Bound<'a, PyAny>,
127-
}
128-
129-
fn rc_params(py: Python<'_>) -> RcParamsRef<'_> {
130-
static RCPARAMS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
131-
let dict = RCPARAMS.import(py, "matplotlib", "rcParams")
132-
.expect("Cannot find matplotlib.rcParams");
133-
RcParamsRef { dict }
170+
pub fn rc_params() -> &'static RcParams {
171+
static RCPARAMS: PyOnceLock<RcParams> = PyOnceLock::new();
172+
Python::attach(|py| {
173+
RCPARAMS.get_or_init(py, || {
174+
let rc = py.import("matplotlib")
175+
.expect("Cannot find matplotlib")
176+
.getattr("rcParams")
177+
.expect("Cannot find matplotlib.rcParams")
178+
.cast_into::<PyDict>().unwrap();
179+
RcParams { rc: rc.unbind() }
180+
});
181+
RCPARAMS.get(py).unwrap()
182+
})
134183
}
135184

136185

0 commit comments

Comments
 (0)