#![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::{primitives, renderer, error, series, scale, ticks, theme, layout, artist, annotations, axes, figure, legend, charts, colormap, colorbar};
pub use plotkit_core::figure::Figure;
pub use plotkit_core::axes::{Axes, TwinSide};
pub use plotkit_core::primitives::Color;
pub use plotkit_core::theme::{Theme, LineStyle, Marker, Loc, GridAxis};
pub use plotkit_core::scale::Scale;
pub use plotkit_core::series::{IntoSeries, IntoCategories};
pub use plotkit_core::error::{PlotError, Result};
pub use plotkit_core::annotations::{ArrowStyle, TextAnnotation, Annotation};
pub use plotkit_core::primitives::{HAlign, VAlign};
use std::cell::RefCell;
use std::path::Path;
thread_local! {
static CURRENT_FIGURE: RefCell<Figure> = RefCell::new(Figure::new());
}
pub mod prelude {
pub use plotkit_core::prelude::*;
pub use crate::{plot, scatter, bar, hist, title, xlabel, ylabel, xlim, ylim, grid, xticks, yticks, xscale, yscale, legend, savefig, clf, subplots};
pub use crate::FigureExt;
}
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)
}
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>;
}
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()))
}
}