#[cfg(feature = "sf")]
pub mod annotation_scale;
pub mod area;
pub mod arrow;
pub mod bar;
pub mod bin2d;
#[cfg(feature = "sf")]
pub mod bivariate_legend;
pub mod blank;
pub mod boxplot;
pub mod bracket;
pub mod candlestick;
pub mod censor;
pub mod col;
pub mod contour;
pub mod cooks;
pub mod count;
pub mod crossbar;
pub mod curve;
pub(crate) mod decorative;
pub mod density;
pub mod density2d;
pub mod dotplot;
pub mod errorbar;
pub mod errorbarh;
pub mod freqpoly;
pub mod hex;
pub mod histogram;
pub mod jitter;
pub(crate) mod layer_attrs;
pub mod line;
pub mod linerange;
pub mod path;
pub mod point;
pub mod pointrange;
pub mod polygon;
pub mod qq;
pub mod raster;
pub mod rect;
pub mod refline;
pub mod repel;
pub mod ribbon;
pub mod rug;
pub mod segment;
#[cfg(feature = "sf")]
pub mod sf;
#[cfg(feature = "sf")]
pub mod sf_text;
pub mod smooth;
pub mod spoke;
pub mod step;
pub(crate) mod support;
pub mod text;
pub mod tile;
pub mod violin;
use std::collections::HashMap;
use crate::aes::Aesthetic;
use crate::coord::Coord;
use crate::data::DataFrame;
use crate::position::Position;
use crate::render::backend::DrawBackend;
use crate::render::RenderError;
use crate::scale::ScaleSet;
use crate::stat::Stat;
use crate::theme::Theme;
#[derive(Clone, Debug, Default)]
pub struct GeomParams {
pub values: HashMap<String, f64>,
pub color: Option<(u8, u8, u8)>,
pub fill: Option<(u8, u8, u8)>,
pub alpha: Option<f64>,
}
pub trait Geom: Send + Sync {
fn draw(
&self,
data: &DataFrame,
coord: &dyn Coord,
scales: &ScaleSet,
theme: &Theme,
backend: &mut dyn DrawBackend,
) -> Result<(), RenderError>;
fn required_aes(&self) -> Vec<Aesthetic>;
fn default_stat(&self) -> Box<dyn Stat>;
fn default_position(&self) -> Box<dyn Position>;
fn default_params(&self) -> GeomParams;
fn name(&self) -> &str;
fn set_series_color(&mut self, _color: (u8, u8, u8)) {}
fn apply_element_geom(&mut self, _el: &crate::theme::elements::ElementGeom) {}
fn include_zero_baseline(&self) -> bool {
false
}
fn allows_infinite(&self) -> bool {
false
}
fn keeps_na_as_break(&self) -> bool {
false
}
fn consumes_tooltips(&self) -> bool {
false
}
fn setup_data(&self, _data: &mut DataFrame, _warnings: &mut Vec<String>) {}
fn inherit_aes(&self) -> bool {
true
}
fn mapped_only_columns(&self) -> &'static [&'static str] {
&[]
}
fn validate_layer(&self, _pre_stat_columns: &[String], _stat_name: &str) -> Result<(), String> {
Ok(())
}
}
pub(crate) fn tip_value(v: &crate::data::Value) -> String {
crate::format::format_value(v)
}
pub(crate) fn continuous_bar_half_width(
mapped_xs: impl Iterator<Item = f64>,
width: f64,
fallback: f64,
) -> f64 {
let mut xs: Vec<f64> = mapped_xs.filter(|x| x.is_finite()).collect();
xs.sort_by(|a, b| a.total_cmp(b));
xs.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
let gap = xs
.windows(2)
.map(|w| w[1] - w[0])
.fold(f64::INFINITY, f64::min);
if gap.is_finite() && gap > 0.0 {
gap * width / 2.0
} else {
fallback
}
}
pub(crate) fn raw_value(v: &crate::data::Value) -> Option<String> {
use crate::data::Value;
match v {
Value::Float(f) if f.is_finite() => Some(format!("{f}")),
Value::Float(_) | Value::Na => None,
Value::Integer(i) => Some(i.to_string()),
Value::DateTime(s) => Some(s.to_string()),
Value::Str(s) => Some(s.clone()),
Value::Bool(b) => Some(b.to_string()),
}
}
pub(crate) fn series_key(data: &DataFrame, i: usize) -> Option<String> {
["color", "fill", "group"].iter().find_map(|c| {
data.column(c)
.and_then(|col| col.get(i))
.filter(|v| !v.is_na())
.map(tip_value)
.filter(|s| !s.is_empty())
})
}
pub(crate) fn measured_value(data: &DataFrame, i: usize) -> Option<String> {
measured_raw(data, i).and_then(raw_value)
}
pub(crate) fn measured_raw(data: &DataFrame, i: usize) -> Option<&crate::data::Value> {
data.column(crate::position::RAW_Y_COL)
.or_else(|| data.column("y"))
.and_then(|c| c.get(i))
}
pub(crate) fn set_mark(
backend: &mut dyn DrawBackend,
tooltip: Option<String>,
x: Option<String>,
series: Option<String>,
value: Option<String>,
) {
backend.set_tooltip(tooltip);
backend.set_mark_axis(x);
backend.set_mark_series(series);
backend.set_mark_value(value);
}
pub(crate) fn resolve_font_family(geom_family: &str, theme: &Theme) -> Option<String> {
if !geom_family.is_empty() {
Some(geom_family.to_string())
} else if let Some(family) = &theme.geom.family {
Some(family.clone())
} else if theme.text.family.is_empty() {
None
} else {
Some(theme.text.family.clone())
}
}
pub(crate) fn parse_fontface(raw: &str) -> Option<crate::render::backend::FontFace> {
use crate::render::backend::FontFace;
match raw.to_lowercase().as_str() {
"plain" | "normal" => Some(FontFace::Plain),
"bold" => Some(FontFace::Bold),
"italic" | "oblique" => Some(FontFace::Italic),
_ => None,
}
}
pub(crate) fn clear_mark(backend: &mut dyn DrawBackend) {
set_mark(backend, None, None, None, None);
backend.set_mark_fvalue(None);
backend.set_mark_fx(None);
}
pub(crate) fn set_mark_formatted(
backend: &mut dyn DrawBackend,
scales: &crate::scale::ScaleSet,
x_raw: Option<&crate::data::Value>,
value_aes: &Aesthetic,
value_raw: Option<&crate::data::Value>,
) {
if !backend.wants_formatted_values() {
backend.set_mark_fvalue(None);
backend.set_mark_fx(None);
return;
}
let fvalue = value_raw
.filter(|v| !v.is_na())
.and_then(|v| scales.get(value_aes).and_then(|s| s.format_value(v)));
backend.set_mark_fvalue(fvalue);
let fx = x_raw
.filter(|v| !v.is_na())
.and_then(|v| scales.get(&Aesthetic::X).and_then(|s| s.format_value(v)));
backend.set_mark_fx(fx);
}
pub(crate) fn set_row_extras(backend: &mut dyn DrawBackend, data: &DataFrame, row: usize) {
if let Some(col) = data.column(crate::aes::TOOLTIP_INTERNAL_NAME) {
let tip = col.get(row).filter(|v| !v.is_na()).map(tip_value);
backend.set_tooltip(tip);
}
let mut attrs = Vec::new();
for name in data.column_names() {
if let Some(target) = name.strip_prefix(crate::aes::DATA_ATTR_INTERNAL_PREFIX) {
if let Some(v) = data.column(name).and_then(|c| c.get(row)) {
if !v.is_na() {
attrs.push((target.to_string(), tip_value(v)));
}
}
}
}
backend.set_mark_attrs(attrs);
let class = data
.column(crate::aes::CLASS_INTERNAL_NAME)
.and_then(|c| c.get(row))
.filter(|v| !v.is_na())
.map(tip_value)
.filter(|s| !s.is_empty());
backend.set_mark_class(class);
let mut group = None;
for name in data.column_names() {
if let Some(target) = name.strip_prefix(crate::aes::GROUP_WRAP_INTERNAL_PREFIX) {
if let Some(v) = data.column(name).and_then(|c| c.get(row)) {
if !v.is_na() {
group = Some((target.to_string(), tip_value(v)));
break;
}
}
}
}
backend.set_mark_group(group);
}
pub(crate) fn clear_row_extras(backend: &mut dyn DrawBackend) {
backend.set_tooltip(None);
backend.set_mark_attrs(Vec::new());
backend.set_mark_class(None);
backend.set_mark_group(None);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn draw_polyline_with_linewidth(
backend: &mut dyn DrawBackend,
points: &[(f64, f64)],
widths: &[f64],
color: (u8, u8, u8),
alpha: f64,
linetype: crate::render::backend::Linetype,
lineend: crate::render::backend::LineEnd,
arrow: Option<&crate::geom::arrow::Arrow>,
fallback_width: f64,
) -> Result<(), RenderError> {
if points.len() < 2 {
return Ok(());
}
let w0 = widths.first().copied().unwrap_or(fallback_width);
let varies = widths.len() == points.len() && widths.iter().any(|&w| (w - w0).abs() > 1e-9);
if varies {
for i in 0..points.len() - 1 {
backend.draw_line(
&points[i..=i + 1],
&crate::render::backend::LineStyle {
color,
alpha,
width: widths[i],
linetype: linetype.clone(),
lineend,
},
)?;
}
} else {
backend.draw_line(
points,
&crate::render::backend::LineStyle {
color,
alpha,
width: w0,
linetype,
lineend,
},
)?;
}
if let Some(arrow) = arrow {
let resolved_width = widths.last().copied().unwrap_or(fallback_width);
let hs = crate::geom::arrow::heads(points, arrow, resolved_width);
crate::geom::arrow::draw_heads(backend, &hs, arrow, color, alpha, resolved_width)?;
}
Ok(())
}