#![deny(missing_docs)]
#[cfg(feature = "jupyter")]
pub mod jupyter;
#[cfg(feature = "ndarray")]
pub use plotkit_ndarray;
#[cfg(feature = "polars")]
pub use plotkit_polars;
pub use plotkit_core::annotations::{Annotation, ArrowStyle, TextAnnotation};
pub use plotkit_core::axes::{Axes, TwinSide};
pub use plotkit_core::error::{PlotError, Result};
pub use plotkit_core::figure::Figure;
pub use plotkit_core::primitives::Color;
pub use plotkit_core::primitives::{HAlign, VAlign};
pub use plotkit_core::scale::Scale;
pub use plotkit_core::series::{IntoCategories, IntoSeries};
pub use plotkit_core::theme::{GridAxis, LineStyle, Loc, Marker, Theme};
pub use plotkit_core::{
annotations, artist, axes, charts, colorbar, colormap, error, figure, layout, legend,
primitives, renderer, scale, series, theme, ticks,
};
use std::cell::RefCell;
use std::path::Path;
thread_local! {
static CURRENT_FIGURE: RefCell<Figure> = RefCell::new(Figure::new());
}
pub mod prelude {
pub use crate::FigureExt;
pub use crate::{
bar, clf, grid, hist, legend, plot, savefig, scatter, subplots, title, xlabel, xlim,
xscale, xticks, ylabel, ylim, yscale, yticks,
};
pub use plotkit_core::prelude::*;
}
pub fn plot<X: IntoSeries, Y: IntoSeries>(x: X, y: Y) -> Result<()> {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.plot(x, y)?;
Ok(())
})
}
pub fn scatter<X: IntoSeries, Y: IntoSeries>(x: X, y: Y) -> Result<()> {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.scatter(x, y)?;
Ok(())
})
}
pub fn bar<C: IntoCategories, H: IntoSeries>(categories: C, heights: H) -> Result<()> {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.bar(categories, heights)?;
Ok(())
})
}
pub fn hist<D: IntoSeries>(data: D, bins: usize) -> Result<()> {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.hist(data, bins)?;
Ok(())
})
}
pub fn title(s: &str) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_title(s);
})
}
pub fn xlabel(s: &str) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_xlabel(s);
})
}
pub fn ylabel(s: &str) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_ylabel(s);
})
}
pub fn xlim(min: f64, max: f64) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_xlim(min, max);
})
}
pub fn ylim(min: f64, max: f64) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_ylim(min, max);
})
}
pub fn grid(visible: bool) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.grid(visible);
})
}
pub fn xticks(ticks: &[f64]) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_xticks(ticks);
})
}
pub fn yticks(ticks: &[f64]) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_yticks(ticks);
})
}
pub fn xscale(scale: Scale) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_xscale(scale);
})
}
pub fn yscale(scale: Scale) {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.set_yscale(scale);
})
}
pub fn legend() {
CURRENT_FIGURE.with(|fig| {
let mut fig = fig.borrow_mut();
if fig.num_axes() == 0 {
fig.add_subplot(1, 1, 1);
}
fig.axes_mut(0)
.expect("axes[0] exists after add_subplot")
.legend();
})
}
pub fn savefig(path: impl AsRef<Path>) -> Result<()> {
CURRENT_FIGURE.with(|fig| {
let fig = fig.borrow();
save_figure(&fig, path.as_ref())
})
}
pub fn subplots(nrows: usize, ncols: usize) {
CURRENT_FIGURE.with(|fig| {
*fig.borrow_mut() = Figure::subplots(nrows, ncols);
})
}
pub fn clf() {
CURRENT_FIGURE.with(|fig| {
*fig.borrow_mut() = Figure::new();
})
}
pub fn save_figure(fig: &Figure, path: &Path) -> Result<()> {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
let bytes = match ext.as_str() {
"png" => {
let renderer = plotkit_render_skia::SkiaRenderer::new(fig.width(), fig.height());
fig.render_to(renderer)
}
#[cfg(feature = "svg")]
"svg" => {
let renderer = plotkit_render_svg::SvgRenderer::new(fig.width(), fig.height());
fig.render_to(renderer)
}
#[cfg(feature = "pdf")]
"pdf" => {
let renderer = plotkit_render_pdf::PdfRenderer::new(fig.width(), fig.height());
fig.render_to(renderer)
}
other => return Err(PlotError::UnsupportedFormat(other.to_string())),
};
std::fs::write(path, bytes)?;
Ok(())
}
pub trait FigureExt {
fn save(&self, path: impl AsRef<Path>) -> Result<()>;
fn to_png_bytes(&self) -> Result<Vec<u8>>;
fn to_svg_string(&self) -> Result<String>;
#[cfg(feature = "pdf")]
fn to_pdf_bytes(&self) -> Result<Vec<u8>>;
}
impl FigureExt for Figure {
fn save(&self, path: impl AsRef<Path>) -> Result<()> {
save_figure(self, path.as_ref())
}
fn to_png_bytes(&self) -> Result<Vec<u8>> {
let renderer = plotkit_render_skia::SkiaRenderer::new(self.width(), self.height());
Ok(self.render_to(renderer))
}
fn to_svg_string(&self) -> Result<String> {
#[cfg(feature = "svg")]
{
let renderer = plotkit_render_svg::SvgRenderer::new(self.width(), self.height());
let bytes = self.render_to(renderer);
String::from_utf8(bytes)
.map_err(|_| PlotError::UnsupportedFormat("svg encoding error".into()))
}
#[cfg(not(feature = "svg"))]
Err(PlotError::UnsupportedFormat(
"svg feature not enabled".into(),
))
}
#[cfg(feature = "pdf")]
fn to_pdf_bytes(&self) -> Result<Vec<u8>> {
let renderer = plotkit_render_pdf::PdfRenderer::new(self.width(), self.height());
Ok(self.render_to(renderer))
}
}