matplotlib/
lib.rs

1//! Quick-and-dirty plotting in Rust using Python and [Matplotlib][matplotlib],
2//! strongly inspired by the Haskell package [`matplotlib`][matplotlib-hs].
3//!
4//! ## Purpose
5//! Both this crate and `matplotlib` internally use an existing Matplotlib
6//! installation by generating a temporary Python source file, and simply
7//! calling the system's Python interpreter. This approach affords a number of
8//! advantages. The most significant is to use more familiar/convenient
9//! construct to separate the logic and data surrounding plotting commands from
10//! the canvases on which the data is eventually draw, leading to more modular
11//! code overall. `matplotlib` provides an elegant model to monoidally compose
12//! plotting commands, and this crate attempts to emulate it.
13//!
14//! However, neither this crate nor `matplotlib` are *safe* libraries. In
15//! particular, both allow for the injection of arbitrary Python code from bare
16//! string data. This allows for much flexibility, but of course makes a large
17//! class of operations opaque to the compiler. Users are therefore warned
18//! *against* using this crate in complex programs. Instead, this library
19//! targets small programs that only need to quickly generate a plot.
20//!
21//! You **should** use this library if you:
22//! - want an easy way to put some data in a nice-looking plot
23//! - like and/or are familiar with Matplotlib, but don't want to use Python
24//!   directly
25//!
26//! You **should not** use this library if you:
27//! - want assurances against invalid Python code output
28//! - want robust handling of errors generated by Python
29//!
30//! You may also be interested in:
31//! - [Plotpy][plotpy], a Rust library with a similar strategy and safer
32//!   constructs, but more verbose building patterns.
33//! - [Plotters][plotters], a pure-Rust plotting library with full control
34//!   over everything that goes on a figure.
35//!
36//! ## How it works
37//! The main two components of the library are the [`Mpl`] type, representing a
38//! plotting script, and the [`Matplotlib`] trait, representing an element of
39//! the script. A given `Mpl` object can be combined with any number of objects
40//! whose types implement `Matplotlib`, which allows for significant flexibility
41//! when it comes to library users defining their own plotting elements. When
42//! ready to be executed, the `Mpl` object's `run` method can be called to save
43//! the output of the script to a file, launch Matplotlib's interactive Qt
44//! interface, or both. The operations described above have also been overloaded
45//! onto Rust's `&` and `|` operators to mimic `matplotlib`'s
46//! API just for the fun of it.
47//!
48//! When `Mpl::run` is executed, any larger data structures associated with
49//! plotting commands (mostly numerical arrays) are serialized to JSON. This
50//! data, along with the plotting script itself, are written to the OS's default
51//! temp directory (e.g. `/tmp` on Linux), and then the system's default
52//! `python3` interpreter is called on the script using
53//! [`std::process::Command`], which blocks the calling thread. Obviously, an
54//! existing installation of Python 3 and Matplotlib are required. After the
55//! script exits, both the script and the JSON file are deleted.
56//!
57//! Although many common plotting commands are defined in [`commands`], users
58//! can define their own by simply implementing `Matplotlib`. This requires
59//! declaring whether the command should be counted as part of the script's
60//! prelude (in which case it is automatically sorted to the top of the script),
61//! what data should be included in the JSON file, and what Python code should
62//! eventually be included in the script. *This library does not validate any
63//! Python code whatsoever*. Users may also wish to implement [`MatplotlibOpts`]
64//! to add optional keyword arguments.
65//!
66//! ```
67//! use matplotlib::{
68//!     Matplotlib,
69//!     MatplotlibOpts,
70//!     Opt,
71//!     PyValue,
72//!     AsPy,
73//!     serde_json::Value,
74//! };
75//!
76//! // example impl for a basic call to `plot`
77//!
78//! #[derive(Clone, Debug)]
79//! struct Plot {
80//!     x: Vec<f64>,
81//!     y: Vec<f64>,
82//!     opts: Vec<Opt>, // optional keyword arguments
83//! }
84//!
85//! impl Plot {
86//!     /// Create a new `Plot` with no options.
87//!     fn new<X, Y>(x: X, y: Y) -> Self
88//!     where
89//!         X: IntoIterator<Item = f64>,
90//!         Y: IntoIterator<Item = f64>,
91//!     {
92//!         Self {
93//!             x: x.into_iter().collect(),
94//!             y: y.into_iter().collect(),
95//!             opts: Vec::new(),
96//!         }
97//!     }
98//! }
99//!
100//! impl Matplotlib for Plot {
101//!     // Commands with `is_prelude == true` are run first
102//!     fn is_prelude(&self) -> bool { false }
103//!
104//!     fn data(&self) -> Option<Value> {
105//!         let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
106//!         let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
107//!         Some(Value::Array(vec![x.into(), y.into()]))
108//!     }
109//!
110//!     fn py_cmd(&self) -> String {
111//!         // JSON data is guaranteed to be loaded in a variable called `data`
112//!         format!("ax.plot(data[0], data[1], {})", self.opts.as_py())
113//!     }
114//! }
115//!
116//! // allow for keyword arguments to be added
117//! impl MatplotlibOpts for Plot {
118//!     fn kwarg<T>(&mut self, key: &str, val: T) -> &mut Self
119//!     where T: Into<PyValue>
120//!     {
121//!         self.opts.push((key, val).into());
122//!         self
123//!     }
124//! }
125//! ```
126//!
127//! ## Example
128//! ```ignore
129//! use std::f64::consts::TAU;
130//! use matplotlib::{ Mpl, Run, MatplotlibOpts, commands as c };
131//!
132//! let dx: f64 = TAU / 50.0;
133//! let x: Vec<f64> = (0..50_u32).map(|k| f64::from(k) * dx).collect();
134//! let y1: Vec<f64> = x.iter().copied().map(f64::sin).collect();
135//! let y2: Vec<f64> = x.iter().copied().map(f64::cos).collect();
136//!
137//! Mpl::new()
138//!     & c::DefPrelude
139//!     & c::rcparam("axes.grid", true) // global rc parameters
140//!     & c::rcparam("axes.linewidth", 0.65)
141//!     & c::rcparam("lines.linewidth", 0.8)
142//!     & c::DefInit
143//!     & c::plot(x.clone(), y1) // the basic plotting command
144//!         .o("marker", "o") // pass optional keyword arguments
145//!         .o("color", "b")  // via `MatplotlibOpts`
146//!         .o("label", r"$\\sin(x)$")
147//!     & c::plot(x,         y2) // `&` is overloaded to allow for Haskell-like
148//!         .o("marker", "D")    // patterns, can also use `Mpl::then`
149//!         .o("color", "r")
150//!         .o("label", r"$\\cos(x)$")
151//!     & c::legend()
152//!     & c::xlabel("$x$")
153//!     | Run::Show // `|` consumes the final `Mpl` value; this calls
154//!                 // `pyplot.show` to launch an interactive interface
155//! ```
156//!
157//! [matplotlib]: https://matplotlib.org/
158//! [matplotlib-hs]: https://hackage.haskell.org/package/matplotlib
159//! [plotpy]: https://crates.io/crates/plotpy
160//! [plotters]: https://crates.io/crates/plotters
161
162mod core;
163pub use core::{
164    Matplotlib,
165    MatplotlibOpts,
166    Mpl,
167    GSPos,
168    Run,
169    Opt,
170    opt,
171    AsPy,
172    PyValue,
173    MplError,
174    MplResult,
175    PRELUDE,
176    INIT,
177};
178pub mod commands;
179
180/// Re-exported for compatibility.
181pub use serde_json;
182