use std::f64::consts::PI;
use crate::scene::{Point, Segment};
use crate::{Error, PT};
use super::super::nodes::{NodeSpec, OnPath};
use super::draw::{axis_char, tick_offset, visible, Frame, Setup};
use super::keys;
use super::{toks, Engine};
pub fn sin_cos(deg: f64) -> (f64, f64) {
let r = deg.rem_euclid(360.0);
if r == 0.0 {
(0.0, 1.0)
} else if r == 90.0 {
(1.0, 0.0)
} else if r == 180.0 {
(0.0, -1.0)
} else if r == 270.0 {
(-1.0, 0.0)
} else {
deg.to_radians().sin_cos()
}
}
fn full(xmin: f64, xmax: f64) -> bool {
let d = (xmax - xmin).rem_euclid(360.0);
d.abs() < 0.002 || (360.0 - d).abs() < 0.002
}
fn outline_points(xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Vec<(f64, f64)> {
let mut v = Vec::new();
let steps = (((xmax - xmin).abs() * 2.0).ceil() as usize).clamp(4, 2000);
for k in 0..=steps {
let a = xmin + (xmax - xmin) * k as f64 / steps as f64;
let (s, c) = sin_cos(a);
v.push((ymax * c, ymax * s));
v.push((ymin * c, ymin * s));
}
v
}
pub fn contains(f: &Frame, p: Point) -> bool {
let d = p.minus(f.origin);
let (u, v) = (d.x / f.e[0].x, d.y / f.e[1].y);
if !(u.is_finite() && v.is_finite()) {
return true;
}
let tol = 0.05 * PT / f.e[1].norm().max(1e-12);
let r = u.hypot(v);
if r < f.min[1].abs().min(f.max[1].abs()) - tol || r > f.max[1].abs() + tol {
return false;
}
if r <= tol || full(f.min[0], f.max[0]) {
return true;
}
let a = v.atan2(u).to_degrees().rem_euclid(360.0);
let (lo, hi) = (f.min[0].rem_euclid(360.0), f.max[0].rem_euclid(360.0));
let eps = 1e-3;
if hi > lo {
a >= lo - eps && a <= hi + eps
} else {
a <= hi + eps || a >= lo - eps
}
}
pub fn outline(f: &Frame) -> Vec<Point> {
outline_points(f.min[0], f.max[0], f.min[1], f.max[1]).into_iter().map(|(x, y)| f.origin.plus(f.e[0].scale(x)).plus(f.e[1].scale(y))).collect()
}
fn arc(f: &Frame, r: f64, a0: f64, a1: f64, segs: &mut Vec<Segment>, start: bool) {
let map = |u: f64, v: f64| f.origin.plus(f.e[0].scale(u)).plus(f.e[1].scale(v));
let n = ((a1 - a0).abs() / 90.0).ceil().max(1.0) as usize;
let step = (a1 - a0) / n as f64;
let (s0, c0) = sin_cos(a0);
if start {
segs.push(Segment::MoveTo(map(r * c0, r * s0)));
}
for i in 0..n {
let (t0, t1) = (a0 + step * i as f64, a0 + step * (i + 1) as f64);
let k = 4.0 / 3.0 * ((t1 - t0).to_radians() / 4.0).tan();
let ((s0, c0), (s1, c1)) = (sin_cos(t0), sin_cos(t1));
let p1 = map(r * (c0 - k * s0), r * (s0 + k * c0));
let p2 = map(r * (c1 + k * s1), r * (s1 - k * c1));
segs.push(Segment::CurveTo(p1, p2, map(r * c1, r * s1)));
}
}
pub fn clip_path(f: &Frame) -> Vec<Segment> {
let (xmin, xmax, ymin, ymax) = (f.min[0], f.max[0], f.min[1], f.max[1]);
let mut segs = Vec::new();
arc(f, ymax, xmin, xmax, &mut segs, true);
if full(xmin, xmax) {
if ymin != 0.0 {
arc(f, ymin, xmin, xmax, &mut segs, true);
}
} else if ymin == 0.0 {
segs.push(Segment::LineTo(f.origin));
} else {
let (s, c) = sin_cos(xmax);
segs.push(Segment::LineTo(f.origin.plus(f.e[0].scale(ymin * c)).plus(f.e[1].scale(ymin * s))));
arc(f, ymin, xmax, xmin, &mut segs, false);
}
segs.push(Segment::Close);
segs
}
pub fn ticks(e: &mut Engine, a: usize, f: &Frame) -> Option<(Vec<f64>, f64)> {
if a != 0 {
return None;
}
let raw = crate::keys::value(&e.tex, "/pgfplots/xtick").unwrap_or_default();
if !crate::keys::trim(&raw).is_empty() || keys::number(e, "/pgfplots/xtick distance").is_some() {
return None;
}
let (xmin, xmax) = (f.min[0], f.max[0]);
let mut length = f.e[0].norm() * f.max[1] * 2.0 * PI / PT;
let frac = (xmax - xmin) / 360.0;
if frac < 1.0 {
length *= frac;
}
let max_space = keys::number(e, "/pgfplots/max space between ticks").unwrap_or(35.0);
let try_min = keys::number(e, "/pgfplots/try min ticks").unwrap_or(4.0) as usize;
let n = ((length / max_space).floor() as usize + 1).max(try_min);
let sp = |v: f64| (v * 65536.0).round();
let h = ((sp(xmax) + sp(0.001) - sp(xmin)) / (n.max(2) - 1) as f64).trunc() / 65536.0;
let h = match h {
h if h > 67.0 => 90.0,
h if h > 30.0 => 45.0,
h if h > 18.0 => 30.0,
h if h > 10.0 => 15.0,
h if h > 6.0 => 10.0,
_ => 5.0,
};
let settings = super::ticks::TickSettings { length_pt: length, max_space, try_min, distance: Some(h), scale: None };
let (mut list, d) = super::ticks::linear(xmin, xmax, settings);
if full(xmin, xmax) {
list.retain(|t| (t - xmax).abs() > 1e-6 * h);
}
Some((list, d))
}
pub(super) fn draw_axis(e: &mut Engine, s: &Setup) -> Result<(), Error> {
let f = s.frame.clone();
let (xmin, xmax, ymin, ymax) = (f.min[0], f.max[0], f.min[1], f.max[1]);
let xt: Vec<f64> = if s.hide[0] { Vec::new() } else { visible(&f, 0, &s.ticks[0]).into_iter().map(|(_, t)| t).collect() };
let yt: Vec<f64> = if s.hide[1] { Vec::new() } else { visible(&f, 1, &s.ticks[1]).into_iter().map(|(_, t)| t).collect() };
if keys::flag(e, "/pgfplots/xmajorgrids") {
let segs: Vec<Segment> = xt.iter().flat_map(|t| [Segment::MoveTo(f.at([*t, ymin, 0.0])), Segment::LineTo(f.at([*t, ymax, 0.0]))]).collect();
e.draw_with("/pgfplots/every axis grid,/pgfplots/every major grid,/pgfplots/every axis x grid,/pgfplots/every major x grid", segs)?;
}
if keys::flag(e, "/pgfplots/ymajorgrids") {
let mut segs = Vec::new();
for t in &yt {
arc(&f, *t, xmin, xmax, &mut segs, true);
}
e.draw_with("/pgfplots/every axis grid,/pgfplots/every major grid,/pgfplots/every axis y grid,/pgfplots/every major y grid", segs)?;
}
let mut segs = Vec::new();
arc(&f, ymax, xmin, xmax, &mut segs, true);
if ymin > 0.0 {
arc(&f, ymin, xmin, xmax, &mut segs, true);
}
if !s.hide[0] {
e.draw_with("/pgfplots/every outer x axis line", segs)?;
}
if !s.hide[1] {
let mut segs = Vec::new();
for a in [xmin, xmax] {
segs.push(Segment::MoveTo(f.at([a, ymin, 0.0])));
segs.push(Segment::LineTo(f.at([a, ymax, 0.0])));
}
e.draw_with("/pgfplots/every outer y axis line", segs)?;
}
for (a, list) in [(0usize, &xt), (1usize, &yt)] {
if s.hide[a] || !keys::flag(e, &format!("/pgfplots/{}majorticks", axis_char(a))) {
continue;
}
let c = axis_char(a);
let styles = format!("/pgfplots/every tick label,/pgfplots/every {c} tick label");
let off = tick_offset(e, a);
let sloped = match e.tex.meaning_of(&crate::keys::macro_name(&format!("/pgfplots/every {c} tick label/.@cmd"))) {
crate::tex::Meaning::Macro(m) => crate::tex::tokens_to_string(&crate::tex::substitute(&m.body, &[])).contains("sloped"),
_ => false,
};
for (k, t) in list.iter().enumerate() {
let (p, out) = if a == 0 {
let p = f.at([*t, ymax, 0.0]);
(p, super::draw::unit(p.minus(f.origin)))
} else {
let (sn, cs) = sin_cos(xmin);
let n = super::draw::unit(f.e[0].scale(-sn).plus(f.e[1].scale(cs)));
(f.at([xmin, *t, 0.0]), n)
};
let at = p.plus(out.scale(off));
let shown = if a == 0 && *t > 360.0 { t.rem_euclid(360.0) } else { *t };
e.begin_group();
let r = (|| {
e.keys_tikz(&styles)?;
super::draw::tick_text_for(e, a, shown, k)
})();
e.end_group();
let text = r?;
let mut angle = (-out.y).atan2(-out.x).to_degrees();
if a == 0 && sloped {
let (sn, cs) = sin_cos(*t);
let mut d = f.e[0].scale(-sn).plus(f.e[1].scale(cs));
if d.x < 0.0 {
d = d.scale(-1.0);
}
angle -= d.y.atan2(d.x).to_degrees();
f.slope.set(Some(d));
}
let options = toks(&format!("anchor={angle},{styles}"));
let items = e.node(NodeSpec { options, name: None, at: Some(at), text, place: OnPath::Point(at), point: at, coordinate: false, default_pos: None });
f.slope.set(None);
e.canvas.items.extend(items?);
}
}
Ok(())
}