#![cfg(feature = "sf")]
use ggplot_rs::data::{DataFrame, Value};
use ggplot_rs::geom::sf::GeomSf;
use ggplot_rs::geom::sf_text::{GeomSfLabel, GeomSfText, SfLabelAnchor};
use ggplot_rs::prelude::*;
fn strs(v: &[&str]) -> Vec<Value> {
v.iter().map(|s| Value::Str(s.to_string())).collect()
}
fn two_regions() -> DataFrame {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&[
"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
"POLYGON ((20 0, 30 0, 30 10, 20 10, 20 0))",
]),
);
df.add_column("name".into(), strs(&["West", "East"]));
df
}
#[test]
fn labels_render_over_their_polygons() {
let df = two_regions();
let svg = GGPlot::new(df.clone())
.aes(Aes::new().label("name"))
.geom_sf_with(GeomSf::default())
.geom_sf_text_with(GeomSfText::default().fun_geometry(SfLabelAnchor::Polylabel))
.render_svg_native()
.expect("sf_text render");
assert!(svg.contains("West") && svg.contains("East"), "{svg}");
}
#[test]
fn geom_sf_label_draws_a_background_box() {
let df = two_regions();
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_label_with(GeomSfLabel::default())
.render_svg_native()
.expect("sf_label render");
assert!(svg.contains("<rect"), "a background box: {svg}");
assert!(svg.contains("West") && svg.contains("East"));
}
#[test]
fn check_overlap_drops_the_lower_priority_label_when_crowded() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&[
"POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
"POLYGON ((1.01 0, 2.01 0, 2.01 1, 1.01 1, 1.01 0))",
]),
);
df.add_column(
"name".into(),
strs(&["Small region name", "Big region name"]),
);
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text_with(GeomSfText::default().check_overlap(true))
.render_svg_native_with_size(200, 200)
.expect("sf_text render");
assert!(svg.contains("region"));
}
#[test]
fn a_label_wider_than_the_panel_is_dropped_not_clipped() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&["POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"]),
);
df.add_column(
"name".into(),
strs(&["A label far too long to ever fit in a tiny panel"]),
);
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text()
.render_svg_native_with_size(60, 60)
.expect("sf_text render");
assert!(
!svg.contains("A label far too long"),
"an oversized label should be dropped, not overflow the panel: {svg}"
);
}
#[test]
fn centroid_anchor_is_selectable() {
let df = two_regions();
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text_with(GeomSfText::default().fun_geometry(SfLabelAnchor::Centroid))
.render_svg_native()
.expect("sf_text render");
assert!(svg.contains("West") && svg.contains("East"));
}
#[test]
fn halo_draws_extra_text_runs_behind_the_label() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&["POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"]),
);
df.add_column("name".into(), strs(&["Haloed"]));
let with_halo = GGPlot::new(df.clone())
.aes(Aes::new().label("name"))
.geom_sf_text_with(GeomSfText::default().halo((255, 255, 255), 2.0))
.render_svg_native()
.expect("sf_text render");
let without_halo = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text()
.render_svg_native()
.expect("sf_text render");
let count = |svg: &str| svg.matches("Haloed").count();
assert!(
count(&with_halo) > count(&without_halo),
"halo draws extra text runs: {} vs {}",
count(&with_halo),
count(&without_halo)
);
}
#[test]
fn a_non_positive_halo_radius_disables_it() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&["POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"]),
);
df.add_column("name".into(), strs(&["Plain"]));
let zero_radius = GGPlot::new(df.clone())
.aes(Aes::new().label("name"))
.geom_sf_text_with(GeomSfText::default().halo((255, 255, 255), 0.0))
.render_svg_native()
.expect("sf_text render");
let no_halo = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text()
.render_svg_native()
.expect("sf_text render");
assert_eq!(
zero_radius.matches("Plain").count(),
no_halo.matches("Plain").count()
);
}
#[test]
fn missing_geometry_column_is_a_clear_error() {
let mut df = DataFrame::new();
df.add_column("name".into(), strs(&["X"]));
let err = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_text()
.render_svg_native()
.unwrap_err();
assert!(format!("{err:?}").contains("geometry"), "{err:?}");
}
#[test]
fn missing_label_column_is_a_clear_error() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&["POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"]),
);
let err = GGPlot::new(df)
.aes(Aes::new())
.geom_sf_text()
.render_svg_native()
.unwrap_err();
assert!(format!("{err:?}").contains("label"), "{err:?}");
}
#[test]
fn geom_sf_label_ink_paper_theme_hook() {
let mut df = DataFrame::new();
df.add_column(
"geometry".into(),
strs(&["POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"]),
);
df.add_column("name".into(), strs(&["Themed"]));
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_label()
.theme(Theme::default().with_ink_paper((255, 255, 255), (0, 0, 0)))
.render_svg_native()
.expect("sf_label render");
assert!(svg.contains("#FFFFFF"), "{svg}");
}
#[test]
fn geom_sf_label_builders_change_behaviour() {
let df = two_regions();
let svg = GGPlot::new(df)
.aes(Aes::new().label("name"))
.geom_sf_label_with(
GeomSfLabel::default()
.fun_geometry(SfLabelAnchor::Centroid)
.check_overlap(true)
.project(ggplot_rs::spatial::SfProjection::Mercator),
)
.render_svg_native()
.expect("sf_label render");
assert!(svg.contains("West") && svg.contains("East"));
}