├── .gitignore ├── .pylintrc ├── .travis.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── simple.png └── simple.rs ├── matplotlibrc ├── rustfmt.toml ├── scripts ├── load_pickle_fig.py └── setup.sh └── src ├── axes2d.rs ├── backend ├── mod.rs ├── mpl.rs └── mpl_native.rs ├── figure.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | *.pdf 5 | *.png 6 | *.svg 7 | *.pkl 8 | 9 | !examples/*.png 10 | 11 | scatter.py 12 | gh-pages/ 13 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubnt-intrepid/rustplotlib/fc555e1a94769fa9438c9dee89be03ebf10b4de8/.pylintrc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | 4 | # necessary for `travis-cargo coveralls --no-sudo` 5 | #addons: 6 | # apt: 7 | # packages: 8 | # - libcurl4-openssl-dev 9 | # - libelf-dev 10 | # - libdw-dev 11 | # - binutils-dev # optional: only required for the --verify flag of coveralls 12 | 13 | rust: 14 | - stable 15 | #- nightly 16 | 17 | before_script: 18 | # load travis-cargo 19 | - | 20 | pip install 'travis-cargo<0.2' --user && 21 | export PATH=$HOME/.local/bin:$PATH 22 | 23 | script: 24 | - travis-cargo build 25 | - travis-cargo test 26 | #- travis-cargo bench 27 | - travis-cargo --only stable doc 28 | 29 | after_success: 30 | # upload the documentation from the build with stable (automatically only actually 31 | # runs on the master branch, not individual PRs) 32 | - travis-cargo --only stable doc-upload 33 | # measure code coverage and upload to coveralls.io (the verify 34 | # argument mitigates kcov crashes due to malformed debuginfo, at the 35 | # cost of some speed ) 36 | #- travis-cargo coveralls --no-sudo --verify 37 | 38 | env: 39 | global: 40 | # override the default `--features unstable` used for the nightly branch (optional) 41 | #- TRAVIS_CARGO_NIGHTLY_FEATURE=nightly 42 | - secure: "aWS8QQ41R6GpSkM56UzFWsI1QBzun1PatvW6OhQgojQY3yd6fk4DPJz6yuVcb56PpNPwJNfKZNIFLgB0d6a1ej/wAHisVHW+1aZfI2P9edFejMHU5nNqw2bz8t6H0+aEqIxv2aT35Q0PeLFgWj4bMTXixm8L1PfiKF4X3VQqtjQljagSIE9GI5k4Zg9N1GYEsTzAVvZADIkRXkbIuGWC474K8qD5MN0u9bPfGHiV22/Az1wgPMs27716vju4LCTf4zcdtIJY0IEBcPaV5BQuJ7KmUZ5Hcd8VQBYFkaKCgvsvLAN7+NS686LpKPNtG1WoPFSzeNn1a2XpcR/xXI+GZ82AMtO2hJsJCJbBzEGo3JPCeqN26SlNHrYipRiw2a7OywcPclIdyq4FJkp9E/A6NXbXP/em8d/IdCFELm8PlPAQ2IygwT6mrbkxUJGYT7ylvYcCCvL93qDl6mFHzSu0p2fQFTRlD7XYbeV2wm51mshxS3KGRgF0xXk3XT+YtCoc4LxelXZh3tY8OXWh/nbRopqRGyY70oxwg3Isv7CZPMsHoeZ78ofU4Sr2cVo2Zx8G0MLR2hOBG+UhGEU5jQyPodWQO/0EutqCLtlM7RyUS4Oth2Jtx1+gfFaIj68grXIcNedHV0YBGHYOxqGywY0b+9mhRcDo2IIdEdbLm6L6DqI=" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust.features": ["native"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.0.4 2 | * improve backends 3 | * remove backend: `MatplotlibFile` 4 | * partially support for subplots 5 | * add support for `plot()`, `fill_between()` 6 | * support for dumping current instance of the figure as Python's pickle format. 7 | 8 | ### v0.0.3 9 | * change internal structure of API 10 | 11 | ### v0.0.2 12 | * add example 13 | * fix re-export configuration 14 | 15 | ### v0.0.1 16 | * First release. 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustplotlib" 3 | version = "0.0.4" 4 | authors = ["Yusuke Sasaki "] 5 | description = "A tiny library to create charts, by using matplotlib." 6 | homepage = "https://github.com/ubnt-intrepid/rustplotlib" 7 | repository = "https://github.com/ubnt-intrepid/rustplotlib" 8 | documentation = "https://docs.rs/rustplotlib/" 9 | readme = "README.md" 10 | license = "MIT" 11 | 12 | [features] 13 | default = [] 14 | native = ["cpython"] 15 | 16 | [dependencies] 17 | cpython = { version = "^0.1.0", optional = true } 18 | 19 | [dev-dependencies] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yusuke Sasaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `rustplotlib` [![](https://img.shields.io/crates/v/rustplotlib.svg)](https://crates.io/crates/rustplotlib) [![docs.rs](https://docs.rs/rustplotlib/badge.svg)](https://docs.rs/rustplotlib) [![Build Status](https://travis-ci.org/ubnt-intrepid/rustplotlib.svg?branch=master)](https://travis-ci.org/ubnt-intrepid/rustplotlib) 2 | 3 | A tiny library for creating 2D charts, by using matplotlib. 4 | 5 | This project is inspired by mneumann's [matplotlib-rs](https://github.com/mneumann/matplotlib-rs), 6 | and SiegeLord's [RustGnuplot](https://github.com/SiegeLord/RustGnuplot). 7 | 8 | __WARNING__ 9 | 10 | This project is currently under the development. 11 | It means that some broken changes will be occurred in API. 12 | 13 | The development version of API documentation is [here](https://ubnt-intrepid.github.io/rustplotlib/rustplotlib/). 14 | 15 | ## Features 16 | * builder style API 17 | * support for multiple backends 18 | 19 | ## Example 20 | 21 | ```rust 22 | extern crate rustplotlib; 23 | 24 | use rustplotlib::Figure; 25 | 26 | fn make_figure<'a>(x: &'a [f64], y1: &'a [f64], y2: &'a [f64]) -> Figure<'a> { 27 | use rustplotlib::{Axes2D, Scatter, Line2D, FillBetween}; 28 | 29 | let ax1 = Axes2D::new() 30 | .add(Scatter::new(r"$y_1 = \sin(x)$") 31 | .data(x, y1) 32 | .marker("o")) 33 | .add(Line2D::new(r"$y_2 = \cos(x)$") 34 | .data(x, y2) 35 | .color("red") 36 | .marker("x") 37 | .linestyle("--") 38 | .linewidth(1.0)) 39 | .xlabel("Time [sec]") 40 | .ylabel("Distance [mm]") 41 | .legend("lower right") 42 | .xlim(0.0, 8.0) 43 | .ylim(-2.0, 2.0); 44 | 45 | let ax2 = Axes2D::new() 46 | .add(FillBetween::new() 47 | .data(x, y1, y2) 48 | .interpolate(true)) 49 | .xlim(0.0, 8.0) 50 | .ylim(-1.5, 1.5); 51 | 52 | Figure::new() 53 | .subplots(2, 1, vec![Some(ax1), Some(ax2)]) 54 | } 55 | 56 | fn main() { 57 | use std::f64::consts::PI; 58 | let x: Vec = (0..40).into_iter().map(|i| (i as f64) * 0.08 * PI).collect(); 59 | let y1: Vec = x.iter().map(|x| x.sin()).collect(); 60 | let y2: Vec = x.iter().map(|x| x.cos()).collect(); 61 | 62 | let fig = make_figure(&x, &y1, &y2); 63 | 64 | use rustplotlib::Backend; 65 | use rustplotlib::backend::Matplotlib; 66 | let mut mpl = Matplotlib::new().unwrap(); 67 | mpl.set_style("ggplot").unwrap(); 68 | 69 | fig.apply(&mut mpl).unwrap(); 70 | 71 | mpl.savefig("simple.png").unwrap(); 72 | mpl.dump_pickle("simple.fig.pickle").unwrap(); 73 | mpl.wait().unwrap(); 74 | } 75 | ``` 76 | 77 | ![example](examples/simple.png) 78 | 79 | See [examples/simple.rs](examples/simple.rs) for details. 80 | 81 | ## License 82 | This software is released under the MIT license. 83 | See [LICENSE](LICENSE) for details. 84 | -------------------------------------------------------------------------------- /examples/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubnt-intrepid/rustplotlib/fc555e1a94769fa9438c9dee89be03ebf10b4de8/examples/simple.png -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate rustplotlib; 2 | #[cfg(feature = "native")] 3 | extern crate cpython; 4 | 5 | use rustplotlib::{backend, Backend}; 6 | use rustplotlib::{Figure, Subplots, Axes2D, Scatter, Line2D, FillBetween}; 7 | use std::f64::consts::PI; 8 | 9 | fn make_figure<'a>(x: &'a [f64], y1: &'a [f64], y2: &'a [f64]) -> Figure<'a> { 10 | Figure::default().subplots(Subplots::new(2, 1) 11 | .at(0, 12 | Axes2D::default() 13 | .add(Scatter::new(r"$y_1 = \sin(x)$") 14 | .data(x, y1) 15 | .marker("o")) 16 | .add(Line2D::new(r"$y_2 = \cos(x)$") 17 | .data(x, y2) 18 | .color("red") 19 | .marker("x") 20 | .linestyle("--") 21 | .linewidth(1.0)) 22 | .legend("lower right") 23 | .xlabel("Time [sec]") 24 | .ylabel("Distance [mm]") 25 | .xlim(0.0, 8.0) 26 | .ylim(-2.0, 2.0)) 27 | .at(1, 28 | Axes2D::default() 29 | .add(FillBetween::default() 30 | .data(x, y1, y2) 31 | .interpolate(true)) 32 | .xlabel("Time [sec]") 33 | .ylabel("Distance [mm]") 34 | .xlim(0.0, 8.0) 35 | .ylim(-1.5, 1.5))) 36 | } 37 | 38 | fn apply_mpl(fig: &Figure, filename: &str) -> std::io::Result<()> { 39 | let mut mp = backend::Matplotlib::new()?; 40 | mp.set_style("ggplot")?; 41 | fig.apply(&mut mp)?; 42 | mp.tight_layout()?; 43 | mp.savefig(filename)? 44 | .dump_pickle(format!("{}.pkl", filename))? 45 | .wait() 46 | } 47 | 48 | #[cfg(feature = "native")] 49 | fn apply_mpl_native(fig: &Figure, filename: &str) -> std::io::Result<()> { 50 | let mut mp = backend::MatplotlibNative::new(); 51 | mp.set_style("dark_background")?; 52 | fig.apply(&mut mp)?; 53 | mp.tight_layout()?; 54 | mp.savefig(filename)?; 55 | mp.dump_pickle(format!("{}.pkl", filename))?; 56 | Ok(()) 57 | } 58 | 59 | fn main() { 60 | let x: Vec = (0..40).into_iter().map(|i| (i as f64) * 0.08 * PI).collect(); 61 | let y1: Vec = x.iter().map(|x| x.sin()).collect(); 62 | let y2: Vec = x.iter().map(|x| x.cos()).collect(); 63 | 64 | let fig = make_figure(&x, &y1, &y2); 65 | 66 | apply_mpl(&fig, "simple.png").unwrap(); 67 | #[cfg(feature = "native")] 68 | apply_mpl_native(&fig, "simple_native.png").unwrap(); 69 | } 70 | -------------------------------------------------------------------------------- /matplotlibrc: -------------------------------------------------------------------------------- 1 | backend: agg -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | write_mode = "Overwrite" -------------------------------------------------------------------------------- /scripts/load_pickle_fig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib as mpl 4 | mpl.use('qt5agg') 5 | 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import pickle as pl 9 | 10 | fig = pl.load(open('scatter_native.png.pkl', 'rb')) 11 | plt.show() -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | # vim set ft=bash : 2 | 3 | export PYTHONPATH=$(realpath $(dirname $(pyenv which python))/../lib/python*/site-packages/) 4 | -------------------------------------------------------------------------------- /src/axes2d.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use backend::Backend; 3 | 4 | /// Represents an instance of `matplotlib.axes.Axes`. 5 | #[derive(Debug, Default)] 6 | pub struct Axes2D<'a> { 7 | plot_data: Vec>, 8 | xlabel: Option, 9 | ylabel: Option, 10 | grid: bool, 11 | legend: Option, 12 | xlim: Option<(f64, f64)>, 13 | ylim: Option<(f64, f64)>, 14 | } 15 | 16 | impl<'a> Axes2D<'a> { 17 | /// add a plot data. 18 | pub fn add>>(mut self, p: P) -> Self { 19 | self.plot_data.push(p.into()); 20 | self 21 | } 22 | 23 | /// set the label text of x axis. 24 | pub fn xlabel(mut self, text: &str) -> Self { 25 | self.xlabel = Some(text.to_owned()); 26 | self 27 | } 28 | 29 | /// set the label text of y axis. 30 | pub fn ylabel(mut self, text: &str) -> Self { 31 | self.ylabel = Some(text.to_owned()); 32 | self 33 | } 34 | 35 | /// set whether the grid is shown or not. 36 | pub fn grid(mut self, enabled: bool) -> Self { 37 | self.grid = enabled; 38 | self 39 | } 40 | 41 | /// set the location of legend in the axes. 42 | /// 43 | /// if the value of `loc` is empty, the legend is hidden. 44 | pub fn legend(mut self, loc: &str) -> Self { 45 | self.legend = if loc.trim() != "" { 46 | Some(loc.to_owned()) 47 | } else { 48 | None 49 | }; 50 | self 51 | } 52 | 53 | /// set the range of x axis. 54 | pub fn xlim(mut self, lb: f64, ub: f64) -> Self { 55 | self.xlim = Some((lb, ub)); 56 | self 57 | } 58 | 59 | /// set the range of y axis. 60 | pub fn ylim(mut self, lb: f64, ub: f64) -> Self { 61 | self.ylim = Some((lb, ub)); 62 | self 63 | } 64 | 65 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 66 | for ref plot in &self.plot_data { 67 | plot.apply(mpl)?; 68 | } 69 | if let Some(ref xlabel) = self.xlabel { 70 | mpl.xlabel(xlabel)?; 71 | } 72 | if let Some(ref ylabel) = self.ylabel { 73 | mpl.ylabel(ylabel)?; 74 | } 75 | mpl.grid(self.grid)?; 76 | if let Some(ref loc) = self.legend { 77 | mpl.legend(loc)?; 78 | } 79 | if let Some(ref xlim) = self.xlim { 80 | mpl.xlim(xlim)?; 81 | } 82 | if let Some(ref ylim) = self.ylim { 83 | mpl.ylim(ylim)?; 84 | } 85 | Ok(()) 86 | } 87 | } 88 | 89 | 90 | /// Plot type. 91 | #[derive(Debug)] 92 | pub enum PlotData<'a> { 93 | Scatter(Scatter<'a>), 94 | Line2D(Line2D<'a>), 95 | FillBetween(FillBetween<'a>), 96 | } 97 | 98 | impl<'a> PlotData<'a> { 99 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 100 | match *self { 101 | PlotData::Scatter(ref s) => s.apply(mpl), 102 | PlotData::Line2D(ref l) => l.apply(mpl), 103 | PlotData::FillBetween(ref f) => f.apply(mpl), 104 | } 105 | } 106 | } 107 | 108 | #[derive(Debug, Default)] 109 | pub struct Scatter<'a> { 110 | xdata: &'a [f64], 111 | ydata: &'a [f64], 112 | label: Option, 113 | color: Option, 114 | marker: Option, 115 | } 116 | 117 | impl<'a> Scatter<'a> { 118 | pub fn new(name: &str) -> Scatter<'a> { 119 | Scatter::default().label(name) 120 | } 121 | 122 | pub fn data(mut self, xdata: &'a [f64], ydata: &'a [f64]) -> Self { 123 | self.xdata = xdata; 124 | self.ydata = ydata; 125 | self 126 | } 127 | 128 | pub fn label(mut self, text: &str) -> Self { 129 | self.label = Some(text.to_owned()); 130 | self 131 | } 132 | 133 | pub fn color(mut self, color: &str) -> Self { 134 | self.color = Some(color.to_owned()); 135 | self 136 | } 137 | 138 | pub fn marker(mut self, marker: &str) -> Self { 139 | self.marker = Some(marker.to_owned()); 140 | self 141 | } 142 | 143 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 144 | mpl.scatter(self.xdata, 145 | self.ydata, 146 | &self.label, 147 | &self.color, 148 | &self.marker)?; 149 | Ok(()) 150 | } 151 | } 152 | 153 | impl<'a> From> for PlotData<'a> { 154 | fn from(data: Scatter) -> PlotData { 155 | PlotData::Scatter(data) 156 | } 157 | } 158 | 159 | 160 | #[derive(Debug, Default)] 161 | pub struct Line2D<'a> { 162 | xdata: &'a [f64], 163 | ydata: &'a [f64], 164 | label: Option, 165 | color: Option, 166 | marker: Option, 167 | linestyle: Option, 168 | linewidth: Option, 169 | } 170 | 171 | impl<'a> Line2D<'a> { 172 | pub fn new(name: &str) -> Line2D<'a> { 173 | Line2D::default().label(name) 174 | } 175 | 176 | pub fn data(mut self, xdata: &'a [f64], ydata: &'a [f64]) -> Self { 177 | self.xdata = xdata; 178 | self.ydata = ydata; 179 | self 180 | } 181 | 182 | pub fn label(mut self, text: &str) -> Self { 183 | self.label = Some(text.to_owned()); 184 | self 185 | } 186 | 187 | pub fn color(mut self, color: &str) -> Self { 188 | self.color = Some(color.to_owned()); 189 | self 190 | } 191 | 192 | pub fn marker(mut self, marker: &str) -> Self { 193 | self.marker = Some(marker.to_owned()); 194 | self 195 | } 196 | 197 | pub fn linestyle(mut self, style: &str) -> Self { 198 | self.linestyle = Some(style.to_owned()); 199 | self 200 | } 201 | 202 | pub fn linewidth(mut self, width: f64) -> Self { 203 | self.linewidth = Some(width); 204 | self 205 | } 206 | 207 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 208 | mpl.plot(self.xdata, 209 | self.ydata, 210 | &self.label, 211 | &self.color, 212 | &self.marker, 213 | &self.linestyle, 214 | &self.linewidth)?; 215 | Ok(()) 216 | } 217 | } 218 | 219 | impl<'a> From> for PlotData<'a> { 220 | fn from(data: Line2D<'a>) -> PlotData<'a> { 221 | PlotData::Line2D(data) 222 | } 223 | } 224 | 225 | 226 | #[derive(Debug, Default)] 227 | pub struct FillBetween<'a> { 228 | x: &'a [f64], 229 | y1: &'a [f64], 230 | y2: &'a [f64], 231 | where_: Option<&'a [bool]>, 232 | interpolate: bool, 233 | step: Option, 234 | } 235 | 236 | impl<'a> FillBetween<'a> { 237 | pub fn data(mut self, x: &'a [f64], y1: &'a [f64], y2: &'a [f64]) -> Self { 238 | self.x = x; 239 | self.y1 = y1; 240 | self.y2 = y2; 241 | self 242 | } 243 | 244 | pub fn where_(mut self, where_: &'a [bool]) -> Self { 245 | self.where_ = Some(where_); 246 | self 247 | } 248 | 249 | pub fn interpolate(mut self, interpolate: bool) -> Self { 250 | self.interpolate = interpolate; 251 | self 252 | } 253 | 254 | pub fn step(mut self, step: &str) -> Self { 255 | self.step = Some(step.to_owned()); 256 | self 257 | } 258 | 259 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 260 | mpl.fill_between(self.x, 261 | self.y1, 262 | self.y2, 263 | &self.where_, 264 | self.interpolate, 265 | &self.step)?; 266 | Ok(()) 267 | } 268 | } 269 | 270 | impl<'a> From> for PlotData<'a> { 271 | fn from(data: FillBetween<'a>) -> PlotData<'a> { 272 | PlotData::FillBetween(data) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | mod mpl; 2 | #[cfg(feature = "native")] 3 | mod mpl_native; 4 | 5 | use std::io; 6 | 7 | pub use self::mpl::Matplotlib; 8 | #[cfg(feature = "native")] 9 | pub use self::mpl_native::MatplotlibNative; 10 | 11 | 12 | pub trait Backend { 13 | fn figure(&mut self) -> io::Result<&mut Self>; 14 | fn subplot(&mut self, rows: u32, cols: u32, n: u32) -> io::Result<&mut Self>; 15 | fn xlabel(&mut self, xlabel: &str) -> io::Result<&mut Self>; 16 | fn ylabel(&mut self, ylabel: &str) -> io::Result<&mut Self>; 17 | fn grid(&mut self, grid: bool) -> io::Result<&mut Self>; 18 | fn legend(&mut self, loc: &str) -> io::Result<&mut Self>; 19 | fn xlim(&mut self, xlim: &(f64, f64)) -> io::Result<&mut Self>; 20 | fn ylim(&mut self, ylim: &(f64, f64)) -> io::Result<&mut Self>; 21 | fn set_style(&mut self, stylename: &str) -> io::Result<&mut Self>; 22 | fn savefig(&mut self, filename: &str) -> io::Result<&mut Self>; 23 | fn show(&mut self) -> io::Result<&mut Self>; 24 | fn plot(&mut self, 25 | xdata: &[f64], 26 | ydata: &[f64], 27 | label: &Option, 28 | color: &Option, 29 | marker: &Option, 30 | linestyle: &Option, 31 | linewidth: &Option) 32 | -> io::Result<&mut Self>; 33 | fn scatter(&mut self, 34 | xdata: &[f64], 35 | ydata: &[f64], 36 | label: &Option, 37 | color: &Option, 38 | marker: &Option) 39 | -> io::Result<&mut Self>; 40 | fn fill_between(&mut self, 41 | x: &[f64], 42 | y1: &[f64], 43 | y2: &[f64], 44 | where_: &Option<&[bool]>, 45 | interpolate: bool, 46 | step: &Option) 47 | -> io::Result<&mut Self>; 48 | fn tight_layout(&mut self) -> io::Result<&mut Self>; 49 | } 50 | -------------------------------------------------------------------------------- /src/backend/mpl.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | use std::process::{Command, Child, Stdio}; 3 | use backend::Backend; 4 | 5 | /// Represents an instance of Python process which executes operations. 6 | pub struct Matplotlib { 7 | child: Child, 8 | } 9 | 10 | impl Matplotlib { 11 | /// create an instance of Matplotlib backend. 12 | pub fn new() -> io::Result { 13 | let child = Command::new("python").arg("-") 14 | .stdin(Stdio::piped()) 15 | .stdout(Stdio::inherit()) 16 | .stderr(Stdio::inherit()) 17 | .spawn()?; 18 | 19 | let mut mpl = Matplotlib { child: child }; 20 | mpl.exec("import matplotlib.pyplot as plt")?; 21 | Ok(mpl) 22 | } 23 | 24 | /// wait until all operations are finished. 25 | pub fn wait(&mut self) -> io::Result<()> { 26 | self.child.wait().and(Ok(())) 27 | } 28 | 29 | /// execute a string as Python script. 30 | pub fn exec>(&mut self, script: S) -> io::Result<&mut Self> { 31 | { 32 | let ref mut stdin = self.child.stdin.as_mut().unwrap(); 33 | stdin.write_all(script.as_ref().as_bytes())?; 34 | stdin.write_all(b"\n")?; 35 | } 36 | Ok(self) 37 | } 38 | 39 | // save current figure as a pickle-format file. 40 | pub fn dump_pickle>(&mut self, filename: S) -> io::Result<&mut Self> { 41 | self.exec("import numpy as np")?; 42 | self.exec("import pickle as pl")?; 43 | self.exec(format!("pl.dump(plt.gcf(), open('{}', 'wb'))", filename.as_ref()))?; 44 | Ok(self) 45 | } 46 | } 47 | 48 | impl Backend for Matplotlib { 49 | fn figure(&mut self) -> io::Result<&mut Self> { 50 | self.exec("plt.figure()") 51 | } 52 | 53 | fn subplot(&mut self, rows: u32, cols: u32, n: u32) -> io::Result<&mut Self> { 54 | self.exec(format!("plt.subplot({}, {}, {})", rows, cols, n)) 55 | } 56 | 57 | fn xlabel(&mut self, xlabel: &str) -> io::Result<&mut Self> { 58 | self.exec(format!("plt.xlabel('{}')", xlabel)) 59 | } 60 | 61 | fn ylabel(&mut self, ylabel: &str) -> io::Result<&mut Self> { 62 | self.exec(format!("plt.ylabel('{}')", ylabel)) 63 | } 64 | 65 | fn grid(&mut self, grid: bool) -> io::Result<&mut Self> { 66 | self.exec(format!("plt.grid({})", if grid { "True" } else { "False" })) 67 | } 68 | 69 | fn legend(&mut self, loc: &str) -> io::Result<&mut Self> { 70 | self.exec(format!("plt.legend(loc='{}')", loc)) 71 | } 72 | 73 | fn xlim(&mut self, xlim: &(f64, f64)) -> io::Result<&mut Self> { 74 | self.exec(format!("plt.xlim(({}, {}))", xlim.0, xlim.1)) 75 | } 76 | 77 | fn ylim(&mut self, ylim: &(f64, f64)) -> io::Result<&mut Self> { 78 | self.exec(format!("plt.ylim(({}, {}))", ylim.0, ylim.1)) 79 | } 80 | 81 | fn set_style(&mut self, stylename: &str) -> io::Result<&mut Self> { 82 | self.exec(format!("plt.style.use('{}')", stylename)) 83 | } 84 | 85 | fn savefig(&mut self, filename: &str) -> io::Result<&mut Self> { 86 | self.exec(format!("plt.savefig('{}')", filename)) 87 | } 88 | 89 | fn show(&mut self) -> io::Result<&mut Self> { 90 | self.exec("plt.show()") 91 | } 92 | 93 | fn scatter(&mut self, 94 | xdata: &[f64], 95 | ydata: &[f64], 96 | label: &Option, 97 | color: &Option, 98 | marker: &Option) 99 | -> io::Result<&mut Self> { 100 | let mut code = format!("plt.scatter({}, {}, ", to_pyvec(xdata), to_pyvec(ydata)); 101 | if let &Some(ref label) = label { 102 | code += &format!("label='{}', ", label); 103 | } 104 | if let &Some(ref color) = color { 105 | code += &format!("color='{}', ", color); 106 | } 107 | if let &Some(ref marker) = marker { 108 | code += &format!("marker='{}', ", marker); 109 | } 110 | code += ")"; 111 | self.exec(code) 112 | } 113 | 114 | fn plot(&mut self, 115 | xdata: &[f64], 116 | ydata: &[f64], 117 | label: &Option, 118 | color: &Option, 119 | marker: &Option, 120 | linestyle: &Option, 121 | linewidth: &Option) 122 | -> io::Result<&mut Self> { 123 | let mut code = format!("plt.plot({}, {}, ", to_pyvec(xdata), to_pyvec(ydata)); 124 | if let &Some(ref label) = label { 125 | code += &format!("label='{}', ", label); 126 | } 127 | if let &Some(ref color) = color { 128 | code += &format!("color='{}', ", color); 129 | } 130 | if let &Some(ref marker) = marker { 131 | code += &format!("marker='{}', ", marker); 132 | } 133 | if let &Some(ref ls) = linestyle { 134 | code += &format!("linestyle='{}', ", ls); 135 | } 136 | if let &Some(ref lw) = linewidth { 137 | code += &format!("linewidth='{}', ", lw); 138 | } 139 | code += ")"; 140 | self.exec(code) 141 | } 142 | 143 | fn fill_between(&mut self, 144 | x: &[f64], 145 | y1: &[f64], 146 | y2: &[f64], 147 | where_: &Option<&[bool]>, 148 | interpolate: bool, 149 | step: &Option) 150 | -> io::Result<&mut Self> { 151 | let mut code = format!("plt.fill_between({}, {}, {}, ", 152 | to_pyvec(x), 153 | to_pyvec(y1), 154 | to_pyvec(y2)); 155 | if let &Some(ref where_) = where_ { 156 | code += &format!("where='{}', ", to_pyvec(where_)); 157 | } 158 | code += &format!("interpolate={}, ", interpolate.to_pystr()); 159 | if let &Some(ref step) = step { 160 | code += &format!("step='{}', ", step); 161 | } 162 | code += ")"; 163 | self.exec(code) 164 | } 165 | 166 | fn tight_layout(&mut self) -> io::Result<&mut Self> { 167 | self.exec("plt.tight_layout()") 168 | } 169 | } 170 | 171 | impl Drop for Matplotlib { 172 | fn drop(&mut self) { 173 | let _ = self.child.kill(); 174 | } 175 | } 176 | 177 | trait ToPyStr { 178 | fn to_pystr(&self) -> String; 179 | } 180 | impl ToPyStr for f64 { 181 | fn to_pystr(&self) -> String { 182 | format!("{}", self) 183 | } 184 | } 185 | impl ToPyStr for bool { 186 | fn to_pystr(&self) -> String { 187 | if *self { 188 | "True".to_owned() 189 | } else { 190 | "False".to_owned() 191 | } 192 | } 193 | } 194 | 195 | fn to_pyvec(data: &[T]) -> String { 196 | let data: Vec = data.iter().map(|x| x.to_pystr()).collect(); 197 | format!("[{}]", data.join(",")) 198 | } 199 | -------------------------------------------------------------------------------- /src/backend/mpl_native.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use backend::Backend; 3 | use cpython::{GILGuard, Python, PyModule, PyDict, NoArgs}; 4 | 5 | 6 | pub struct MatplotlibNative { 7 | gil: GILGuard, 8 | plt: PyModule, 9 | } 10 | 11 | impl MatplotlibNative { 12 | pub fn new() -> MatplotlibNative { 13 | let gil = Python::acquire_gil(); 14 | let plt; 15 | { 16 | let py = gil.python(); 17 | plt = PyModule::import(py, "matplotlib.pyplot").unwrap(); 18 | } 19 | MatplotlibNative { 20 | gil: gil, 21 | plt: plt, 22 | } 23 | } 24 | 25 | pub fn py<'a>(&'a self) -> Python<'a> { 26 | self.gil.python() 27 | } 28 | 29 | pub fn exec>(&mut self, script: S) -> io::Result<&mut Self> { 30 | self.py().run(script.as_ref(), None, None).unwrap(); 31 | Ok(self) 32 | } 33 | 34 | // save current figure as a pickle-format file. 35 | pub fn dump_pickle>(&mut self, filename: S) -> io::Result<&mut Self> { 36 | let pl = PyModule::import(self.py(), "pickle").unwrap(); 37 | let gcf = self.plt.call(self.py(), "gcf", NoArgs, None).unwrap(); 38 | let file = self.py().eval(&format!("open('{}', 'wb')", filename.as_ref()), None, None).unwrap(); 39 | pl.call(self.py(), "dump", (gcf, file), None).unwrap(); 40 | Ok(self) 41 | } 42 | } 43 | 44 | impl Backend for MatplotlibNative { 45 | /// call `plt.figure()` to create a instance of `matplotlib.figure.Figure`. 46 | fn figure(&mut self) -> io::Result<&mut Self> { 47 | self.plt.call(self.py(), "figure", NoArgs, None).unwrap(); 48 | Ok(self) 49 | } 50 | 51 | fn savefig(&mut self, filename: &str) -> io::Result<&mut Self> { 52 | self.plt.call(self.py(), "savefig", (filename,), None).unwrap(); 53 | Ok(self) 54 | } 55 | 56 | fn show(&mut self) -> io::Result<&mut Self> { 57 | self.plt.call(self.py(), "show", NoArgs, None).unwrap(); 58 | Ok(self) 59 | } 60 | 61 | fn subplot(&mut self, i: u32, j: u32, k: u32) -> io::Result<&mut Self> { 62 | self.plt.call(self.py(), "subplot", (i, j, k), None).unwrap(); 63 | Ok(self) 64 | } 65 | 66 | fn xlabel(&mut self, xlabel: &str) -> io::Result<&mut Self> { 67 | self.plt.call(self.py(), "xlabel", (xlabel,), None).unwrap(); 68 | Ok(self) 69 | } 70 | 71 | fn ylabel(&mut self, ylabel: &str) -> io::Result<&mut Self> { 72 | self.plt.call(self.py(), "ylabel", (ylabel,), None).unwrap(); 73 | Ok(self) 74 | } 75 | 76 | fn grid(&mut self, grid: bool) -> io::Result<&mut Self> { 77 | self.plt.call(self.py(), "grid", (grid,), None).unwrap(); 78 | Ok(self) 79 | } 80 | 81 | fn legend(&mut self, loc: &str) -> io::Result<&mut Self> { 82 | let kwargs = PyDict::new(self.py()); 83 | kwargs.set_item(self.py(), "loc", loc).unwrap(); 84 | self.plt 85 | .call(self.py(), "legend", NoArgs, Some(&kwargs)) 86 | .unwrap(); 87 | Ok(self) 88 | } 89 | 90 | fn xlim(&mut self, xlim: &(f64, f64)) -> io::Result<&mut Self> { 91 | self.plt.call(self.py(), "xlim", xlim, None).unwrap(); 92 | Ok(self) 93 | } 94 | 95 | fn ylim(&mut self, ylim: &(f64, f64)) -> io::Result<&mut Self> { 96 | self.plt.call(self.py(), "ylim", ylim, None).unwrap(); 97 | Ok(self) 98 | } 99 | 100 | fn scatter(&mut self, 101 | xdata: &[f64], 102 | ydata: &[f64], 103 | label: &Option, 104 | color: &Option, 105 | marker: &Option) 106 | -> io::Result<&mut Self> { 107 | let kwargs = PyDict::new(self.py()); 108 | kwargs.set_item(self.py(), "label", label).unwrap(); 109 | kwargs.set_item(self.py(), "color", color).unwrap(); 110 | kwargs.set_item(self.py(), "marker", marker).unwrap(); 111 | self.plt.call(self.py(), "scatter", (xdata, ydata), Some(&kwargs)).unwrap(); 112 | Ok(self) 113 | } 114 | 115 | fn plot(&mut self, 116 | xdata: &[f64], 117 | ydata: &[f64], 118 | label: &Option, 119 | color: &Option, 120 | marker: &Option, 121 | linestyle: &Option, 122 | linewidth: &Option) 123 | -> io::Result<&mut Self> { 124 | let kwargs = PyDict::new(self.py()); 125 | kwargs.set_item(self.py(), "label", label).unwrap(); 126 | kwargs.set_item(self.py(), "color", color).unwrap(); 127 | kwargs.set_item(self.py(), "marker", marker).unwrap(); 128 | kwargs.set_item(self.py(), "ls", linestyle).unwrap(); 129 | kwargs.set_item(self.py(), "lw", linewidth).unwrap(); 130 | self.plt.call(self.py(), "plot", (xdata, ydata), Some(&kwargs)).unwrap(); 131 | Ok(self) 132 | } 133 | 134 | fn fill_between(&mut self, 135 | x: &[f64], 136 | y1: &[f64], 137 | y2: &[f64], 138 | where_: &Option<&[bool]>, 139 | interpolate: bool, 140 | step: &Option) 141 | -> io::Result<&mut Self> { 142 | let kwargs = PyDict::new(self.py()); 143 | kwargs.set_item(self.py(), "where", where_).unwrap(); 144 | kwargs.set_item(self.py(), "interpolate", interpolate).unwrap(); 145 | kwargs.set_item(self.py(), "step", step).unwrap(); 146 | self.plt.call(self.py(), "fill_between", (x, y1, y2), Some(&kwargs)).unwrap(); 147 | Ok(self) 148 | } 149 | 150 | fn set_style(&mut self, stylename: &str) -> io::Result<&mut Self> { 151 | use cpython::FromPyObject; 152 | let style = self.plt 153 | .get(self.py(), "style") 154 | .and_then(|ref style| PyModule::extract(self.py(), style)) 155 | .unwrap(); 156 | style.call(self.py(), "use", (stylename,), None).unwrap(); 157 | Ok(self) 158 | } 159 | 160 | fn tight_layout(&mut self) -> io::Result<&mut Self> { 161 | self.plt.call(self.py(), "tight_layout", NoArgs, None).unwrap(); 162 | Ok(self) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/figure.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use axes2d::Axes2D; 3 | use backend::Backend; 4 | 5 | /// Represents an instance of `matplotlib.figure.Figure`. 6 | #[derive(Debug, Default)] 7 | pub struct Figure<'a> { 8 | subplots: Option>, 9 | } 10 | 11 | impl<'a> Figure<'a> { 12 | pub fn subplots(mut self, subplots: Subplots<'a>) -> Self { 13 | self.subplots = Some(subplots); 14 | self 15 | } 16 | 17 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 18 | mpl.figure()?; 19 | if let Some(ref subplots) = self.subplots { 20 | subplots.apply(mpl)?; 21 | } 22 | Ok(()) 23 | } 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct Subplots<'a> { 28 | rows: u32, 29 | cols: u32, 30 | share_x: bool, 31 | share_y: bool, 32 | axes: Vec>>, 33 | } 34 | 35 | impl<'a> Subplots<'a> { 36 | pub fn new(rows: u32, cols: u32) -> Self { 37 | Subplots { 38 | rows: rows, 39 | cols: cols, 40 | share_x: false, 41 | share_y: false, 42 | axes: (0..((rows * cols) as usize)).map(|_| None).collect(), 43 | } 44 | } 45 | 46 | pub fn share_x(mut self, share_x: bool) -> Self { 47 | self.share_x = share_x; 48 | self 49 | } 50 | 51 | pub fn share_y(mut self, share_y: bool) -> Self { 52 | self.share_y = share_y; 53 | self 54 | } 55 | 56 | pub fn at(mut self, n: usize, axes: Axes2D<'a>) -> Self { 57 | *self.axes.get_mut(n).unwrap() = Some(axes); 58 | self 59 | } 60 | 61 | pub fn apply(&self, mpl: &mut B) -> io::Result<()> { 62 | for (i, axes) in self.axes.iter().enumerate() { 63 | if let &Some(ref axes) = axes { 64 | mpl.subplot(self.rows, self.cols, (i + 1) as u32)?; 65 | axes.apply(mpl)?; 66 | } 67 | } 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "native")] 2 | extern crate cpython; 3 | 4 | mod axes2d; 5 | mod figure; 6 | 7 | // re-exports 8 | pub mod backend; 9 | pub use axes2d::{Axes2D, PlotData, Scatter, Line2D, FillBetween}; 10 | pub use backend::Backend; 11 | pub use figure::{Figure, Subplots}; 12 | --------------------------------------------------------------------------------