use super::labels;
use super::marks::marker_diameter;
use super::metrics::LABEL_SIZE;
use super::model::{AxisRef, Chart, Mark, MarkAt};
use super::project::{Dir, Plot};
use super::tooltip::Tooltip;
use crate::layout::PlacedNode;
use crate::layout::prim;
use crate::resolve::{MarkerKind, ResolvedValue};
const SHADE: f64 = 0.15;
const LINE_W: f64 = 1.5;
fn axis_px(plot: &Plot, chart: &Chart, axis: &AxisRef, v: f64) -> f64 {
match axis {
AxisRef::X => plot.domain_at(&chart.x.scale, chart.x.scale.clamp(v)),
AxisRef::Value(i) => {
let s = &chart.values[*i].scale;
plot.value_at(s, s.clamp(v))
}
}
}
fn runs_horizontal(plot: &Plot, axis: &AxisRef) -> bool {
match axis {
AxisRef::X => plot.dir != Dir::Row,
AxisRef::Value(_) => plot.dir == Dir::Row,
}
}
fn contains(chart: &Chart, axis: &AxisRef, v: f64) -> bool {
match axis {
AxisRef::X => chart.x.scale.contains(v),
AxisRef::Value(i) => chart.values[*i].scale.contains(v),
}
}
pub fn band_shades(plot: &Plot, chart: &Chart, out: &mut Vec<PlacedNode>) {
let mut drawn: Vec<f64> = Vec::new(); for b in &chart.bands {
let p0 = axis_px(plot, chart, &b.axis, b.span.0);
let p1 = axis_px(plot, chart, &b.axis, b.span.1);
if (p1 - p0).abs() < 0.5 {
continue; }
match &b.fill {
Some(fill) => out.push(shade(plot, &b.axis, p0, p1, fill.clone())),
None => dividers(plot, &b.axis, &b.tick, p0, p1, &mut drawn, out),
}
}
}
fn shade(plot: &Plot, axis: &AxisRef, p0: f64, p1: f64, fill: ResolvedValue) -> PlacedNode {
if runs_horizontal(plot, axis) {
prim::rect(
(p0 + p1) / 2.0,
(plot.y0 + plot.y1) / 2.0,
(p1 - p0).abs(),
plot.h(),
fill,
SHADE,
)
} else {
prim::rect(
(plot.x0 + plot.x1) / 2.0,
(p0 + p1) / 2.0,
plot.w(),
(p1 - p0).abs(),
fill,
SHADE,
)
}
}
fn dividers(
plot: &Plot,
axis: &AxisRef,
color: &ResolvedValue,
p0: f64,
p1: f64,
drawn: &mut Vec<f64>,
out: &mut Vec<PlacedNode>,
) {
let horizontal = runs_horizontal(plot, axis);
for p in [p0, p1] {
let edge = if horizontal {
near(p, plot.x0) || near(p, plot.x1)
} else {
near(p, plot.y0) || near(p, plot.y1)
};
if edge || drawn.iter().any(|&q| near(q, p)) {
continue;
}
drawn.push(p);
out.push(prim::line(plot.cross(horizontal, p), color.clone(), 1.0));
}
}
pub fn band_ticks(plot: &Plot, chart: &Chart, out: &mut Vec<PlacedNode>) {
for b in &chart.bands {
let Some(label) = &b.label else { continue };
let mid = axis_px(plot, chart, &b.axis, (b.span.0 + b.span.1) / 2.0);
let color = Some(b.tick.clone());
let node = if runs_horizontal(plot, &b.axis) {
prim::text(
label,
mid,
plot.y1 + 4.0 + LABEL_SIZE * 1.7,
LABEL_SIZE,
color,
false,
chart.font_kind,
)
} else {
prim::text_right(
label,
plot.x0 - 6.0,
mid,
LABEL_SIZE,
color,
chart.font_kind,
)
};
out.push(node);
}
}
pub fn x_band_row(chart: &Chart) -> f64 {
let horizontal = |axis: &AxisRef| match axis {
AxisRef::X => chart.dir != Dir::Row,
AxisRef::Value(_) => chart.dir == Dir::Row,
};
let labelled = chart
.bands
.iter()
.any(|b| horizontal(&b.axis) && b.label.is_some());
if labelled { LABEL_SIZE } else { 0.0 }
}
pub fn marks(plot: &Plot, chart: &Chart, out: &mut Vec<PlacedNode>, reqs: &mut Vec<labels::Req>) {
for m in &chart.marks {
match m.at {
MarkAt::Line(v) => ref_line(plot, chart, m, v, out),
MarkAt::Point(x, y) => point(plot, chart, m, x, y, out, reqs),
}
}
}
fn ref_line(plot: &Plot, chart: &Chart, m: &Mark, v: f64, out: &mut Vec<PlacedNode>) {
if !contains(chart, &m.axis, v) {
return; }
let p = axis_px(plot, chart, &m.axis, v);
let horizontal = runs_horizontal(plot, &m.axis);
let mut ln = prim::line(plot.cross(horizontal, p), m.color.clone(), LINE_W);
if let Some(ss) = &m.stroke_style {
ln.attrs.insert("stroke-style", ss.clone());
}
out.push(ln);
if let Some(text) = &m.label {
let color = Some(m.color.clone());
let node = if horizontal {
let y = if plot.dir == Dir::Row {
plot.y1 - LABEL_SIZE * 0.9
} else {
plot.y0 + LABEL_SIZE * 0.9
};
prim::text(text, p, y, LABEL_SIZE, color, false, chart.font_kind)
} else {
prim::text_left(
text,
plot.x0 + 3.0,
p - LABEL_SIZE * 0.6,
LABEL_SIZE,
color,
chart.font_kind,
)
};
out.push(node);
}
}
fn point(
plot: &Plot,
chart: &Chart,
m: &Mark,
x: f64,
y: f64,
out: &mut Vec<PlacedNode>,
reqs: &mut Vec<labels::Req>,
) {
let vi = match &m.axis {
AxisRef::Value(i) => *i,
AxisRef::X => 0,
};
if !chart.x.scale.contains(x) || !chart.values[vi].scale.contains(y) {
return;
}
let (xp, yp) = plot.project(&chart.x.scale, x, &chart.values[vi].scale, y);
let radius = if m.marker != MarkerKind::None {
let d = marker_diameter(m.marker, 2.0);
out.push(prim::marker(m.marker, xp, yp, d, d, m.color.clone()));
d / 2.0
} else {
0.0
};
if let Some(text) = &m.label
&& m.tooltip != Tooltip::None
{
reqs.push(labels::Req {
anchor: (xp, yp),
radius,
text: text.clone(),
color: m.color.clone(),
forced: true,
inside: None,
});
}
}
fn near(a: f64, b: f64) -> bool {
(a - b).abs() < 0.5
}