Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
// d3-axis's default gap between the end of a tick mark and its label.
const LABEL_PADDING: f64 = 3.0;
/// A pixel coordinate rounded to 2 places, the precision every axis output uses.
pub fn axis_pixel(value: f64) -> f64 {
round_float(value, 2)
}
pub fn axis_line(x1: f64, y1: f64, x2: f64, y2: f64) -> Line {
Line { x1: axis_pixel(x1), y1: axis_pixel(y1), x2: axis_pixel(x2), y2: axis_pixel(y2) }
}
pub fn check_orient(orient: &str) {
if !matches!(orient, "bottom" | "top" | "left" | "right") {
panic!("orient must be bottom, top, left or right, received \"{}\"", orient);
}
}
/// Axis geometry from tick positions and labels, for any scale (d3-axis layout).
///
/// # Panics
/// Panics on an unknown orient, positions and labels of different lengths, or
/// a range without exactly two values.
pub fn axis_from_ticks(positions: &[f64], labels: &[String], range: &[f64], orient: &str, position: f64, tick_size: f64) -> Axis {
check_orient(orient);
if positions.len() != labels.len() {
panic!(
"positions and labels must be the same length, received {} and {}",
positions.len(),
labels.len()
);
}
if range.len() != 2 {
panic!("range must have exactly 2 values, [from, to]; got {}", range.len());
}
let horizontal = orient == "bottom" || orient == "top";
let sign = if orient == "bottom" || orient == "right" { 1.0 } else { -1.0 };
let mark_end = position + sign * tick_size;
let label_at = position + sign * (tick_size + LABEL_PADDING);
let ticks = positions
.iter()
.zip(labels)
.map(|(&p, label)| {
if horizontal {
AxisTick {
position: axis_pixel(p),
label: label.clone(),
tick: axis_line(p, position, p, mark_end),
label_x: axis_pixel(p),
label_y: axis_pixel(label_at),
anchor: "middle".to_string(),
baseline: if orient == "bottom" { "hanging" } else { "alphabetic" }.to_string(),
}
} else {
AxisTick {
position: axis_pixel(p),
label: label.clone(),
tick: axis_line(position, p, mark_end, p),
label_x: axis_pixel(label_at),
label_y: axis_pixel(p),
anchor: if orient == "left" { "end" } else { "start" }.to_string(),
baseline: "middle".to_string(),
}
}
})
.collect();
let line = if horizontal {
axis_line(range[0], position, range[1], position)
} else {
axis_line(position, range[0], position, range[1])
};
Axis { orient: orient.to_string(), position: axis_pixel(position), line, ticks }
}
pub fn line_to_value(l: &Line) -> Value {
Value::obj(vec![
("x1", Value::Float(l.x1)),
("y1", Value::Float(l.y1)),
("x2", Value::Float(l.x2)),
("y2", Value::Float(l.y2)),
])
}
pub fn line_from_value(v: &Value) -> Line {
Line { x1: v.get("x1").as_f64(), y1: v.get("y1").as_f64(), x2: v.get("x2").as_f64(), y2: v.get("y2").as_f64() }
}
pub fn axis_to_value(a: &Axis) -> Value {
Value::obj(vec![
("orient", Value::str(&a.orient)),
("position", Value::Float(a.position)),
("line", line_to_value(&a.line)),
(
"ticks",
Value::Arr(
a.ticks
.iter()
.map(|t| {
Value::obj(vec![
("position", Value::Float(t.position)),
("label", Value::str(&t.label)),
("tick", line_to_value(&t.tick)),
("labelX", Value::Float(t.label_x)),
("labelY", Value::Float(t.label_y)),
("anchor", Value::str(&t.anchor)),
("baseline", Value::str(&t.baseline)),
])
})
.collect(),
),
),
])
}
pub fn axis_from_value(v: &Value) -> Axis {
Axis {
orient: v.get("orient").as_str().to_string(),
position: v.get("position").as_f64(),
line: line_from_value(v.get("line")),
ticks: v
.get("ticks")
.as_arr()
.iter()
.map(|t| AxisTick {
position: t.get("position").as_f64(),
label: t.get("label").as_str().to_string(),
tick: line_from_value(t.get("tick")),
label_x: t.get("labelX").as_f64(),
label_y: t.get("labelY").as_f64(),
anchor: t.get("anchor").as_str().to_string(),
baseline: t.get("baseline").as_str().to_string(),
})
.collect(),
}
}
/// A JSON list of numbers or strings, for the vector adapters.
pub fn axis_floats(v: &Value) -> Vec<f64> {
v.as_arr().iter().map(|x| x.as_f64()).collect()
}
pub fn axis_strings(v: &Value) -> Vec<String> {
v.as_arr().iter().map(|x| x.as_str().to_string()).collect()
}
pub fn fune_vector(args: &[Value]) -> Value {
axis_to_value(&axis_from_ticks(
&axis_floats(&args[0]),
&axis_strings(&args[1]),
&axis_floats(&args[2]),
args[3].as_str(),
args[4].as_f64(),
args[5].as_f64(),
))
}