Expand description
Quick-and-dirty plotting in Rust using Python and Matplotlib,
strongly inspired by the Haskell package matplotlib
.
§Purpose
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:
- want an easy way to put some data in a nice-looking plot
- like and/or are familiar with Matplotlib, but don’t want to use Python directly
You should not use this library if you:
- want assurances against invalid Python code output
- want robust handling of errors generated by Python
You may also be interested in:
- Plotpy, a Rust library with a similar strategy and safer constructs, but more verbose building patterns.
- Plotters, a pure-Rust plotting library with full control over everything that goes on a figure.
§How it works
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 described 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 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
}
}
§Example
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
& c::rcparam("axes.grid", true) // global rc parameters
& c::rcparam("axes.linewidth", 0.65)
& c::rcparam("lines.linewidth", 0.8)
& c::DefInit
& 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
Re-exports§
pub use serde_json;
Modules§
- commands
- Commonly used plotting commands.
Structs§
- GSPos
- A single subplot’s position in a
gridspec
. - Mpl
- Main script builder type.
- Opt
- An optional keyword argument.
Enums§
- MplError
- PyValue
- A primitive Python value or variable to be used in a keyword argument.
- Run
- Determines the final IO command(s) in the plotting script generated by an
Mpl
.
Constants§
- INIT
- Default initializer for plotting objects, defaulting to a single figure and axis frame.
- PRELUDE
- Default prelude to a Matplotlib script.
Traits§
- AsPy
- Convert a Rust value to a Python source code string.
- Matplotlib
- An executable element in a Matplotlib script.
- Matplotlib
Opts - Extends
Matplotlib
to take optional keyword arguments.