pub mod traits;
pub mod basic;
pub mod boxplot;
pub mod heatmap;
pub mod histogram;
pub mod statistics;
pub mod categorical;
pub mod composition;
pub mod continuous;
pub mod discrete;
pub mod distribution;
pub mod error;
pub mod hierarchical;
pub mod polar;
#[cfg(feature = "3d")]
pub mod three_d;
pub mod vector;
pub mod composite;
#[doc(hidden)]
pub mod regression;
pub use traits::{
AxisScaleSupport, ComputedSeries, ComputedStyle, PlotArea, PlotCompute, PlotConfig, PlotData,
PlotPrimitive, PlotRender, StyledShape, draw_primitives, draw_primitives_svg,
};
pub use basic::{BarConfig, BarOrientation, LineConfig, ScatterConfig};
pub use distribution::{
BandwidthMethod, Boxen, BoxenConfig, BoxenData, BoxenOrientation, Ecdf, EcdfConfig, EcdfData,
EcdfStat, Kde, KdeConfig, KdeData, Violin, ViolinConfig, ViolinData, compute_boxen,
compute_ecdf, compute_kde,
};
pub use boxplot::{
BoxPlotConfig, BoxPlotData, CATEGORY_SLOT_HALF_WIDTH, OutlierMethod, WhiskerMethod,
calculate_box_plot, category_slot_span,
};
pub use heatmap::{
ColorbarFontSizes, HeatmapConfig, HeatmapData, HeatmapOrigin, Interpolation, process_heatmap,
process_heatmap_flat,
};
pub use histogram::{BinMethod, HistogramConfig, HistogramData, calculate_histogram};
pub use statistics::{iqr, mean, median, percentile, std_dev};
pub use continuous::contour::{
ContourConfig, ContourInterpolation, ContourPlotData, compute_contour_plot,
};
pub use discrete::{StemConfig, StemMarker, StemOrientation, StepConfig, StepWhere};
pub use composition::pie::{DEFAULT_DONUT_INNER_RADIUS, PieConfig, PieData};
pub use polar::polar_plot::{PolarPlotConfig, PolarPlotData, compute_polar_plot};
pub use polar::radar::{
RadarConfig, RadarPlotData, compute_radar_chart, compute_radar_chart_with_labels,
};
#[cfg(feature = "3d")]
pub use three_d::{
Line3DConfig, Scatter3DConfig, Surface3DConfig, SurfaceSampling, SurfaceShading,
Wireframe3DConfig,
};
pub use vector::{
Quiver, QuiverArrow, QuiverConfig, QuiverInput, QuiverPivot, QuiverPlotData, compute_quiver,
quiver_range,
};
#[cfg(test)]
mod catalog_is_true {
const CATALOG: &[(&str, &[&str])] = &[
("Line", &["line"]),
("Scatter", &["scatter"]),
("Bar", &["bar"]),
("Histogram", &["histogram"]),
("Box Plot", &["boxplot"]),
("KDE", &["kde"]),
("ECDF", &["ecdf"]),
("Violin", &["violin"]),
("Boxen", &["boxen"]),
("Pie", &["pie"]),
("Donut", &["donut"]),
("Contour", &["contour"]),
("Area", &["area"]),
("Fill Between", &["fill_between"]),
("Step", &["step"]),
("Stem", &["stem"]),
("Heatmap", &["heatmap"]),
("Error Bars", &["error_bars", "error_bars_xy"]),
("Polar Line", &["polar_line"]),
("Radar", &["radar"]),
("Quiver", &["quiver"]),
("Rug", &["rug"]),
("Strip", &["strip"]),
("Swarm", &["swarm"]),
("Hexbin", &["hexbin"]),
("Dendrogram", &["dendrogram"]),
("Grouped Bar", &["grouped_bar"]),
("Stacked Bar", &["stacked_bar"]),
("Stacked Area", &["stacked_area"]),
];
const AWAITING_A_BUILDER: &[(&str, &str)] = &[];
const CATALOG_3D: &[(&str, &str)] = &[
("Scatter3D", "scatter3d"),
("Line3D", "line3d"),
("Surface", "surface"),
("Wireframe", "wireframe"),
];
const BUILDER_SOURCES: &[&str] = &[
include_str!("../core/plot/series_api.rs"),
include_str!("../core/plot/series_builders.rs"),
include_str!("../core/plot/builder.rs"),
include_str!("../core/plot/annotations.rs"),
];
fn module_doc() -> String {
include_str!("mod.rs")
.lines()
.take_while(|line| line.starts_with("//!"))
.map(|line| line.trim_start_matches("//!").trim())
.collect::<Vec<_>>()
.join(" ")
}
fn declares(source: &str, method: &str) -> bool {
let needle = format!("pub fn {method}");
source.match_indices(&needle).any(|(at, _)| {
let rest = &source[at + needle.len()..];
matches!(rest.chars().next(), Some('(' | '<'))
})
}
fn has_builder_method(method: &str) -> bool {
BUILDER_SOURCES.iter().any(|s| declares(s, method))
}
#[test]
fn every_catalogued_plot_type_has_a_builder_method() {
for (plot_type, methods) in CATALOG {
for method in *methods {
assert!(
has_builder_method(method),
"the plots module doc advertises `{plot_type}` as drawable \
via `Plot::{method}`, but no `pub fn {method}` exists in \
any BUILDER_SOURCES file. Wire the method, drop the row \
from CATALOG and the doc table, or add the file that \
defines it to BUILDER_SOURCES."
);
}
}
}
#[test]
fn no_builder_exists_yet_for_the_types_listed_as_unreachable() {
for (plot_type, method) in AWAITING_A_BUILDER {
assert!(
!has_builder_method(method),
"`Plot::{method}` now exists, so `{plot_type}` is reachable and \
the docs in this file are out of date. Four edits: move it from \
AWAITING_A_BUILDER to CATALOG, add a row to the first doc \
table, delete its row from the `Not available` table, and bump \
both counts in the opening paragraph."
);
}
}
#[test]
fn the_unreachable_types_are_listed_as_unreachable() {
let doc = module_doc();
let (_, unreachable) = doc
.split_once("## Not available: implemented but unreachable")
.expect("the module doc must keep its `Not available` section");
for (plot_type, _) in AWAITING_A_BUILDER {
assert!(
unreachable.contains(*plot_type),
"`{plot_type}` has no builder but is not listed in the \
`Not available` table, so a reader has no way to learn that"
);
}
}
#[test]
fn every_document_that_states_the_catalogue_size_agrees_with_the_api() {
let expected = CATALOG.len();
let claim = format!("{expected} types from [`Plot`]");
let crate_doc = include_str!("../lib.rs");
assert!(
crate_doc.contains(&claim),
"src/lib.rs must say \"{claim}\" — it is the docs.rs landing page, \
and the builder now exposes {expected} plot types"
);
let with_3d = expected + CATALOG_3D.len();
for (doc_name, doc) in [
("src/lib.rs", crate_doc),
("README.md", include_str!("../../README.md")),
] {
let lowered = doc.to_lowercase();
for (offset, _) in lowered.match_indices("plot type") {
let head = &lowered[..offset];
let token: String = head
.trim_end_matches(|c: char| c.is_whitespace() || c == '*')
.chars()
.rev()
.take_while(char::is_ascii_digit)
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
let Ok(claimed) = token.parse::<usize>() else {
continue; };
assert!(
claimed == expected || claimed == with_3d,
"{doc_name} claims \"{claimed} plot types\", but the builder \
exposes {expected} ({with_3d} with the `3d` feature). Every \
statement of the count has to move together."
);
}
}
let readme = include_str!("../../README.md");
assert!(
readme.contains(&format!("{expected} plot types"))
|| readme.contains(&format!("All {expected} ")),
"README.md must state the catalogue size as {expected}"
);
for (plot_type, methods) in CATALOG {
let Some(method) = methods.first() else {
continue;
};
if !has_builder_method(method) {
continue;
}
for (doc_name, doc) in [("src/lib.rs", crate_doc), ("README.md", readme)] {
let Some((_, after)) = doc.split_once("no builder method") else {
continue;
};
let sentence = after.split('.').next().unwrap_or("");
assert!(
!sentence.contains(&plot_type.to_lowercase()),
"{doc_name} still lists `{plot_type}` as having no builder \
method, but `Plot::{method}` exists"
);
}
}
}
#[cfg(feature = "3d")]
#[test]
fn every_catalogued_3d_plot_type_has_a_builder_method() {
const PLOT3D_BUILDER: &str = include_str!("../core/plot3d/builder.rs");
for (plot_type, method) in CATALOG_3D {
assert!(
PLOT3D_BUILDER.contains(&format!("pub fn {method}")),
"the plots module doc advertises `{plot_type}` via \
`Plot3D::{method}`, but no `pub fn {method}` exists in \
src/core/plot3d/builder.rs"
);
}
}
#[test]
fn the_advertised_counts_match_the_catalog() {
let doc = module_doc();
let two_d = CATALOG.len();
let three_d = CATALOG_3D.len();
let total = two_d + three_d;
let headline = format!("**{two_d} plot types are reachable");
assert!(
doc.contains(&headline),
"the module doc must open with `{headline}...`; CATALOG has \
{two_d} entries. Update whichever is wrong."
);
let total_claim = format!("— {total} in total");
assert!(
doc.contains(&total_claim),
"the module doc must claim `{total_claim}` \
({two_d} from `Plot` + {three_d} from `Plot3D`)"
);
}
#[test]
fn the_doc_table_lists_every_catalogued_method() {
let doc = module_doc();
for (plot_type, methods) in CATALOG {
for method in *methods {
let named = doc.contains(&format!("`{method}`"));
let chained = doc.contains(&format!(".{method}("));
assert!(
named || chained,
"`Plot::{method}` draws `{plot_type}` but is not named in \
the doc table above, so a reader cannot find it"
);
}
}
}
#[test]
fn the_unreachable_table_does_not_claim_a_wired_type() {
let doc = module_doc();
let (_, unreachable) = doc
.split_once("## Not available: implemented but unreachable")
.expect("the module doc must keep its `Not available` section");
for (plot_type, _) in CATALOG {
assert!(
!unreachable.contains(&format!("| {plot_type} ")),
"`{plot_type}` has a builder method but is still listed as \
unreachable; delete its row from the second table"
);
}
}
}