use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use super::{ChartSpec, validate_number, validate_text};
use crate::error::{ChartError, ChartWarning};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct VennSpec {
pub sets: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub regions: Vec<VennRegionSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct VennRegionSpec {
pub sets: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub count: Option<f64>,
}
impl VennSpec {
pub(crate) fn masks(&self) -> Vec<u8> {
if self.sets.len() == 2 {
vec![1, 2, 3]
} else {
vec![1, 2, 4, 3, 5, 6, 7]
}
}
pub(crate) fn mask_of(&self, names: &[String]) -> u8 {
names
.iter()
.filter_map(|name| self.sets.iter().position(|set| set == name))
.fold(0, |mask, index| mask | (1 << index))
}
pub(crate) fn region(&self, mask: u8) -> Option<&VennRegionSpec> {
self.regions
.iter()
.find(|region| self.mask_of(®ion.sets) == mask)
}
pub(crate) fn names(&self, mask: u8) -> Vec<&str> {
self.sets
.iter()
.enumerate()
.filter(|(index, _)| mask & (1 << index) != 0)
.map(|(_, set)| set.as_str())
.collect()
}
}
impl ChartSpec {
pub(super) fn validate_venn(&self) -> Result<Vec<ChartWarning>, ChartError> {
self.reject_chart_fields("a Venn diagram is drawn from its sets and regions")?;
let Some(venn) = &self.venn else {
return Err(ChartError::new(
"missing_venn",
"/venn",
"a Venn diagram requires a venn block",
));
};
if venn.sets.len() < 2 {
return Err(ChartError::new(
"not_enough_sets",
"/venn/sets",
"a Venn diagram needs two or three sets",
));
}
if venn.sets.len() > 3 {
return Err(ChartError::new(
"too_many_sets",
"/venn/sets",
"circles overlap in every region for two or three sets only; a diagram of more sets is no Venn diagram, so use a heatmap of the sets against each other, or three sets at a time",
));
}
let mut names = BTreeSet::new();
for (index, set) in venn.sets.iter().enumerate() {
validate_text(set, &format!("/venn/sets/{index}"), 60)?;
if !names.insert(set.as_str()) {
return Err(ChartError::new(
"duplicate_label",
format!("/venn/sets/{index}"),
"set names must be unique",
));
}
}
let mut masks = BTreeSet::new();
for (index, region) in venn.regions.iter().enumerate() {
let path = format!("/venn/regions/{index}");
if region.sets.is_empty() {
return Err(ChartError::new(
"empty_data",
format!("{path}/sets"),
"name the sets the region lies in",
));
}
for (position, name) in region.sets.iter().enumerate() {
if !names.contains(name.as_str()) {
return Err(ChartError::new(
"unknown_set",
format!("{path}/sets/{position}"),
"a region names a set from /venn/sets",
));
}
if region.sets[..position].contains(name) {
return Err(ChartError::new(
"duplicate_label",
format!("{path}/sets/{position}"),
"name every set of a region once",
));
}
}
if !masks.insert(venn.mask_of(®ion.sets)) {
return Err(ChartError::new(
"duplicate_region",
format!("{path}/sets"),
"a region may be given once",
));
}
if let Some(label) = ®ion.label {
validate_text(label, &format!("{path}/label"), 100)?;
}
if let Some(count) = region.count {
validate_number(count, &format!("{path}/count"))?;
if count < 0.0 {
return Err(ChartError::new(
"invalid_value",
format!("{path}/count"),
"a count must be zero or more",
));
}
}
}
Ok(Vec::new())
}
}