use super::labels;
use super::model::{Bubble, Chart};
use super::project::Plot;
use super::scale::fmt_tick;
use crate::layout::PlacedNode;
use crate::layout::prim;
use crate::resolve::ResolvedValue;
const MAX_R: f64 = 0.14;
pub fn lay_out(plot: &Plot, chart: &Chart, out: &mut Vec<PlacedNode>, reqs: &mut Vec<labels::Req>) {
let vmax = chart
.bubbles
.iter()
.map(|b| b.value)
.fold(0.0_f64, f64::max);
if vmax <= 0.0 {
return;
}
let rmax = plot.w().min(plot.h()) * MAX_R;
let xs = &chart.x.scale;
for b in &chart.bubbles {
let vs = &chart.values[b.axis].scale;
if !xs.contains(b.at.0) || !vs.contains(b.at.1) {
continue; }
let (cx, cy) = plot.project(xs, b.at.0, vs, b.at.1);
let d = ((b.value / vmax).sqrt() * rmax * 2.0).max(2.0); let mut bubble = prim::oval(cx, cy, d, d, b.color.clone());
if let Some((color, width)) = &b.outline {
prim::outline(&mut bubble, color.clone(), *width);
}
prim::set_title(&mut bubble, bubble_title(b));
out.push(bubble);
if let Some(label) = &b.label
&& !label.is_empty()
&& b.tooltip.inline()
{
reqs.push(labels::Req {
anchor: (cx, cy),
radius: d / 2.0,
text: label.clone(),
color: muted(),
forced: b.tooltip.forced(),
inside: Some(labels::Inside {
fit: d,
color: on_fill(),
}),
});
}
}
}
fn muted() -> ResolvedValue {
ResolvedValue::LiveVar {
name: "muted".into(),
raw: false,
}
}
fn bubble_title(b: &Bubble) -> String {
let v = fmt_tick(b.value);
match &b.label {
Some(l) => format!("{l}: {v}"),
None => v,
}
}
fn on_fill() -> ResolvedValue {
ResolvedValue::LiveVar {
name: "accent-text".into(),
raw: false,
}
}