Skip to main content

merman_render/
venn.rs

1//! Source-backed Venn layout kernel and diagram adapter.
2//!
3//! This module ports the layout/geometry path used by `@upsetjs/venn.js@2.0.0` and the minimal
4//! `fmin@0.0.4` optimizer helpers it depends on. The diagram adapter is intentionally thin: it
5//! projects the core render model into the same helper layout surface that Mermaid consumes before
6//! SVG emission.
7
8use crate::config::{config_bool, config_f64};
9use crate::model::{
10    Bounds as LayoutBounds, VennAreaLayout, VennCircleLayout, VennDiagramLayout,
11    VennTextAreaLayout, VennTextDebugCellLayout, VennTextNodeLayout,
12};
13use crate::{Error, Result};
14use indexmap::IndexMap;
15use merman_core::diagrams::venn::VennDiagramRenderModel;
16use ryu_js::Buffer;
17use std::collections::{HashMap, HashSet};
18use std::f64::consts::{PI, TAU};
19
20const SMALL: f64 = 1e-10;
21const DEFAULT_SVG_WIDTH: f64 = 800.0;
22const DEFAULT_SVG_HEIGHT: f64 = 450.0;
23const DEFAULT_PADDING: f64 = 15.0;
24const REFERENCE_WIDTH: f64 = 1600.0;
25
26pub fn layout_venn_diagram(
27    semantic: &serde_json::Value,
28    diagram_title: Option<&str>,
29    effective_config: &serde_json::Value,
30) -> Result<VennDiagramLayout> {
31    let model: VennDiagramRenderModel = crate::json::from_value_ref(semantic)?;
32    layout_venn_diagram_typed(&model, diagram_title, effective_config)
33}
34
35pub fn layout_venn_diagram_typed(
36    model: &VennDiagramRenderModel,
37    diagram_title: Option<&str>,
38    effective_config: &serde_json::Value,
39) -> Result<VennDiagramLayout> {
40    let width = config_f64(effective_config, &["venn", "width"])
41        .unwrap_or(DEFAULT_SVG_WIDTH)
42        .max(1.0);
43    let height = config_f64(effective_config, &["venn", "height"])
44        .unwrap_or(DEFAULT_SVG_HEIGHT)
45        .max(1.0);
46    let padding = config_f64(effective_config, &["venn", "padding"])
47        .unwrap_or(DEFAULT_PADDING)
48        .max(0.0);
49    let use_max_width = config_bool(effective_config, &["venn", "useMaxWidth"]).unwrap_or(true);
50    let use_debug_layout =
51        config_bool(effective_config, &["venn", "useDebugLayout"]).unwrap_or(false);
52    let title = model
53        .title
54        .as_deref()
55        .map(str::trim)
56        .filter(|title| !title.is_empty())
57        .or_else(|| {
58            diagram_title
59                .map(str::trim)
60                .filter(|title| !title.is_empty())
61        });
62    let scale = width / REFERENCE_WIDTH;
63    let title_height = if title.is_some() { 48.0 * scale } else { 0.0 };
64    let diagram_height = (height - title_height).max(1.0);
65
66    let areas = model
67        .subsets
68        .iter()
69        .map(|subset| VennArea {
70            sets: subset.sets.clone(),
71            size: subset.size,
72            weight: None,
73            label: subset.label.clone(),
74        })
75        .collect::<Vec<_>>();
76    let layout_areas = if areas.is_empty() {
77        Vec::new()
78    } else {
79        compute_venn_layout(
80            &areas,
81            &VennLayoutOptions {
82                width,
83                height: diagram_height,
84                padding,
85                ..Default::default()
86            },
87        )
88        .map_err(|err| Error::InvalidModel {
89            message: err.to_string(),
90        })?
91    };
92
93    let areas = layout_areas
94        .iter()
95        .map(|area| VennAreaLayout {
96            sets: area.data.sets.clone(),
97            size: area.data.size,
98            label: area.data.label.clone(),
99            text_x: area.text.x.floor(),
100            text_y: area.text.y.floor(),
101            text_disjoint: area.text.disjoint,
102            circles: area
103                .circles
104                .iter()
105                .map(|circle| VennCircleLayout {
106                    set: circle.set.clone(),
107                    x: circle.x,
108                    y: circle.y,
109                    radius: circle.radius,
110                })
111                .collect(),
112            path: area.path.clone(),
113            distinct_path: area.distinct_path.clone(),
114        })
115        .collect::<Vec<_>>();
116    let layout_by_key = layout_areas
117        .iter()
118        .map(|area| (stable_sets_key(&area.data.sets), area))
119        .collect::<HashMap<_, _>>();
120    let (text_areas, text_nodes) =
121        layout_text_nodes(model, &layout_by_key, scale, use_debug_layout);
122
123    Ok(VennDiagramLayout {
124        bounds: Some(LayoutBounds {
125            min_x: 0.0,
126            min_y: 0.0,
127            max_x: width,
128            max_y: height,
129        }),
130        width,
131        height,
132        diagram_height,
133        title_height,
134        scale,
135        padding,
136        use_max_width,
137        use_debug_layout,
138        areas,
139        text_areas,
140        text_nodes,
141    })
142}
143
144fn layout_text_nodes(
145    model: &VennDiagramRenderModel,
146    layout_by_key: &HashMap<String, &VennLayoutArea>,
147    scale: f64,
148    use_debug_layout: bool,
149) -> (Vec<VennTextAreaLayout>, Vec<VennTextNodeLayout>) {
150    let mut nodes_by_area: IndexMap<
151        String,
152        Vec<&merman_core::diagrams::venn::VennTextNodeRenderModel>,
153    > = IndexMap::new();
154    for node in &model.text_nodes {
155        nodes_by_area
156            .entry(stable_sets_key(&node.sets))
157            .or_default()
158            .push(node);
159    }
160
161    let mut text_areas = Vec::new();
162    let mut text_nodes = Vec::new();
163    for (key, nodes) in nodes_by_area {
164        let Some(area) = layout_by_key.get(&key).copied() else {
165            continue;
166        };
167        if area.circles.is_empty() {
168            continue;
169        }
170
171        let center_x = area.text.x;
172        let center_y = area.text.y;
173        let min_circle_radius = area
174            .circles
175            .iter()
176            .map(|circle| circle.radius)
177            .fold(f64::INFINITY, f64::min);
178        let inner_radius_raw = area
179            .circles
180            .iter()
181            .map(|circle| {
182                circle.radius
183                    - ((center_x - circle.x).powi(2) + (center_y - circle.y).powi(2)).sqrt()
184            })
185            .fold(f64::INFINITY, f64::min);
186        let mut inner_radius = if inner_radius_raw.is_finite() {
187            inner_radius_raw.max(0.0)
188        } else {
189            0.0
190        };
191        if inner_radius == 0.0 && min_circle_radius.is_finite() {
192            inner_radius = min_circle_radius * 0.6;
193        }
194
195        let inner_width = (80.0 * scale).max(inner_radius * 2.0 * 0.95);
196        let inner_height = (60.0 * scale).max(inner_radius * 2.0 * 0.95);
197        let has_label = area
198            .data
199            .label
200            .as_deref()
201            .is_some_and(|label| !label.is_empty());
202        let label_offset_base = if has_label {
203            (32.0 * scale).min(inner_radius * 0.25)
204        } else {
205            0.0
206        };
207        let label_offset = label_offset_base + if nodes.len() <= 2 { 30.0 * scale } else { 0.0 };
208        let start_x = center_x - inner_width / 2.0;
209        let start_y = center_y - inner_height / 2.0 + label_offset;
210        let cols = (nodes.len() as f64).sqrt().ceil().max(1.0) as usize;
211        let rows = nodes.len().div_ceil(cols).max(1);
212        let cell_width = inner_width / cols as f64;
213        let cell_height = inner_height / rows as f64;
214
215        let mut debug_cells = Vec::new();
216        for (index, node) in nodes.iter().enumerate() {
217            let col = index % cols;
218            let row = index / cols;
219            let cell_x = start_x + cell_width * col as f64;
220            let cell_y = start_y + cell_height * row as f64;
221            if use_debug_layout {
222                debug_cells.push(VennTextDebugCellLayout {
223                    x: cell_x,
224                    y: cell_y,
225                    width: cell_width,
226                    height: cell_height,
227                });
228            }
229
230            let x = start_x + cell_width * (col as f64 + 0.5);
231            let y = start_y + cell_height * (row as f64 + 0.5);
232            let box_width = cell_width * 0.9;
233            let box_height = cell_height * 0.9;
234            text_nodes.push(VennTextNodeLayout {
235                sets: node.sets.clone(),
236                id: node.id.clone(),
237                label: node.label.clone(),
238                x: x - box_width / 2.0,
239                y: y - box_height / 2.0,
240                width: box_width,
241                height: box_height,
242            });
243        }
244
245        text_areas.push(VennTextAreaLayout {
246            sets: area.data.sets.clone(),
247            center_x,
248            center_y,
249            inner_radius,
250            font_size: 40.0 * scale,
251            debug_cells,
252        });
253    }
254
255    (text_areas, text_nodes)
256}
257
258fn stable_sets_key(sets: &[String]) -> String {
259    sets.join("|")
260}
261
262#[derive(Debug, Clone, PartialEq)]
263pub struct VennArea {
264    pub sets: Vec<String>,
265    pub size: f64,
266    pub weight: Option<f64>,
267    pub label: Option<String>,
268}
269
270impl VennArea {
271    pub fn new(sets: impl IntoIterator<Item = impl Into<String>>, size: f64) -> Self {
272        Self {
273            sets: sets.into_iter().map(Into::into).collect(),
274            size,
275            weight: None,
276            label: None,
277        }
278    }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq)]
282pub struct VennPoint {
283    pub x: f64,
284    pub y: f64,
285}
286
287#[derive(Debug, Clone, PartialEq)]
288pub struct VennCircle {
289    pub set: String,
290    pub x: f64,
291    pub y: f64,
292    pub radius: f64,
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub struct VennArc {
297    pub circle: VennCircle,
298    pub width: f64,
299    pub p1: VennPoint,
300    pub p2: VennPoint,
301    pub large: bool,
302    pub sweep: bool,
303}
304
305#[derive(Debug, Clone, Copy, PartialEq)]
306pub struct VennTextPoint {
307    pub x: f64,
308    pub y: f64,
309    pub disjoint: bool,
310}
311
312#[derive(Debug, Clone, PartialEq)]
313pub struct VennLayoutArea {
314    pub data: VennArea,
315    pub text: VennTextPoint,
316    pub circles: Vec<VennCircle>,
317    pub arcs: Vec<VennArc>,
318    pub path: String,
319    pub distinct_path: String,
320}
321
322#[derive(Debug, Clone)]
323pub struct VennLayoutOptions {
324    pub width: f64,
325    pub height: f64,
326    pub padding: f64,
327    pub normalize: bool,
328    pub orientation: f64,
329    pub scale_to_fit: Option<f64>,
330    pub symmetrical_text_centre: bool,
331    pub distinct: bool,
332    pub round: Option<usize>,
333    pub max_iterations: usize,
334    pub restarts: usize,
335    pub random_seed: u64,
336}
337
338impl Default for VennLayoutOptions {
339    fn default() -> Self {
340        Self {
341            width: 600.0,
342            height: 350.0,
343            padding: 15.0,
344            normalize: true,
345            orientation: PI / 2.0,
346            scale_to_fit: None,
347            symmetrical_text_centre: false,
348            distinct: false,
349            // `diagram.js` helper defaults `round` to 2.
350            round: Some(2),
351            max_iterations: 500,
352            restarts: 10,
353            random_seed: 1,
354        }
355    }
356}
357
358#[derive(Debug, thiserror::Error)]
359pub enum VennLayoutError {
360    #[error("missing pairwise overlap information for Venn set `{0}`")]
361    MissingPairwiseOverlap(String),
362    #[error("Venn layout requires at least one single-set area")]
363    EmptySingleSetAreas,
364}
365
366pub type VennLayoutResult<T> = std::result::Result<T, VennLayoutError>;
367pub type VennSolution = IndexMap<String, VennCircle>;
368
369type VennResult<T> = VennLayoutResult<T>;
370type Solution = VennSolution;
371
372#[derive(Debug, Clone)]
373struct AreaStats {
374    area: f64,
375    arcs: Vec<VennArc>,
376}
377
378#[derive(Debug, Clone)]
379struct IntersectionPoint {
380    point: VennPoint,
381    parent_index: [usize; 2],
382    angle: f64,
383}
384
385#[derive(Debug, Clone, Copy)]
386struct Bounds {
387    x_min: f64,
388    x_max: f64,
389    y_min: f64,
390    y_max: f64,
391}
392
393#[derive(Debug, Clone)]
394struct OptimParams {
395    max_iterations: Option<usize>,
396    min_error_delta: Option<f64>,
397}
398
399#[derive(Debug, Clone)]
400struct OptimResult {
401    x: Vec<f64>,
402}
403
404#[derive(Debug, Clone)]
405struct CgState {
406    x: Vec<f64>,
407    fx: f64,
408    fxprime: Vec<f64>,
409}
410
411#[derive(Debug, Clone)]
412struct XorShift64Star {
413    state: u64,
414}
415
416impl XorShift64Star {
417    fn new(seed: u64) -> Self {
418        Self { state: seed.max(1) }
419    }
420
421    fn next_u64(&mut self) -> u64 {
422        let mut x = self.state;
423        x ^= x >> 12;
424        x ^= x << 25;
425        x ^= x >> 27;
426        self.state = x;
427        x.wrapping_mul(0x2545F4914F6CDD1D_u64)
428    }
429
430    fn next_f64_unit(&mut self) -> f64 {
431        let u = self.next_u64() >> 11;
432        (u as f64) / ((1u64 << 53) as f64)
433    }
434}
435
436pub fn compute_venn_layout(
437    data: &[VennArea],
438    options: &VennLayoutOptions,
439) -> VennLayoutResult<Vec<VennLayoutArea>> {
440    let mut solution = venn(data, options)?;
441    if options.normalize {
442        solution = normalize_solution(&solution, options.orientation);
443    }
444    let circles = scale_solution(
445        &solution,
446        options.width,
447        options.height,
448        options.padding,
449        options.scale_to_fit,
450    );
451    let text_centres = compute_text_centres(&circles, data, options.symmetrical_text_centre);
452
453    let mut helpers = Vec::with_capacity(data.len());
454    for area in data {
455        let area_circles: Vec<VennCircle> = area
456            .sets
457            .iter()
458            .filter_map(|set| circles.get(set).cloned())
459            .collect();
460        let arcs = intersection_area_arcs(&area_circles);
461        let path = arcs_to_path(&arcs, options.round);
462        let text = text_centres
463            .get(&sets_key(&area.sets))
464            .copied()
465            .unwrap_or(VennTextPoint {
466                x: 0.0,
467                y: 0.0,
468                disjoint: true,
469            });
470        helpers.push(VennLayoutArea {
471            data: area.clone(),
472            text,
473            circles: area_circles,
474            arcs,
475            path,
476            distinct_path: String::new(),
477        });
478    }
479
480    for i in 0..helpers.len() {
481        let mut distinct_path = helpers[i].path.clone();
482        for j in 0..helpers.len() {
483            if helpers[j].data.sets.len() > helpers[i].data.sets.len()
484                && helpers[i]
485                    .data
486                    .sets
487                    .iter()
488                    .all(|set| helpers[j].data.sets.contains(set))
489            {
490                distinct_path.push(' ');
491                distinct_path.push_str(&helpers[j].path);
492            }
493        }
494        helpers[i].distinct_path = distinct_path;
495    }
496
497    Ok(helpers)
498}
499
500pub fn venn(sets: &[VennArea], options: &VennLayoutOptions) -> VennLayoutResult<VennSolution> {
501    let areas = add_missing_areas(sets, options.distinct);
502    let mut circles = best_initial_layout(&areas, options)?;
503    let setids: Vec<String> = circles.keys().cloned().collect();
504    let mut initial = Vec::with_capacity(setids.len() * 2);
505    for setid in &setids {
506        let circle = &circles[setid];
507        initial.push(circle.x);
508        initial.push(circle.y);
509    }
510
511    let params = OptimParams {
512        max_iterations: Some(options.max_iterations),
513        min_error_delta: None,
514    };
515    let solution = nelder_mead(
516        |values| {
517            let mut current = Solution::new();
518            for (i, setid) in setids.iter().enumerate() {
519                let base = &circles[setid];
520                current.insert(
521                    setid.clone(),
522                    VennCircle {
523                        set: setid.clone(),
524                        x: values[2 * i],
525                        y: values[2 * i + 1],
526                        radius: base.radius,
527                    },
528                );
529            }
530            loss_function(&current, &areas)
531        },
532        &initial,
533        &params,
534    );
535
536    for (i, setid) in setids.iter().enumerate() {
537        if let Some(circle) = circles.get_mut(setid) {
538            circle.x = solution.x[2 * i];
539            circle.y = solution.x[2 * i + 1];
540        }
541    }
542    Ok(circles)
543}
544
545fn add_missing_areas(areas: &[VennArea], distinct: bool) -> Vec<VennArea> {
546    let mut out = areas.to_vec();
547
548    if distinct {
549        let mut count: HashMap<String, f64> = HashMap::new();
550        for area in &out {
551            for i in 0..area.sets.len() {
552                let si = &area.sets[i];
553                *count.entry(si.clone()).or_insert(0.0) += area.size;
554                for j in i + 1..area.sets.len() {
555                    let sj = &area.sets[j];
556                    *count.entry(format!("{si};{sj}")).or_insert(0.0) += area.size;
557                    *count.entry(format!("{sj};{si}")).or_insert(0.0) += area.size;
558                }
559            }
560        }
561        for area in &mut out {
562            if area.sets.len() < 3 {
563                if let Some(size) = count.get(&area.sets.join(";")).copied() {
564                    area.size = size;
565                }
566            }
567        }
568    }
569
570    let mut ids = Vec::new();
571    let mut pairs = HashSet::new();
572    for area in &out {
573        if area.sets.len() == 1 {
574            ids.push(area.sets[0].clone());
575        } else if area.sets.len() == 2 {
576            let a = &area.sets[0];
577            let b = &area.sets[1];
578            pairs.insert(format!("{a};{b}"));
579            pairs.insert(format!("{b};{a}"));
580        }
581    }
582
583    ids.sort();
584    for i in 0..ids.len() {
585        for j in i + 1..ids.len() {
586            let a = &ids[i];
587            let b = &ids[j];
588            if !pairs.contains(&format!("{a};{b}")) {
589                out.push(VennArea::new([a.clone(), b.clone()], 0.0));
590            }
591        }
592    }
593    out
594}
595
596pub fn distance_from_intersect_area(r1: f64, r2: f64, overlap: f64) -> f64 {
597    if r1.min(r2).powi(2) * PI <= overlap + SMALL {
598        return (r1 - r2).abs();
599    }
600    bisect(
601        |distance| circle_overlap(r1, r2, distance) - overlap,
602        0.0,
603        r1 + r2,
604        100,
605        1e-10,
606    )
607}
608
609fn best_initial_layout(areas: &[VennArea], options: &VennLayoutOptions) -> VennResult<Solution> {
610    let mut initial = greedy_layout(areas)?;
611    if areas.len() >= 8 {
612        let constrained = constrained_mds_layout(areas, options)?;
613        let constrained_loss = loss_function(&constrained, areas);
614        let greedy_loss = loss_function(&initial, areas);
615        if constrained_loss + 1e-8 < greedy_loss {
616            initial = constrained;
617        }
618    }
619    Ok(initial)
620}
621
622pub fn greedy_layout(areas: &[VennArea]) -> VennLayoutResult<VennSolution> {
623    let mut circles = Solution::new();
624    let mut set_overlaps: IndexMap<String, Vec<SetOverlap>> = IndexMap::new();
625
626    for area in areas {
627        if area.sets.len() == 1 {
628            let set = area.sets[0].clone();
629            circles.insert(
630                set.clone(),
631                VennCircle {
632                    set: set.clone(),
633                    x: 1e10,
634                    y: 1e10,
635                    radius: (area.size / PI).sqrt(),
636                },
637            );
638            set_overlaps.insert(set, Vec::new());
639        }
640    }
641    if circles.is_empty() {
642        return Err(VennLayoutError::EmptySingleSetAreas);
643    }
644
645    for area in areas.iter().filter(|a| a.sets.len() == 2) {
646        let left = &area.sets[0];
647        let right = &area.sets[1];
648        let Some(left_circle) = circles.get(left) else {
649            continue;
650        };
651        let Some(right_circle) = circles.get(right) else {
652            continue;
653        };
654        let mut weight = area.weight.unwrap_or(1.0);
655        if area.size + SMALL
656            >= (left_circle.radius * left_circle.radius * PI)
657                .min(right_circle.radius * right_circle.radius * PI)
658        {
659            weight = 0.0;
660        }
661        if let Some(overlaps) = set_overlaps.get_mut(left) {
662            overlaps.push(SetOverlap {
663                set: right.clone(),
664                size: area.size,
665                weight,
666            });
667        }
668        if let Some(overlaps) = set_overlaps.get_mut(right) {
669            overlaps.push(SetOverlap {
670                set: left.clone(),
671                size: area.size,
672                weight,
673            });
674        }
675    }
676
677    let mut most_overlapped: Vec<MostOverlapped> = set_overlaps
678        .iter()
679        .map(|(set, overlaps)| MostOverlapped {
680            set: set.clone(),
681            size: overlaps.iter().map(|o| o.size * o.weight).sum(),
682        })
683        .collect();
684    most_overlapped.sort_by(|a, b| b.size.total_cmp(&a.size));
685
686    let mut positioned = HashSet::new();
687    let first = most_overlapped[0].set.clone();
688    position_set(
689        &mut circles,
690        &mut positioned,
691        &first,
692        VennPoint { x: 0.0, y: 0.0 },
693    );
694
695    for item in most_overlapped.iter().skip(1) {
696        let set_index = &item.set;
697        let mut overlap: Vec<SetOverlap> = set_overlaps
698            .get(set_index)
699            .cloned()
700            .unwrap_or_default()
701            .into_iter()
702            .filter(|o| positioned.contains(&o.set))
703            .collect();
704        overlap.sort_by(|a, b| b.size.total_cmp(&a.size));
705        if overlap.is_empty() {
706            return Err(VennLayoutError::MissingPairwiseOverlap(set_index.clone()));
707        }
708
709        let set = circles[set_index].clone();
710        let mut points = Vec::new();
711        for j in 0..overlap.len() {
712            let p1 = &circles[&overlap[j].set];
713            let d1 = distance_from_intersect_area(set.radius, p1.radius, overlap[j].size);
714            points.push(VennPoint {
715                x: p1.x + d1,
716                y: p1.y,
717            });
718            points.push(VennPoint {
719                x: p1.x - d1,
720                y: p1.y,
721            });
722            points.push(VennPoint {
723                x: p1.x,
724                y: p1.y + d1,
725            });
726            points.push(VennPoint {
727                x: p1.x,
728                y: p1.y - d1,
729            });
730
731            for k in j + 1..overlap.len() {
732                let p2 = &circles[&overlap[k].set];
733                let d2 = distance_from_intersect_area(set.radius, p2.radius, overlap[k].size);
734                points.extend(circle_circle_intersection(
735                    &VennCircle {
736                        set: String::new(),
737                        x: p1.x,
738                        y: p1.y,
739                        radius: d1,
740                    },
741                    &VennCircle {
742                        set: String::new(),
743                        x: p2.x,
744                        y: p2.y,
745                        radius: d2,
746                    },
747                ));
748            }
749        }
750
751        let mut best_loss = 1e50;
752        let mut best_point = points[0];
753        for point in points {
754            if let Some(circle) = circles.get_mut(set_index) {
755                circle.x = point.x;
756                circle.y = point.y;
757            }
758            let local_loss = loss_function(&circles, areas);
759            if local_loss < best_loss {
760                best_loss = local_loss;
761                best_point = point;
762            }
763        }
764
765        position_set(&mut circles, &mut positioned, set_index, best_point);
766    }
767
768    Ok(circles)
769}
770
771#[derive(Debug, Clone)]
772struct SetOverlap {
773    set: String,
774    size: f64,
775    weight: f64,
776}
777
778#[derive(Debug, Clone)]
779struct MostOverlapped {
780    set: String,
781    size: f64,
782}
783
784fn position_set(
785    circles: &mut Solution,
786    positioned: &mut HashSet<String>,
787    set: &str,
788    point: VennPoint,
789) {
790    if let Some(circle) = circles.get_mut(set) {
791        circle.x = point.x;
792        circle.y = point.y;
793    }
794    positioned.insert(set.to_string());
795}
796
797fn constrained_mds_layout(areas: &[VennArea], options: &VennLayoutOptions) -> VennResult<Solution> {
798    let mut sets = Vec::new();
799    let mut setids = HashMap::new();
800    for area in areas {
801        if area.sets.len() == 1 {
802            setids.insert(area.sets[0].clone(), sets.len());
803            sets.push(area.clone());
804        }
805    }
806    if sets.is_empty() {
807        return Err(VennLayoutError::EmptySingleSetAreas);
808    }
809
810    let (mut distances, constraints) = get_distance_matrices(areas, &sets, &setids);
811    let norm =
812        norm2(&distances.iter().map(|row| norm2(row)).collect::<Vec<_>>()) / distances.len() as f64;
813    if !norm.is_finite() || norm == 0.0 {
814        return greedy_layout(areas);
815    }
816    for row in &mut distances {
817        for value in row {
818            *value /= norm;
819        }
820    }
821
822    let mut rng = XorShift64Star::new(options.random_seed);
823    let mut best: Option<CgState> = None;
824    for _ in 0..options.restarts {
825        let initial: Vec<f64> = (0..distances.len() * 2)
826            .map(|_| rng.next_f64_unit())
827            .collect();
828        let current = conjugate_gradient(
829            |x, fxprime| constrained_mds_gradient(x, fxprime, &distances, &constraints),
830            &initial,
831            Some(options.max_iterations),
832        );
833        if best.as_ref().is_none_or(|b| current.fx < b.fx) {
834            best = Some(current);
835        }
836    }
837
838    let positions = best
839        .map(|b| b.x)
840        .unwrap_or_else(|| vec![0.0; distances.len() * 2]);
841    let mut circles = Solution::new();
842    for (i, set) in sets.iter().enumerate() {
843        let setid = set.sets[0].clone();
844        circles.insert(
845            setid.clone(),
846            VennCircle {
847                set: setid,
848                x: positions[2 * i] * norm,
849                y: positions[2 * i + 1] * norm,
850                radius: (set.size / PI).sqrt(),
851            },
852        );
853    }
854    Ok(circles)
855}
856
857fn get_distance_matrices(
858    areas: &[VennArea],
859    sets: &[VennArea],
860    setids: &HashMap<String, usize>,
861) -> (Vec<Vec<f64>>, Vec<Vec<i8>>) {
862    let mut distances = vec![vec![0.0; sets.len()]; sets.len()];
863    let mut constraints = vec![vec![0; sets.len()]; sets.len()];
864
865    for current in areas.iter().filter(|a| a.sets.len() == 2) {
866        let Some(&left) = setids.get(&current.sets[0]) else {
867            continue;
868        };
869        let Some(&right) = setids.get(&current.sets[1]) else {
870            continue;
871        };
872        let r1 = (sets[left].size / PI).sqrt();
873        let r2 = (sets[right].size / PI).sqrt();
874        let distance = distance_from_intersect_area(r1, r2, current.size);
875        distances[left][right] = distance;
876        distances[right][left] = distance;
877
878        let mut constraint = 0;
879        if current.size + SMALL >= sets[left].size.min(sets[right].size) {
880            constraint = 1;
881        } else if current.size <= SMALL {
882            constraint = -1;
883        }
884        constraints[left][right] = constraint;
885        constraints[right][left] = constraint;
886    }
887
888    (distances, constraints)
889}
890
891fn constrained_mds_gradient(
892    x: &[f64],
893    fxprime: &mut [f64],
894    distances: &[Vec<f64>],
895    constraints: &[Vec<i8>],
896) -> f64 {
897    fxprime.fill(0.0);
898    let mut loss = 0.0;
899    for i in 0..distances.len() {
900        let xi = x[2 * i];
901        let yi = x[2 * i + 1];
902        for j in i + 1..distances.len() {
903            let xj = x[2 * j];
904            let yj = x[2 * j + 1];
905            let dij = distances[i][j];
906            let constraint = constraints[i][j];
907            let squared_distance = (xj - xi).powi(2) + (yj - yi).powi(2);
908            let distance = squared_distance.sqrt();
909            let delta = squared_distance - dij * dij;
910
911            if (constraint > 0 && distance <= dij) || (constraint < 0 && distance >= dij) {
912                continue;
913            }
914
915            loss += 2.0 * delta * delta;
916            fxprime[2 * i] += 4.0 * delta * (xi - xj);
917            fxprime[2 * i + 1] += 4.0 * delta * (yi - yj);
918            fxprime[2 * j] += 4.0 * delta * (xj - xi);
919            fxprime[2 * j + 1] += 4.0 * delta * (yj - yi);
920        }
921    }
922    loss
923}
924
925pub fn loss_function(circles: &VennSolution, overlaps: &[VennArea]) -> f64 {
926    let mut output = 0.0;
927    for area in overlaps {
928        if area.sets.len() == 1 {
929            continue;
930        }
931        let overlap = if area.sets.len() == 2 {
932            let Some(left) = circles.get(&area.sets[0]) else {
933                continue;
934            };
935            let Some(right) = circles.get(&area.sets[1]) else {
936                continue;
937            };
938            circle_overlap(left.radius, right.radius, distance(left, right))
939        } else {
940            let area_circles: Vec<VennCircle> = area
941                .sets
942                .iter()
943                .filter_map(|set| circles.get(set).cloned())
944                .collect();
945            intersection_area(&area_circles)
946        };
947        let weight = area.weight.unwrap_or(1.0);
948        output += weight * (overlap - area.size) * (overlap - area.size);
949    }
950    output
951}
952
953pub fn normalize_solution(solution: &VennSolution, orientation: f64) -> VennSolution {
954    let circles: Vec<VennCircle> = solution.values().cloned().collect();
955    let mut clusters: Vec<Cluster> = disjoint_cluster(circles)
956        .into_iter()
957        .map(|mut circles| {
958            orientate_circles(&mut circles, orientation);
959            let bounds = get_bounding_box(&circles);
960            let size = (bounds.x_max - bounds.x_min) * (bounds.y_max - bounds.y_min);
961            Cluster {
962                circles,
963                bounds,
964                size,
965            }
966        })
967        .collect();
968
969    if clusters.is_empty() {
970        return Solution::new();
971    }
972    clusters.sort_by(|a, b| b.size.total_cmp(&a.size));
973    let mut circles = clusters[0].circles.clone();
974    let mut return_bounds = clusters[0].bounds;
975    let spacing = (return_bounds.x_max - return_bounds.x_min) / 50.0;
976
977    let mut index = 1;
978    while index < clusters.len() {
979        add_cluster(
980            clusters.get(index),
981            true,
982            false,
983            &mut circles,
984            &return_bounds,
985            spacing,
986        );
987        add_cluster(
988            clusters.get(index + 1),
989            false,
990            true,
991            &mut circles,
992            &return_bounds,
993            spacing,
994        );
995        add_cluster(
996            clusters.get(index + 2),
997            true,
998            true,
999            &mut circles,
1000            &return_bounds,
1001            spacing,
1002        );
1003        index += 3;
1004        return_bounds = get_bounding_box(&circles);
1005    }
1006
1007    circles_to_solution(circles)
1008}
1009
1010#[derive(Debug, Clone)]
1011struct Cluster {
1012    circles: Vec<VennCircle>,
1013    bounds: Bounds,
1014    size: f64,
1015}
1016
1017fn add_cluster(
1018    cluster: Option<&Cluster>,
1019    right: bool,
1020    bottom: bool,
1021    circles: &mut Vec<VennCircle>,
1022    return_bounds: &Bounds,
1023    spacing: f64,
1024) {
1025    let Some(cluster) = cluster else {
1026        return;
1027    };
1028    let bounds = cluster.bounds;
1029    let mut x_offset = if right {
1030        return_bounds.x_max - bounds.x_min + spacing
1031    } else {
1032        let mut offset = return_bounds.x_max - bounds.x_max;
1033        let centering =
1034            (bounds.x_max - bounds.x_min) / 2.0 - (return_bounds.x_max - return_bounds.x_min) / 2.0;
1035        if centering < 0.0 {
1036            offset += centering;
1037        }
1038        offset
1039    };
1040    let mut y_offset = if bottom {
1041        return_bounds.y_max - bounds.y_min + spacing
1042    } else {
1043        let mut offset = return_bounds.y_max - bounds.y_max;
1044        let centering =
1045            (bounds.y_max - bounds.y_min) / 2.0 - (return_bounds.y_max - return_bounds.y_min) / 2.0;
1046        if centering < 0.0 {
1047            offset += centering;
1048        }
1049        offset
1050    };
1051    if !x_offset.is_finite() {
1052        x_offset = 0.0;
1053    }
1054    if !y_offset.is_finite() {
1055        y_offset = 0.0;
1056    }
1057    for circle in &cluster.circles {
1058        let mut c = circle.clone();
1059        c.x += x_offset;
1060        c.y += y_offset;
1061        circles.push(c);
1062    }
1063}
1064
1065fn orientate_circles(circles: &mut [VennCircle], orientation: f64) {
1066    circles.sort_by(|a, b| b.radius.total_cmp(&a.radius));
1067    if let Some(largest) = circles.first().cloned() {
1068        for circle in circles.iter_mut() {
1069            circle.x -= largest.x;
1070            circle.y -= largest.y;
1071        }
1072    }
1073
1074    if circles.len() == 2 {
1075        let dist = distance(&circles[0], &circles[1]);
1076        if dist < (circles[1].radius - circles[0].radius).abs() {
1077            circles[1].x = circles[0].x + circles[0].radius - circles[1].radius - SMALL;
1078            circles[1].y = circles[0].y;
1079        }
1080    }
1081
1082    if circles.len() > 1 {
1083        let rotation = circles[1].x.atan2(circles[1].y) - orientation;
1084        let c = rotation.cos();
1085        let s = rotation.sin();
1086        for circle in circles.iter_mut() {
1087            let x = circle.x;
1088            let y = circle.y;
1089            circle.x = c * x - s * y;
1090            circle.y = s * x + c * y;
1091        }
1092    }
1093
1094    if circles.len() > 2 {
1095        let mut angle = circles[2].x.atan2(circles[2].y) - orientation;
1096        while angle < 0.0 {
1097            angle += TAU;
1098        }
1099        while angle > TAU {
1100            angle -= TAU;
1101        }
1102        if angle > PI {
1103            let slope = circles[1].y / (SMALL + circles[1].x);
1104            for circle in circles.iter_mut() {
1105                let d = (circle.x + slope * circle.y) / (1.0 + slope * slope);
1106                circle.x = 2.0 * d - circle.x;
1107                circle.y = 2.0 * d * slope - circle.y;
1108            }
1109        }
1110    }
1111}
1112
1113pub fn disjoint_cluster(circles: Vec<VennCircle>) -> Vec<Vec<VennCircle>> {
1114    let n = circles.len();
1115    let mut parent: Vec<usize> = (0..n).collect();
1116
1117    fn find(parent: &mut [usize], x: usize) -> usize {
1118        if parent[x] != x {
1119            parent[x] = find(parent, parent[x]);
1120        }
1121        parent[x]
1122    }
1123
1124    for i in 0..n {
1125        for j in i + 1..n {
1126            if distance(&circles[i], &circles[j]) + SMALL < circles[i].radius + circles[j].radius {
1127                let x_root = find(&mut parent, j);
1128                let y_root = find(&mut parent, i);
1129                parent[x_root] = y_root;
1130            }
1131        }
1132    }
1133
1134    let mut order = Vec::new();
1135    let mut grouped: IndexMap<usize, Vec<VennCircle>> = IndexMap::new();
1136    for i in 0..n {
1137        let root = find(&mut parent, i);
1138        if !grouped.contains_key(&root) {
1139            order.push(root);
1140        }
1141        grouped.entry(root).or_default().push(circles[i].clone());
1142    }
1143    order
1144        .into_iter()
1145        .filter_map(|root| grouped.shift_remove(&root))
1146        .collect()
1147}
1148
1149pub fn scale_solution(
1150    solution: &VennSolution,
1151    width: f64,
1152    height: f64,
1153    padding: f64,
1154    scale_to_fit: Option<f64>,
1155) -> VennSolution {
1156    let circles: Vec<VennCircle> = solution.values().cloned().collect();
1157    let width = width - 2.0 * padding;
1158    let height = height - 2.0 * padding;
1159    let bounds = get_bounding_box(&circles);
1160    if bounds.x_max == bounds.x_min || bounds.y_max == bounds.y_min {
1161        return solution.clone();
1162    }
1163
1164    let (x_scaling, y_scaling) = if let Some(scale_to_fit) = scale_to_fit {
1165        let to_scale_diameter = (scale_to_fit / PI).sqrt() * 2.0;
1166        (width / to_scale_diameter, height / to_scale_diameter)
1167    } else {
1168        (
1169            width / (bounds.x_max - bounds.x_min),
1170            height / (bounds.y_max - bounds.y_min),
1171        )
1172    };
1173    let scaling = y_scaling.min(x_scaling);
1174    let x_offset = (width - (bounds.x_max - bounds.x_min) * scaling) / 2.0;
1175    let y_offset = (height - (bounds.y_max - bounds.y_min) * scaling) / 2.0;
1176
1177    circles_to_solution(
1178        circles
1179            .into_iter()
1180            .map(|circle| VennCircle {
1181                set: circle.set,
1182                radius: scaling * circle.radius,
1183                x: padding + x_offset + (circle.x - bounds.x_min) * scaling,
1184                y: padding + y_offset + (circle.y - bounds.y_min) * scaling,
1185            })
1186            .collect(),
1187    )
1188}
1189
1190fn get_bounding_box(circles: &[VennCircle]) -> Bounds {
1191    let mut bounds = Bounds {
1192        x_min: f64::INFINITY,
1193        x_max: f64::NEG_INFINITY,
1194        y_min: f64::INFINITY,
1195        y_max: f64::NEG_INFINITY,
1196    };
1197    for circle in circles {
1198        bounds.x_min = bounds.x_min.min(circle.x - circle.radius);
1199        bounds.x_max = bounds.x_max.max(circle.x + circle.radius);
1200        bounds.y_min = bounds.y_min.min(circle.y - circle.radius);
1201        bounds.y_max = bounds.y_max.max(circle.y + circle.radius);
1202    }
1203    bounds
1204}
1205
1206fn circles_to_solution(circles: Vec<VennCircle>) -> Solution {
1207    let mut solution = Solution::new();
1208    for circle in circles {
1209        solution.insert(circle.set.clone(), circle);
1210    }
1211    solution
1212}
1213
1214pub fn intersection_area(circles: &[VennCircle]) -> f64 {
1215    intersection_area_stats(circles).area
1216}
1217
1218fn intersection_area_stats(circles: &[VennCircle]) -> AreaStats {
1219    if circles.is_empty() {
1220        return AreaStats {
1221            area: 0.0,
1222            arcs: Vec::new(),
1223        };
1224    }
1225
1226    let intersection_points = get_intersection_points(circles);
1227    let mut inner_points: Vec<IntersectionPoint> = intersection_points
1228        .into_iter()
1229        .filter(|p| contained_in_circles(p.point, circles))
1230        .collect();
1231
1232    let mut arc_area = 0.0;
1233    let mut polygon_area = 0.0;
1234    let mut arcs = Vec::new();
1235
1236    if inner_points.len() > 1 {
1237        let center = get_center(inner_points.iter().map(|p| p.point));
1238        for point in &mut inner_points {
1239            point.angle = (point.point.x - center.x).atan2(point.point.y - center.y);
1240        }
1241        inner_points.sort_by(|a, b| b.angle.total_cmp(&a.angle));
1242
1243        let mut p2 = inner_points[inner_points.len() - 1].clone();
1244        for p1 in &inner_points {
1245            polygon_area += (p2.point.x + p1.point.x) * (p1.point.y - p2.point.y);
1246            let mid_point = VennPoint {
1247                x: (p1.point.x + p2.point.x) / 2.0,
1248                y: (p1.point.y + p2.point.y) / 2.0,
1249            };
1250            let mut arc: Option<VennArc> = None;
1251
1252            for parent in p1.parent_index {
1253                if !p2.parent_index.contains(&parent) {
1254                    continue;
1255                }
1256                let circle = &circles[parent];
1257                let a1 = (p1.point.x - circle.x).atan2(p1.point.y - circle.y);
1258                let a2 = (p2.point.x - circle.x).atan2(p2.point.y - circle.y);
1259                let mut angle_diff = a2 - a1;
1260                if angle_diff < 0.0 {
1261                    angle_diff += TAU;
1262                }
1263                let a = a2 - angle_diff / 2.0;
1264                let mut width = distance_points(
1265                    mid_point,
1266                    VennPoint {
1267                        x: circle.x + circle.radius * a.sin(),
1268                        y: circle.y + circle.radius * a.cos(),
1269                    },
1270                );
1271                if width > circle.radius * 2.0 {
1272                    width = circle.radius * 2.0;
1273                }
1274                if arc.as_ref().is_none_or(|current| current.width > width) {
1275                    arc = Some(VennArc {
1276                        circle: circle.clone(),
1277                        width,
1278                        p1: p1.point,
1279                        p2: p2.point,
1280                        large: width > circle.radius,
1281                        sweep: true,
1282                    });
1283                }
1284            }
1285
1286            if let Some(arc) = arc {
1287                arc_area += circle_area(arc.circle.radius, arc.width);
1288                arcs.push(arc);
1289                p2 = p1.clone();
1290            }
1291        }
1292    } else {
1293        let mut smallest = &circles[0];
1294        for circle in circles.iter().skip(1) {
1295            if circle.radius < smallest.radius {
1296                smallest = circle;
1297            }
1298        }
1299
1300        let mut disjoint = false;
1301        for circle in circles {
1302            if distance(circle, smallest) > (smallest.radius - circle.radius).abs() {
1303                disjoint = true;
1304                break;
1305            }
1306        }
1307
1308        if disjoint {
1309            arc_area = 0.0;
1310            polygon_area = 0.0;
1311        } else {
1312            arc_area = smallest.radius * smallest.radius * PI;
1313            arcs.push(VennArc {
1314                circle: smallest.clone(),
1315                p1: VennPoint {
1316                    x: smallest.x,
1317                    y: smallest.y + smallest.radius,
1318                },
1319                p2: VennPoint {
1320                    x: smallest.x - SMALL,
1321                    y: smallest.y + smallest.radius,
1322                },
1323                width: smallest.radius * 2.0,
1324                large: true,
1325                sweep: true,
1326            });
1327        }
1328    }
1329
1330    polygon_area /= 2.0;
1331    AreaStats {
1332        area: arc_area + polygon_area,
1333        arcs,
1334    }
1335}
1336
1337fn contained_in_circles(point: VennPoint, circles: &[VennCircle]) -> bool {
1338    circles
1339        .iter()
1340        .all(|circle| distance_point_circle(point, circle) < circle.radius + SMALL)
1341}
1342
1343fn get_intersection_points(circles: &[VennCircle]) -> Vec<IntersectionPoint> {
1344    let mut out = Vec::new();
1345    for i in 0..circles.len() {
1346        for j in i + 1..circles.len() {
1347            for point in circle_circle_intersection(&circles[i], &circles[j]) {
1348                out.push(IntersectionPoint {
1349                    point,
1350                    parent_index: [i, j],
1351                    angle: 0.0,
1352                });
1353            }
1354        }
1355    }
1356    out
1357}
1358
1359pub fn circle_area(r: f64, width: f64) -> f64 {
1360    r * r * (1.0 - width / r).acos() - (r - width) * (width * (2.0 * r - width)).sqrt()
1361}
1362
1363pub fn distance(p1: &VennCircle, p2: &VennCircle) -> f64 {
1364    distance_points(
1365        VennPoint { x: p1.x, y: p1.y },
1366        VennPoint { x: p2.x, y: p2.y },
1367    )
1368}
1369
1370fn distance_point_circle(point: VennPoint, circle: &VennCircle) -> f64 {
1371    distance_points(
1372        point,
1373        VennPoint {
1374            x: circle.x,
1375            y: circle.y,
1376        },
1377    )
1378}
1379
1380fn distance_points(p1: VennPoint, p2: VennPoint) -> f64 {
1381    ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt()
1382}
1383
1384pub fn circle_overlap(r1: f64, r2: f64, d: f64) -> f64 {
1385    if d >= r1 + r2 {
1386        return 0.0;
1387    }
1388    if d <= (r1 - r2).abs() {
1389        return PI * r1.min(r2) * r1.min(r2);
1390    }
1391    let w1 = r1 - (d * d - r2 * r2 + r1 * r1) / (2.0 * d);
1392    let w2 = r2 - (d * d - r1 * r1 + r2 * r2) / (2.0 * d);
1393    circle_area(r1, w1) + circle_area(r2, w2)
1394}
1395
1396pub fn circle_circle_intersection(p1: &VennCircle, p2: &VennCircle) -> Vec<VennPoint> {
1397    let d = distance(p1, p2);
1398    let r1 = p1.radius;
1399    let r2 = p2.radius;
1400    if d >= r1 + r2 || d <= (r1 - r2).abs() {
1401        return Vec::new();
1402    }
1403
1404    let a = (r1 * r1 - r2 * r2 + d * d) / (2.0 * d);
1405    let h = (r1 * r1 - a * a).sqrt();
1406    let x0 = p1.x + (a * (p2.x - p1.x)) / d;
1407    let y0 = p1.y + (a * (p2.y - p1.y)) / d;
1408    let rx = -(p2.y - p1.y) * (h / d);
1409    let ry = -(p2.x - p1.x) * (h / d);
1410
1411    vec![
1412        VennPoint {
1413            x: x0 + rx,
1414            y: y0 - ry,
1415        },
1416        VennPoint {
1417            x: x0 - rx,
1418            y: y0 + ry,
1419        },
1420    ]
1421}
1422
1423fn get_center(points: impl IntoIterator<Item = VennPoint>) -> VennPoint {
1424    let mut count = 0usize;
1425    let mut center = VennPoint { x: 0.0, y: 0.0 };
1426    for point in points {
1427        center.x += point.x;
1428        center.y += point.y;
1429        count += 1;
1430    }
1431    if count > 0 {
1432        center.x /= count as f64;
1433        center.y /= count as f64;
1434    }
1435    center
1436}
1437
1438fn compute_text_centres(
1439    circles: &Solution,
1440    areas: &[VennArea],
1441    symmetrical_text_centre: bool,
1442) -> HashMap<String, VennTextPoint> {
1443    let overlapped = get_overlapping_circles(circles);
1444    let mut out = HashMap::new();
1445    for area in areas {
1446        let areaids: HashSet<&str> = area.sets.iter().map(String::as_str).collect();
1447        let mut exclude = HashSet::new();
1448        for set in &area.sets {
1449            if let Some(overlaps) = overlapped.get(set) {
1450                for overlap in overlaps {
1451                    exclude.insert(overlap.as_str());
1452                }
1453            }
1454        }
1455
1456        let mut interior = Vec::new();
1457        let mut exterior = Vec::new();
1458        for (setid, circle) in circles {
1459            if areaids.contains(setid.as_str()) {
1460                interior.push(circle.clone());
1461            } else if !exclude.contains(setid.as_str()) {
1462                exterior.push(circle.clone());
1463            }
1464        }
1465        out.insert(
1466            sets_key(&area.sets),
1467            compute_text_centre(&interior, &exterior, symmetrical_text_centre),
1468        );
1469    }
1470    out
1471}
1472
1473pub fn compute_text_centre(
1474    interior: &[VennCircle],
1475    exterior: &[VennCircle],
1476    symmetrical_text_centre: bool,
1477) -> VennTextPoint {
1478    if interior.is_empty() {
1479        return VennTextPoint {
1480            x: 0.0,
1481            y: 0.0,
1482            disjoint: true,
1483        };
1484    }
1485
1486    let mut points = Vec::new();
1487    for c in interior {
1488        points.push(VennPoint { x: c.x, y: c.y });
1489        points.push(VennPoint {
1490            x: c.x + c.radius / 2.0,
1491            y: c.y,
1492        });
1493        points.push(VennPoint {
1494            x: c.x - c.radius / 2.0,
1495            y: c.y,
1496        });
1497        points.push(VennPoint {
1498            x: c.x,
1499            y: c.y + c.radius / 2.0,
1500        });
1501        points.push(VennPoint {
1502            x: c.x,
1503            y: c.y - c.radius / 2.0,
1504        });
1505    }
1506
1507    let mut initial = points[0];
1508    let mut margin = circle_margin(points[0], interior, exterior);
1509    for point in points.into_iter().skip(1) {
1510        let m = circle_margin(point, interior, exterior);
1511        if m >= margin {
1512            initial = point;
1513            margin = m;
1514        }
1515    }
1516
1517    let solution = nelder_mead(
1518        |p| -circle_margin(VennPoint { x: p[0], y: p[1] }, interior, exterior),
1519        &[initial.x, initial.y],
1520        &OptimParams {
1521            max_iterations: Some(500),
1522            min_error_delta: Some(1e-10),
1523        },
1524    )
1525    .x;
1526
1527    let ret = VennTextPoint {
1528        x: if symmetrical_text_centre {
1529            0.0
1530        } else {
1531            solution[0]
1532        },
1533        y: solution[1],
1534        disjoint: false,
1535    };
1536
1537    let valid_interior = interior.iter().all(|circle| {
1538        distance_point_circle(VennPoint { x: ret.x, y: ret.y }, circle) <= circle.radius
1539    });
1540    let valid_exterior = exterior.iter().all(|circle| {
1541        distance_point_circle(VennPoint { x: ret.x, y: ret.y }, circle) >= circle.radius
1542    });
1543    if valid_interior && valid_exterior {
1544        return ret;
1545    }
1546
1547    if interior.len() == 1 {
1548        return VennTextPoint {
1549            x: interior[0].x,
1550            y: interior[0].y,
1551            disjoint: false,
1552        };
1553    }
1554
1555    let area_stats = intersection_area_stats(interior);
1556    if area_stats.arcs.is_empty() {
1557        return VennTextPoint {
1558            x: 0.0,
1559            y: -1000.0,
1560            disjoint: true,
1561        };
1562    }
1563    if area_stats.arcs.len() == 1 {
1564        return VennTextPoint {
1565            x: area_stats.arcs[0].circle.x,
1566            y: area_stats.arcs[0].circle.y,
1567            disjoint: false,
1568        };
1569    }
1570    if !exterior.is_empty() {
1571        return compute_text_centre(interior, &[], false);
1572    }
1573
1574    let center = get_center(area_stats.arcs.iter().map(|arc| arc.p1));
1575    VennTextPoint {
1576        x: center.x,
1577        y: center.y,
1578        disjoint: false,
1579    }
1580}
1581
1582fn circle_margin(current: VennPoint, interior: &[VennCircle], exterior: &[VennCircle]) -> f64 {
1583    let mut margin = interior[0].radius - distance_point_circle(current, &interior[0]);
1584    for circle in interior.iter().skip(1) {
1585        margin = margin.min(circle.radius - distance_point_circle(current, circle));
1586    }
1587    for circle in exterior {
1588        margin = margin.min(distance_point_circle(current, circle) - circle.radius);
1589    }
1590    margin
1591}
1592
1593fn get_overlapping_circles(circles: &Solution) -> HashMap<String, Vec<String>> {
1594    let mut out: HashMap<String, Vec<String>> = circles
1595        .keys()
1596        .map(|setid| (setid.clone(), Vec::new()))
1597        .collect();
1598    let ids: Vec<String> = circles.keys().cloned().collect();
1599    for i in 0..ids.len() {
1600        let ci = &ids[i];
1601        let a = &circles[ci];
1602        for cj in ids.iter().skip(i + 1) {
1603            let b = &circles[cj];
1604            let d = distance(a, b);
1605            if d + b.radius <= a.radius + SMALL {
1606                out.entry(cj.clone()).or_default().push(ci.clone());
1607            } else if d + a.radius <= b.radius + SMALL {
1608                out.entry(ci.clone()).or_default().push(cj.clone());
1609            }
1610        }
1611    }
1612    out
1613}
1614
1615fn intersection_area_arcs(circles: &[VennCircle]) -> Vec<VennArc> {
1616    if circles.is_empty() {
1617        return Vec::new();
1618    }
1619    intersection_area_stats(circles).arcs
1620}
1621
1622pub fn intersection_area_path(circles: &[VennCircle], round: Option<usize>) -> String {
1623    arcs_to_path(&intersection_area_arcs(circles), round)
1624}
1625
1626fn arcs_to_path(arcs: &[VennArc], round: Option<usize>) -> String {
1627    if arcs.is_empty() {
1628        return "M 0 0".to_string();
1629    }
1630    if arcs.len() == 1 {
1631        let circle = &arcs[0].circle;
1632        return circle_path(
1633            round_path_value(circle.x, round),
1634            round_path_value(circle.y, round),
1635            round_path_value(circle.radius, round),
1636        );
1637    }
1638
1639    let mut out = String::new();
1640    out.push_str("\nM ");
1641    push_js_number(&mut out, round_path_value(arcs[0].p2.x, round));
1642    out.push(' ');
1643    push_js_number(&mut out, round_path_value(arcs[0].p2.y, round));
1644    for arc in arcs {
1645        out.push_str(" \nA ");
1646        let radius = round_path_value(arc.circle.radius, round);
1647        push_js_number(&mut out, radius);
1648        out.push(' ');
1649        push_js_number(&mut out, radius);
1650        out.push_str(" 0 ");
1651        out.push(if arc.large { '1' } else { '0' });
1652        out.push(' ');
1653        out.push(if arc.sweep { '1' } else { '0' });
1654        out.push(' ');
1655        push_js_number(&mut out, round_path_value(arc.p1.x, round));
1656        out.push(' ');
1657        push_js_number(&mut out, round_path_value(arc.p1.y, round));
1658    }
1659    out
1660}
1661
1662fn circle_path(x: f64, y: f64, r: f64) -> String {
1663    let mut out = String::new();
1664    out.push_str("\nM ");
1665    push_js_number(&mut out, x);
1666    out.push(' ');
1667    push_js_number(&mut out, y);
1668    out.push_str(" \nm ");
1669    push_js_number(&mut out, -r);
1670    out.push_str(" 0 \na ");
1671    push_js_number(&mut out, r);
1672    out.push(' ');
1673    push_js_number(&mut out, r);
1674    out.push_str(" 0 1 0 ");
1675    push_js_number(&mut out, r * 2.0);
1676    out.push_str(" 0 \na ");
1677    push_js_number(&mut out, r);
1678    out.push(' ');
1679    push_js_number(&mut out, r);
1680    out.push_str(" 0 1 0 ");
1681    push_js_number(&mut out, -r * 2.0);
1682    out.push_str(" 0");
1683    out
1684}
1685
1686fn round_path_value(v: f64, round: Option<usize>) -> f64 {
1687    let Some(round) = round else {
1688        return v;
1689    };
1690    let factor = 10_f64.powi(round as i32);
1691    let out = ((v * factor) + 0.5).floor() / factor;
1692    if out == -0.0 { 0.0 } else { out }
1693}
1694
1695fn push_js_number(out: &mut String, mut v: f64) {
1696    if !v.is_finite() {
1697        out.push('0');
1698        return;
1699    }
1700    if v == -0.0 {
1701        v = 0.0;
1702    }
1703    let mut buf = Buffer::new();
1704    out.push_str(buf.format_finite(v));
1705}
1706
1707fn sets_key(sets: &[String]) -> String {
1708    sets.join(",")
1709}
1710
1711fn bisect<F>(mut f: F, mut a: f64, b: f64, max_iterations: usize, tolerance: f64) -> f64
1712where
1713    F: FnMut(f64) -> f64,
1714{
1715    let f_a = f(a);
1716    let f_b = f(b);
1717    let mut delta = b - a;
1718    if f_a * f_b > 0.0 {
1719        return a;
1720    }
1721    if f_a == 0.0 {
1722        return a;
1723    }
1724    if f_b == 0.0 {
1725        return b;
1726    }
1727    for _ in 0..max_iterations {
1728        delta /= 2.0;
1729        let mid = a + delta;
1730        let f_mid = f(mid);
1731        if f_mid * f_a >= 0.0 {
1732            a = mid;
1733        }
1734        if delta.abs() < tolerance || f_mid == 0.0 {
1735            return mid;
1736        }
1737    }
1738    a + delta
1739}
1740
1741fn nelder_mead<F>(mut f: F, x0: &[f64], parameters: &OptimParams) -> OptimResult
1742where
1743    F: FnMut(&[f64]) -> f64,
1744{
1745    let n = x0.len();
1746    let max_iterations = parameters.max_iterations.unwrap_or(n * 200);
1747    let non_zero_delta = 1.05;
1748    let zero_delta = 0.001;
1749    let min_error_delta = parameters.min_error_delta.unwrap_or(1e-6);
1750    let min_tolerance = parameters.min_error_delta.unwrap_or(1e-5);
1751    let rho = 1.0;
1752    let chi = 2.0;
1753    let psi = -0.5;
1754    let sigma = 0.5;
1755
1756    let mut simplex = Vec::with_capacity(n + 1);
1757    simplex.push(SimplexPoint {
1758        x: x0.to_vec(),
1759        fx: f(x0),
1760    });
1761    for i in 0..n {
1762        let mut point = x0.to_vec();
1763        point[i] = if point[i] != 0.0 {
1764            point[i] * non_zero_delta
1765        } else {
1766            zero_delta
1767        };
1768        let fx = f(&point);
1769        simplex.push(SimplexPoint { x: point, fx });
1770    }
1771
1772    let mut centroid = x0.to_vec();
1773    let mut reflected = x0.to_vec();
1774    let mut contracted = x0.to_vec();
1775    let mut expanded = x0.to_vec();
1776
1777    for _ in 0..max_iterations {
1778        simplex.sort_by(|a, b| a.fx.total_cmp(&b.fx));
1779        let mut max_diff: f64 = 0.0;
1780        if simplex.len() > 1 {
1781            for i in 0..n {
1782                max_diff = max_diff.max((simplex[0].x[i] - simplex[1].x[i]).abs());
1783            }
1784        }
1785        if (simplex[0].fx - simplex[n].fx).abs() < min_error_delta && max_diff < min_tolerance {
1786            break;
1787        }
1788
1789        for i in 0..n {
1790            centroid[i] = 0.0;
1791            for point in simplex.iter().take(n) {
1792                centroid[i] += point.x[i];
1793            }
1794            centroid[i] /= n as f64;
1795        }
1796
1797        let worst = simplex[n].x.clone();
1798        weighted_sum(&mut reflected, 1.0 + rho, &centroid, -rho, &worst);
1799        let reflected_fx = f(&reflected);
1800        if reflected_fx < simplex[0].fx {
1801            weighted_sum(&mut expanded, 1.0 + chi, &centroid, -chi, &worst);
1802            let expanded_fx = f(&expanded);
1803            if expanded_fx < reflected_fx {
1804                update_simplex(&mut simplex[n], &expanded, expanded_fx);
1805            } else {
1806                update_simplex(&mut simplex[n], &reflected, reflected_fx);
1807            }
1808        } else if reflected_fx >= simplex[n - 1].fx {
1809            let mut should_reduce = false;
1810            if reflected_fx > simplex[n].fx {
1811                weighted_sum(&mut contracted, 1.0 + psi, &centroid, -psi, &worst);
1812                let contracted_fx = f(&contracted);
1813                if contracted_fx < simplex[n].fx {
1814                    update_simplex(&mut simplex[n], &contracted, contracted_fx);
1815                } else {
1816                    should_reduce = true;
1817                }
1818            } else {
1819                weighted_sum(
1820                    &mut contracted,
1821                    1.0 - psi * rho,
1822                    &centroid,
1823                    psi * rho,
1824                    &worst,
1825                );
1826                let contracted_fx = f(&contracted);
1827                if contracted_fx < reflected_fx {
1828                    update_simplex(&mut simplex[n], &contracted, contracted_fx);
1829                } else {
1830                    should_reduce = true;
1831                }
1832            }
1833
1834            if should_reduce {
1835                if sigma >= 1.0 {
1836                    break;
1837                }
1838                let best = simplex[0].x.clone();
1839                for point in simplex.iter_mut().skip(1) {
1840                    let current = point.x.clone();
1841                    weighted_sum(&mut point.x, 1.0 - sigma, &best, sigma, &current);
1842                    point.fx = f(&point.x);
1843                }
1844            }
1845        } else {
1846            update_simplex(&mut simplex[n], &reflected, reflected_fx);
1847        }
1848    }
1849
1850    simplex.sort_by(|a, b| a.fx.total_cmp(&b.fx));
1851    OptimResult {
1852        x: simplex[0].x.clone(),
1853    }
1854}
1855
1856#[derive(Debug, Clone)]
1857struct SimplexPoint {
1858    x: Vec<f64>,
1859    fx: f64,
1860}
1861
1862fn update_simplex(point: &mut SimplexPoint, value: &[f64], fx: f64) {
1863    point.x.copy_from_slice(value);
1864    point.fx = fx;
1865}
1866
1867fn conjugate_gradient<F>(mut f: F, initial: &[f64], max_iterations: Option<usize>) -> CgState
1868where
1869    F: FnMut(&[f64], &mut [f64]) -> f64,
1870{
1871    let mut current = CgState {
1872        x: initial.to_vec(),
1873        fx: 0.0,
1874        fxprime: initial.to_vec(),
1875    };
1876    let mut next = current.clone();
1877    let mut yk = initial.to_vec();
1878    let mut pk = current.fxprime.clone();
1879    let mut a = 1.0;
1880    let max_iterations = max_iterations.unwrap_or(initial.len() * 20);
1881
1882    current.fx = f(&current.x, &mut current.fxprime);
1883    scale(&mut pk, &current.fxprime, -1.0);
1884
1885    for _ in 0..max_iterations {
1886        a = wolfe_line_search(&mut f, &pk, &current, &mut next, a);
1887        if a == 0.0 {
1888            scale(&mut pk, &current.fxprime, -1.0);
1889        } else {
1890            weighted_sum(&mut yk, 1.0, &next.fxprime, -1.0, &current.fxprime);
1891            let delta_k = dot(&current.fxprime, &current.fxprime);
1892            let beta_k = (dot(&yk, &next.fxprime) / delta_k).max(0.0);
1893            let old_pk = pk.clone();
1894            weighted_sum(&mut pk, beta_k, &old_pk, -1.0, &next.fxprime);
1895            std::mem::swap(&mut current, &mut next);
1896        }
1897
1898        if norm2(&current.fxprime) <= 1e-5 {
1899            break;
1900        }
1901    }
1902
1903    current
1904}
1905
1906fn wolfe_line_search<F>(
1907    f: &mut F,
1908    pk: &[f64],
1909    current: &CgState,
1910    next: &mut CgState,
1911    mut a: f64,
1912) -> f64
1913where
1914    F: FnMut(&[f64], &mut [f64]) -> f64,
1915{
1916    let phi0 = current.fx;
1917    let phi_prime0 = dot(&current.fxprime, pk);
1918    let mut phi_old = phi0;
1919    let mut a0 = 0.0;
1920    let c1 = 1e-6;
1921    let c2 = 0.1;
1922    if a == 0.0 {
1923        a = 1.0;
1924    }
1925
1926    for iteration in 0..10 {
1927        weighted_sum(&mut next.x, 1.0, &current.x, a, pk);
1928        next.fx = f(&next.x, &mut next.fxprime);
1929        let phi = next.fx;
1930        let phi_prime = dot(&next.fxprime, pk);
1931        if phi > phi0 + c1 * a * phi_prime0 || (iteration > 0 && phi >= phi_old) {
1932            return zoom(
1933                f, pk, current, next, a0, a, phi_old, phi0, phi_prime0, c1, c2,
1934            );
1935        }
1936        if phi_prime.abs() <= -c2 * phi_prime0 {
1937            return a;
1938        }
1939        if phi_prime >= 0.0 {
1940            return zoom(f, pk, current, next, a, a0, phi, phi0, phi_prime0, c1, c2);
1941        }
1942        phi_old = phi;
1943        a0 = a;
1944        a *= 2.0;
1945    }
1946    a
1947}
1948
1949#[allow(clippy::too_many_arguments)]
1950fn zoom<F>(
1951    f: &mut F,
1952    pk: &[f64],
1953    current: &CgState,
1954    next: &mut CgState,
1955    mut a_lo: f64,
1956    mut a_high: f64,
1957    mut phi_lo: f64,
1958    phi0: f64,
1959    phi_prime0: f64,
1960    c1: f64,
1961    c2: f64,
1962) -> f64
1963where
1964    F: FnMut(&[f64], &mut [f64]) -> f64,
1965{
1966    for _ in 0..16 {
1967        let a = (a_lo + a_high) / 2.0;
1968        weighted_sum(&mut next.x, 1.0, &current.x, a, pk);
1969        next.fx = f(&next.x, &mut next.fxprime);
1970        let phi = next.fx;
1971        let phi_prime = dot(&next.fxprime, pk);
1972        if phi > phi0 + c1 * a * phi_prime0 || phi >= phi_lo {
1973            a_high = a;
1974        } else {
1975            if phi_prime.abs() <= -c2 * phi_prime0 {
1976                return a;
1977            }
1978            if phi_prime * (a_high - a_lo) >= 0.0 {
1979                a_high = a_lo;
1980            }
1981            a_lo = a;
1982            phi_lo = phi;
1983        }
1984    }
1985    0.0
1986}
1987
1988fn dot(a: &[f64], b: &[f64]) -> f64 {
1989    a.iter().zip(b).map(|(a, b)| a * b).sum()
1990}
1991
1992fn norm2(a: &[f64]) -> f64 {
1993    dot(a, a).sqrt()
1994}
1995
1996fn scale(ret: &mut [f64], value: &[f64], c: f64) {
1997    for (out, value) in ret.iter_mut().zip(value) {
1998        *out = value * c;
1999    }
2000}
2001
2002fn weighted_sum(ret: &mut [f64], w1: f64, v1: &[f64], w2: f64, v2: &[f64]) {
2003    for ((out, v1), v2) in ret.iter_mut().zip(v1).zip(v2) {
2004        *out = w1 * v1 + w2 * v2;
2005    }
2006}
2007
2008#[cfg(test)]
2009mod tests {
2010    use super::*;
2011
2012    fn c(set: &str, x: f64, y: f64, radius: f64) -> VennCircle {
2013        VennCircle {
2014            set: set.to_string(),
2015            x,
2016            y,
2017            radius,
2018        }
2019    }
2020
2021    fn area(sets: &[&str], size: f64) -> VennArea {
2022        VennArea::new(sets.iter().copied(), size)
2023    }
2024
2025    fn assert_close(actual: f64, expected: f64, tolerance: f64) {
2026        assert!(
2027            (actual - expected).abs() <= tolerance,
2028            "expected {actual} to be within {tolerance} of {expected}"
2029        );
2030    }
2031
2032    #[test]
2033    fn circle_area_matches_upstream_cases() {
2034        assert_close(circle_area(10.0, 0.0), 0.0, 1e-9);
2035        assert_close(circle_area(10.0, 10.0), (PI * 10.0 * 10.0) / 2.0, 1e-9);
2036        assert_close(circle_area(10.0, 20.0), PI * 10.0 * 10.0, 1e-9);
2037    }
2038
2039    #[test]
2040    fn circle_overlap_matches_upstream_cases() {
2041        assert_close(circle_overlap(10.0, 10.0, 200.0), 0.0, 1e-9);
2042        assert_close(circle_overlap(10.0, 10.0, 0.0), PI * 10.0 * 10.0, 1e-9);
2043        assert_close(circle_overlap(10.0, 5.0, 5.0), PI * 5.0 * 5.0, 1e-9);
2044    }
2045
2046    #[test]
2047    fn circle_circle_intersection_matches_upstream_cases() {
2048        assert!(
2049            circle_circle_intersection(&c("a", 0.0, 3.0, 10.0), &c("b", 3.0, 0.0, 20.0)).is_empty()
2050        );
2051        assert!(
2052            circle_circle_intersection(&c("a", 0.0, 0.0, 10.0), &c("b", 21.0, 0.0, 10.0))
2053                .is_empty()
2054        );
2055
2056        let points = circle_circle_intersection(&c("a", 0.0, 0.0, 10.0), &c("b", 10.0, 0.0, 10.0));
2057        assert_eq!(points.len(), 2);
2058        assert_close(points[0].x, 5.0, 1e-9);
2059        assert_close(points[1].x, 5.0, 1e-9);
2060        assert_close(points[0].y, -points[1].y, 1e-9);
2061
2062        let points = circle_circle_intersection(&c("a", 15.0, 5.0, 10.0), &c("b", 20.0, 0.0, 10.0));
2063        assert_eq!(points.len(), 2);
2064    }
2065
2066    #[test]
2067    fn intersection_area_matches_upstream_regressions() {
2068        let circles = vec![
2069            c("0", 0.909, 0.905, 0.548),
2070            c("1", 0.765, 0.382, 0.703),
2071            c("2", 0.63, 0.019, 0.449),
2072            c("3", 0.21, 0.755, 0.656),
2073            c("4", 0.276, 0.723, 1.145),
2074            c("5", 0.141, 0.585, 0.419),
2075        ];
2076        assert_eq!(intersection_area(&circles), 0.0);
2077
2078        let circles = vec![
2079            c("0", 0.426, 0.882, 0.944),
2080            c("1", 0.24, 0.685, 0.992),
2081            c("2", 0.01, 0.909, 1.161),
2082            c("3", 0.54, 0.475, 0.41),
2083        ];
2084        assert_close(intersection_area(&circles), 0.41 * 0.41 * PI, 1e-9);
2085
2086        let circles = vec![
2087            c("0", 0.501, 0.32, 0.629),
2088            c("1", 0.945, 0.022, 1.015),
2089            c("2", 0.021, 0.863, 0.261),
2090            c("3", 0.528, 0.09, 0.676),
2091        ];
2092        assert_close(intersection_area(&circles), 0.0008914, 0.0001);
2093
2094        let circles = vec![
2095            c("0", 9.154829758385864, 0.0, 8.481629223064205),
2096            c(
2097                "1",
2098                5.806079662851866,
2099                7.4438023223126795,
2100                15.274853405932202,
2101            ),
2102            c(
2103                "2",
2104                9.484491297623553,
2105                4.064806303558571,
2106                10.280023453913834,
2107            ),
2108            c(
2109                "3",
2110                10.56492833796709,
2111                3.0723147554880175,
2112                8.812923024107548,
2113            ),
2114        ];
2115        assert_close(intersection_area(&circles), 10.96362, 1e-5);
2116
2117        let circles = vec![
2118            c(
2119                "0",
2120                -0.0014183481763938425,
2121                0.0006071174738860746,
2122                510.3115834996166,
2123            ),
2124            c(
2125                "1",
2126                875.0163281608848,
2127                0.0007003612396158774,
2128                465.1793581792228,
2129            ),
2130            c(
2131                "2",
2132                462.7394999567192,
2133                387.9359963330729,
2134                172.62633992134658,
2135            ),
2136        ];
2137        assert!(!intersection_area(&circles).is_nan());
2138    }
2139
2140    #[test]
2141    fn greedy_layout_matches_upstream_zero_loss_cases() {
2142        let cases = vec![
2143            vec![
2144                area(&["0"], 0.7746543297103429),
2145                area(&["1"], 0.1311252856844238),
2146                area(&["2"], 0.2659942131443344),
2147                area(&["3"], 0.44600866168641723),
2148                area(&["0", "1"], 0.02051532092950205),
2149                area(&["0", "2"], 0.0),
2150                area(&["0", "3"], 0.0),
2151                area(&["1", "2"], 0.0),
2152                area(&["1", "3"], 0.07597023820511245),
2153                area(&["2", "3"], 0.0),
2154            ],
2155            vec![
2156                area(&["0"], 0.5299368855059736),
2157                area(&["1"], 0.03364187025606481),
2158                area(&["2"], 0.3121450394871512),
2159                area(&["3"], 0.0514397361783036),
2160                area(&["0", "1"], 0.013912447645582351),
2161                area(&["0", "2"], 0.005903647141469598),
2162                area(&["0", "3"], 0.0514397361783036),
2163                area(&["1", "2"], 0.012138157839477597),
2164                area(&["1", "3"], 0.008010688232481479),
2165                area(&["2", "3"], 0.0),
2166            ],
2167            vec![
2168                area(&["0"], 1.7288584050841396),
2169                area(&["1"], 0.040875831658950056),
2170                area(&["2"], 2.587146019782323),
2171                area(&["0", "1"], 0.040875831658950056),
2172                area(&["0", "2"], 0.5114617575187569),
2173                area(&["1", "2"], 0.040875831658950056),
2174            ],
2175        ];
2176
2177        for areas in cases {
2178            let circles = greedy_layout(&areas).expect("greedy layout");
2179            assert_close(loss_function(&circles, &areas), 0.0, 1e-8);
2180        }
2181    }
2182
2183    #[test]
2184    fn distance_from_intersect_area_round_trips_overlap() {
2185        for (r1, r2, overlap) in [
2186            (1.9544100476116797, 2.256758334191025, 11.0),
2187            (111.06512962798197, 113.32348546565727, 1218.0),
2188            (44.456564007075, 149.4335753619362, 2799.0),
2189            (592.89, 134.75, 56995.0),
2190            (139.50778247443944, 32.892784970851956, 3399.0),
2191            (4.886025119029199, 5.077706251929807, 75.0),
2192        ] {
2193            let d = distance_from_intersect_area(r1, r2, overlap);
2194            assert_close(circle_overlap(r1, r2, d), overlap, 1e-7);
2195        }
2196    }
2197
2198    #[test]
2199    fn normalize_solution_places_disjoint_circles_close_together() {
2200        let mut solution = Solution::new();
2201        solution.insert("0".to_string(), c("0", 0.0, 0.0, 0.5));
2202        solution.insert("1".to_string(), c("1", 1e10, 0.0, 1.5));
2203
2204        let normalized = normalize_solution(&solution, PI / 2.0);
2205        assert!(distance(&normalized["0"], &normalized["1"]) < 2.1);
2206    }
2207
2208    #[test]
2209    fn disjoint_clusters_match_upstream_case() {
2210        let input = vec![
2211            c(
2212                "0",
2213                0.8047033110633492,
2214                0.9396705999970436,
2215                0.47156485118903224,
2216            ),
2217            c(
2218                "1",
2219                0.7961132447235286,
2220                0.014027722179889679,
2221                0.14554832570720466,
2222            ),
2223            c(
2224                "2",
2225                0.28841276094317436,
2226                0.98081015329808,
2227                0.9851036085514352,
2228            ),
2229            c(
2230                "3",
2231                0.7689983483869582,
2232                0.2899463507346809,
2233                0.7210563338827342,
2234            ),
2235        ];
2236
2237        assert_eq!(disjoint_cluster(input).len(), 1);
2238    }
2239
2240    #[test]
2241    fn compute_text_centre_matches_upstream_cases() {
2242        let center = compute_text_centre(&[c("0", 0.0, 0.0, 1.0)], &[], false);
2243        assert_close(center.x, 0.0, 1e-9);
2244        assert_close(center.y, 0.0, 1e-9);
2245
2246        let center = compute_text_centre(&[c("0", 0.0, 0.0, 1.0)], &[c("1", 0.0, 1.0, 1.0)], false);
2247        assert_close(center.x, 0.0, 1e-4);
2248        assert_close(center.y, -0.5, 1e-6);
2249    }
2250
2251    #[test]
2252    fn compute_venn_layout_returns_typed_helper_surface() {
2253        let areas = vec![
2254            area(&["A"], 10.0),
2255            area(&["B"], 10.0),
2256            area(&["A", "B"], 3.0),
2257        ];
2258        let layout = compute_venn_layout(&areas, &VennLayoutOptions::default()).expect("layout");
2259        assert_eq!(layout.len(), 3);
2260        assert_eq!(layout[0].data.sets, vec!["A"]);
2261        assert!(!layout[0].path.is_empty());
2262        assert_eq!(layout[2].circles.len(), 2);
2263        assert!(!layout[2].text.disjoint);
2264    }
2265}