Skip to main content

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