Crates.io | matplotlib |
lib.rs | matplotlib |
version | 0.2.0 |
source | src |
created_at | 2024-08-01 20:17:17.989487 |
updated_at | 2024-10-25 06:54:42.802487 |
description | Quick-and-dirty plotting in Rust using Python and Matplotlib |
homepage | https://gitlab.com/whooie/mpl |
repository | https://gitlab.com/whooie/mpl |
max_upload_size | |
id | 1322475 |
size | 225,646 |
Quick-and-dirty plotting in Rust using Python and Matplotlib,
strongly inspired by the Haskell package matplotlib
.
Both this crate and matplotlib
internally use an existing Matplotlib
installation by generating a temporary Python source file, and simply calling
the system's Python interpreter. This approach affords a number of advantages.
The most significant is to use more familiar/convenient construct to separate
the logic and data surrounding plotting commands from the canvases on which the
data is eventually draw, leading to more modular code overall. matplotlib
provides an elegant model to monoidally compose plotting commands, and this
crate attempts to emulate it.
However, neither this crate nor matplotlib
are safe libraries. In
particular, both allow for the injection of arbitrary Python code from bare
string data. This allows for much flexibility, but of course makes a large class
of operations opaque to the compiler. Users are therefore warned against using
this crate in complex programs. Instead, this library targets small programs
that only need to quickly generate a plot.
You should use this library if you:
You should not use this library if you:
You may also be interested in:
The main two components of the library are the Mpl
type, representing a
plotting script, and the Matplotlib
trait, representing an element of the
script. A given Mpl
object can be combined with any number of objects whose
types implement Matplotlib
, which allows for significant flexibility when it
comes to library users defining their own plotting elements. When ready to be
executed, the Mpl
object's run
method can be called to save the output of
the script to a file, launch Matplotlib's interactive Qt interface, or both. The
operations listed above have also been overloaded onto Rust's &
and |
operators to mimic matplotlib
's API just for the fun of it.
When Mpl::run
is executed, any larger data structures associated with plotting
commands (mostly numerical arrays) are serialized to JSON. This data, along with
the plotting script itself, are written to the OS's default temp directory (e.g.
/tmp
on Linux), and then the system's default python3
interpreter is called
on the script using std::process::Command
, which blocks the calling thread.
Obviously, an existing installation of Python 3 and Matplotlib are required.
After the script exits, both the script and the JSON file are deleted.
Although many common plotting commands are defined in mpl::commands
, users can
define their own by simply implementing Matplotlib
. This requires declaring
whether the command should be counted as part of the script's prelude (in which
case it is automatically sorted to the top of the script), what data should be
included in the JSON file, and what Python code should eventually be included in
the script. This library does not validate any Python code whatsoever. Users
may also wish to implement MatplotlibOpts
to add optional keyword arguments.
use matplotlib::{
Matplotlib,
MatplotlibOpts,
Opt,
PyValue,
AsPy,
serde_json::Value,
};
// example impl for a basic call to `plot`
#[derive(Clone, Debug)]
struct Plot {
x: Vec<f64>,
y: Vec<f64>,
opts: Vec<Opt>, // optional keyword arguments
}
impl Plot {
/// Create a new `Plot` with no options.
fn new<X, Y>(x: X, y: Y) -> Self
where
X: IntoIterator<Item = f64>,
Y: IntoIterator<Item = f64>,
{
Self {
x: x.into_iter().collect(),
y: y.into_iter().collect(),
opts: Vec::new(),
}
}
}
impl Matplotlib for Plot {
// Commands with `is_prelude == true` are run first
fn is_prelude(&self) -> bool { false }
fn data(&self) -> Option<Value> {
let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
Some(Value::Array(vec![x.into(), y.into()]))
}
fn py_cmd(&self) -> String {
// JSON data is guaranteed to be loaded in a variable called `data`
format!("ax.plot(data[0], data[1], {})", self.opts.as_py())
}
}
// allow for keyword arguments to be added
impl MatplotlibOpts for Plot {
fn kwarg<T>(&mut self, key: &str, val: T) -> &mut Self
where T: Into<PyValue>
{
self.opts.push((key, val).into());
self
}
}
use std::f64::consts::TAU;
use matplotlib::{ Mpl, Run, MatplotlibOpts, commands as c };
let dx: f64 = TAU / 50.0;
let x: Vec<f64> = (0..50_u32).map(|k| f64::from(k) * dx).collect();
let y1: Vec<f64> = x.iter().copied().map(f64::sin).collect();
let y2: Vec<f64> = x.iter().copied().map(f64::cos).collect();
Mpl::new()
& c::DefPrelude // a bunch of imports
& c::rcparam("axes.grid", true) // global rc parameters
& c::rcparam("axes.linewidth", 0.65)
& c::rcparam("lines.linewidth", 0.8)
& c::DefInit // fig, ax = plt.subplots()
& c::plot(x.clone(), y1) // the basic plotting command
.o("marker", "o") // pass optional keyword arguments
.o("color", "b") // via `MatplotlibOpts`
.o("label", r"$\\sin(x)$")
& c::plot(x, y2) // `&` is overloaded to allow for Haskell-like
.o("marker", "D") // patterns, can also use `Mpl::then`
.o("color", "r")
.o("label", r"$\\cos(x)$")
& c::legend()
& c::xlabel("$x$")
| Run::Show // `|` consumes the final `Mpl` value; this calls
// `pyplot.show` to launch an interactive interface