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