use crate::keys::{self, macro_name};
use crate::scene::Point;
use crate::tex::{self, Cat, Meaning, Token};
use crate::{Error, PT};
use super::{group, key_text, macro_text, set_key, Engine, Hooks, Object, Signal, Slot};
use crate::engine::{macro_tokens, options::KeyFn, tikra_tokens};
pub const DV: &str = "/tikz/data visualization";
pub const NATIVE: &[(&str, KeyFn)] = &[
("/tikz/data visualization/.unknown", k_unknown),
("/tikz/data visualization/data/.unknown", k_data_unknown),
("/tikz/data visualization/new object", k_new_object),
("/tikz/data visualization/before survey", k_hook),
("/tikz/data visualization/at start survey", k_hook),
("/tikz/data visualization/at end survey", k_hook),
("/tikz/data visualization/after survey", k_hook),
("/tikz/data visualization/before visualization", k_hook),
("/tikz/data visualization/at start visualization", k_hook),
("/tikz/data visualization/at end visualization", k_hook),
("/tikz/data visualization/after visualization", k_hook),
("/tikz/data visualization/data point", k_data_point),
("/tikz/data visualization/new axis base", k_new_axis_base),
("/tikz/data visualization/new Cartesian axis", k_new_cartesian_axis),
("/tikz/data visualization/new polar axes", k_new_polar_axes),
("/tikz/data visualization/all axes", k_all_axes),
("/tikz/data visualization/new axis system", k_new_axis_system),
("/tikz/data visualization/common", k_setter),
("/tikz/data visualization/major", k_setter),
("/tikz/data visualization/minor", k_setter),
("/tikz/data visualization/subminor", k_setter),
("/tikz/data visualization/node styling", k_node_styling),
("/tikz/data visualization/visualize as line", k_visualize_as),
("/tikz/data visualization/visualize as smooth line", k_visualize_as),
("/tikz/data visualization/visualize as smooth cycle", k_visualize_as),
("/tikz/data visualization/visualize as scatter", k_visualize_as),
("/tikz/data visualization/visualize as rectangles", k_visualize_as),
("/tikz/data visualization/new visualizer", k_new_visualizer),
("/tikz/data visualization/style sheet", k_style_sheet),
("/tikz/data visualization/new legend", k_new_legend),
("/tikz/data visualization/legend", k_legend_style),
("/tikz/data visualization/new legend entry", k_new_legend_entry),
("/tikz/data visualization/@radius axes", k_radius_axes),
("/tikz/data visualization/axis options/attribute", k_axis_value),
("/tikz/data visualization/axis options/function", k_axis_value),
("/tikz/data visualization/axis options/scaling", k_axis_value),
("/tikz/data visualization/axis options/@default scaling", k_axis_value),
("/tikz/data visualization/axis options/padding min", k_axis_value),
("/tikz/data visualization/axis options/padding max", k_axis_value),
("/tikz/data visualization/axis options/tick placement strategy", k_axis_value),
("/tikz/data visualization/axis options/label", k_axis_label),
("/tikz/data visualization/axis options/goto", k_goto),
("/tikz/data visualization/axis options/goto pos", k_goto_pos),
("/tikz/data visualization/axis options/anchor at min", k_anchor_at),
("/tikz/data visualization/axis options/anchor at max", k_anchor_at),
("/tikz/data visualization/axis options/visualize axis", k_visualize_part),
("/tikz/data visualization/axis options/visualize ticks", k_visualize_part),
("/tikz/data visualization/axis options/visualize grid", k_visualize_part),
("/tikz/data visualization/axis options/visualize label", k_visualize_part),
("/tikz/data visualization/axis options/ticks", k_ticks_or_grid),
("/tikz/data visualization/axis options/grid", k_ticks_or_grid),
("/tikz/data visualization/axis options/ticks and grid", k_ticks_or_grid),
("/tikz/data visualization/axis options/include value", k_include_value),
("/tikz/data visualization/axis options/min value", k_range_end),
("/tikz/data visualization/axis options/max value", k_range_end),
("/tikz/data visualization/axis options/unit vector", k_unit_vector),
("/tikz/data visualization/axis options/unit vectors", k_unit_vectors),
("/tikz/data visualization/axis options/unit length", k_unit_length),
("/tikz/data visualization/visualizer options/@handler", k_handler),
("/tikz/data visualization/visualizer options/label in legend", k_label_in_legend),
("/tikz/data visualization/visualizer options/label in data", k_label_in_data),
("/tikz/data visualization/visualizer options/ignore style sheets", k_ignore_style_sheets),
("/tikz/data visualization/visualizer label options/when", k_label_when),
("/tikz/data visualization/visualizer label options/index", k_label_at),
("/tikz/data visualization/visualizer label options/pos", k_label_at),
("/tikz/data visualization/visualizer label options/auto", k_label_at),
("/handlers/.style sheet", k_style_sheet_handler),
("/data point/.unknown", k_data_point_unknown),
];
pub const DEFAULTS: &[(&str, &str)] = &[
("/tikz/data visualization/new polar axes", "{angle axis}{radius axis}"),
("/tikz/data visualization/visualize as line", "line"),
("/tikz/data visualization/visualize as smooth line", "line"),
("/tikz/data visualization/visualize as smooth cycle", "line"),
("/tikz/data visualization/visualize as scatter", "scatter"),
("/tikz/data visualization/visualize as rectangles", "rectangles"),
("/tikz/data visualization/new legend", "main legend"),
("/tikz/data visualization/axis options/ticks", "some"),
("/tikz/data visualization/axis options/grid", "at default ticks"),
];
struct Kind {
key: &'static str,
options: &'static str,
handlers: &'static str,
handler: Option<&'static str>,
sample: &'static str,
}
const KINDS: &[Kind] = &[
Kind {
key: "visualize as line",
options: "every path/.style={draw},style={every mark/.append style={color=visualizer color}},mark size=2pt,semithick,color=visualizer color,mark=none,/tikz/data visualization/every visualize as line/.try",
handlers: "\\tikra@dv@handler,\\tikra@dv@marks",
handler: Some("straight line"),
sample: "\\tikra@dv@sample",
},
Kind {
key: "visualize as smooth line",
options: "",
handlers: "",
handler: Some("smooth line"),
sample: "",
},
Kind {
key: "visualize as smooth cycle",
options: "",
handlers: "",
handler: Some("smooth cycle"),
sample: "",
},
Kind {
key: "visualize as scatter",
options: "mark=x,mark size=2pt,style={every mark/.append style={color=visualizer color}},/tikz/data visualization/every visualize as scatter/.try",
handlers: "\\tikra@dv@marks",
handler: Some("no lines"),
sample: "\\tikra@dv@sample",
},
Kind {
key: "visualize as rectangles",
options: "every path/.style={draw},semithick,color=visualizer color,/tikz/data visualization/every visualize as rectangles/.try",
handlers: "",
handler: None,
sample: "\\tikra@dv@rectsample",
},
];
fn name_of(e: &Engine) -> String {
keys::name(&e.current_key).to_string()
}
fn text_of(e: &mut Engine, v: Option<Vec<Token>>) -> Result<String, Error> {
Ok(e.tex.expand_to_string(v.unwrap_or_default())?.trim().to_string())
}
pub fn dv_keys(e: &mut Engine, v: Vec<Token>) -> Result<(), Error> {
e.keys_command(v, Some(DV), true)
}
fn dv_text(e: &mut Engine, s: &str) -> Result<(), Error> {
dv_keys(e, tikra_tokens(s))
}
fn k_unknown(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = macro_text(e, "pgfkeyscurrentname");
let path = format!("/tikz/{name}");
if e.run_key(&path, v.clone())? {
return Ok(());
}
let old = std::mem::replace(&mut e.st.key_path, "/tikz".into());
let r = e.invoke(&path, v);
e.st.key_path = old;
r
}
fn k_data_unknown(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = macro_text(e, "pgfkeyscurrentname");
let old = std::mem::replace(&mut e.st.key_path, "/pgf/data".into());
let r = e.invoke(&format!("/pgf/data/{name}"), v);
e.st.key_path = old;
r
}
fn k_data_point_unknown(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = macro_text(e, "pgfkeyscurrentname");
let v = v.unwrap_or_else(|| vec![Token::cs("pgfkeysnovalue")]);
e.tex.set_meaning(&Token::cs(¯o_name(&format!("/data point/{name}"))), macro_tokens(v), false);
Ok(())
}
fn k_hook(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = name_of(e);
add_hook(e, &name, v.unwrap_or_default())
}
pub fn add_hook(e: &mut Engine, name: &str, code: Vec<Token>) -> Result<(), Error> {
let Some(dv) = e.datavis.last_mut() else { return Ok(()) };
let h: &mut Hooks = &mut dv.hooks;
let (list, front) = match name {
"before survey" => (&mut h.before_survey, false),
"at start survey" => (&mut h.at_start_survey, false),
"at end survey" => (&mut h.at_end_survey, true),
"after survey" => (&mut h.after_survey, true),
"before visualization" => (&mut h.before_visualization, false),
"at start visualization" => (&mut h.at_start_visualization, false),
"at end visualization" => (&mut h.at_end_visualization, true),
_ => (&mut h.after_visualization, true),
};
if front {
let old = std::mem::take(list);
list.extend(code);
list.extend(old);
} else {
list.extend(code);
}
Ok(())
}
fn command(name: &str, args: &[Vec<Token>]) -> Vec<Token> {
let mut v = vec![Token::cs(name)];
for a in args {
v.extend(group(a.clone()));
}
v
}
fn text_arg(s: &str) -> Vec<Token> {
tex::string_tokens(s)
}
fn k_new_object(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let spec = v.unwrap_or_default();
let mut store = String::new();
let mut when = "before survey".to_string();
for item in keys::split_list(&spec) {
let value = e.tex.expand_to_string(item.value.unwrap_or_default())?.trim().to_string();
match item.key.as_str() {
"store" => store = value,
"when" => when = value,
_ => {}
}
}
if !store.is_empty() {
set_key(e, &store, "");
}
let code = command("tikra@dv@newobject", &[text_arg(&store), spec]);
add_hook(e, &when, code)
}
fn k_data_point(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let code = super::data_point_code(v.unwrap_or_default());
e.do_add_data(code)
}
pub fn current_axis(e: &mut Engine) -> String {
macro_text(e, "tikra@dv@axis")
}
fn axis_key(e: &mut Engine, sub: &str) -> String {
format!("{}/{sub}", current_axis(e))
}
fn k_new_axis_base(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = text_of(e, v)?;
new_axis(e, &name)
}
fn new_axis(e: &mut Engine, name: &str) -> Result<(), Error> {
let axis = format!("{DV}/{name}");
for (sub, value) in [
("attribute", name),
("function", ""),
("scaling", ""),
("scaling/default", "0 at 0 and 1 at 1"),
("ticks at", ""),
("default ticks at", ""),
("grid at", ""),
("default grid at", ""),
("label", ""),
("padding min", ""),
("padding max", ""),
("tick placement strategy", "linear"),
] {
set_key(e, &format!("{axis}/{sub}"), value);
}
define_native(e, &axis, k_axis);
if let Some(dv) = e.datavis.last_mut() {
if !dv.axes.iter().any(|a| a == name) {
dv.axes.push(name.to_string());
}
}
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("mapper"), text_arg(&axis)]))
}
pub fn define_native(e: &mut Engine, path: &str, f: KeyFn) {
let id = match e.key_fns.iter().position(|g| *g as usize == f as usize) {
Some(i) => super::NATIVE_KEY_BASE + i as u16,
None => {
e.key_fns.push(f);
super::NATIVE_KEY_BASE + (e.key_fns.len() - 1) as u16
}
};
e.tex.define(¯o_name(&format!("{path}/.@cmd")), Meaning::Native(id));
}
pub fn k_cell_reset_cm(e: &mut Engine, _v: Option<Vec<Token>>) -> Result<(), Error> {
let t = e.st.transform;
e.st.transform = crate::scene::Transform { a: 1.0, b: 0.0, c: 0.0, d: 1.0, ..t };
e.st.pending = crate::scene::Transform::default();
Ok(())
}
fn k_new_cartesian_axis(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = text_of(e, v)?;
new_axis(e, &name)?;
let axis = format!("{DV}/{name}");
set_key(e, &format!("{axis}/scaling/default"), "0 at 0 and 1 at 1cm");
set_key(e, &format!("{axis}/unit vector"), "\\pgfqpoint{1pt}{0pt}");
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("linear"), text_arg(&axis)]))
}
fn k_new_polar_axes(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let args = crate::engine::options::split_arguments(&v.unwrap_or_default(), 2);
let angle = e.tex.expand_to_string(args[0].clone())?.trim().to_string();
let radius = e.tex.expand_to_string(args[1].clone())?.trim().to_string();
new_axis(e, &angle)?;
new_axis(e, &radius)?;
let (a, r) = (format!("{DV}/{angle}"), format!("{DV}/{radius}"));
set_key(e, &format!("{a}/attribute"), "angle");
set_key(e, &format!("{r}/attribute"), "radius");
set_key(e, &format!("{r}/scaling/default"), "0 at 0 and 1 at 1cm");
set_key(e, &format!("{a}/unit vector 0 degrees"), "\\pgfqpoint{1pt}{0pt}");
set_key(e, &format!("{a}/unit vector 90 degrees"), "\\pgfqpoint{0pt}{1pt}");
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("polar"), text_arg(&a), text_arg(&r)]))
}
fn k_axis(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let axis = e.current_key.clone();
let old = e.tex.meaning_of("tikra@dv@axis");
e.tex.define_string("tikra@dv@axis", &axis, false);
let r = e.keys_command(v.unwrap_or_default(), Some(&format!("{DV}/axis options")), true);
e.tex.set_meaning(&Token::cs("tikra@dv@axis"), old, false);
r
}
fn k_all_axes(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let v = v.unwrap_or_default();
let axes = e.datavis.last().map(|d| d.axes.clone()).unwrap_or_default();
for a in axes {
let mut item = tikra_tokens(&format!("{a}="));
item.extend(group(v.clone()));
dv_keys(e, item)?;
}
Ok(())
}
fn k_new_axis_system(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let args = crate::engine::options::split_arguments(&v.unwrap_or_default(), 4);
let name = e.tex.expand_to_string(args[0].clone())?.trim().to_string();
let mut body = args[1].clone();
body.extend(tikra_tokens(&format!(",{name}/.cd,")));
body.extend(args[2].clone());
body.extend(tikra_tokens(&format!(",{DV}/every {name}/.try,")));
body.push(Token::Char('#', Cat::Param));
body.push(Token::other('1'));
body.extend(tikra_tokens(&format!(",{DV}/.cd,")));
body.extend(args[3].clone());
let mut def = tikra_tokens(&format!("{name}/.style="));
def.extend(group(body));
dv_keys(e, def)
}
fn k_setter(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
if e.datavis.last().is_some_and(|d| d.setters_off) {
return Ok(());
}
let key = e.current_key.clone();
let mut old = keys::value(&e.tex, &key).unwrap_or_default();
old.push(Token::other(','));
old.extend(v.unwrap_or_default());
e.tex.set_meaning(&Token::cs(¯o_name(&key)), macro_tokens(old), false);
Ok(())
}
fn k_node_styling(e: &mut Engine, _v: Option<Vec<Token>>) -> Result<(), Error> {
let v = keys::value(&e.tex, &format!("{DV}/@node styling")).unwrap_or_default();
let old = std::mem::replace(&mut e.st.key_path, "/tikz".into());
let r = e.keys_command(v, Some("/tikz"), true);
e.st.key_path = old;
r
}
fn k_axis_value(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let sub = match name_of(e).as_str() {
"@default scaling" => "scaling/default".to_string(),
n => n.to_string(),
};
let key = axis_key(e, &sub);
let v = v.unwrap_or_default();
e.tex.set_meaning(&Token::cs(¯o_name(&key)), macro_tokens(v), false);
Ok(())
}
fn k_axis_label(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let v = match v {
Some(v) => v,
None => {
let a = {
let k = axis_key(e, "attribute");
key_text(e, &k)
}
.unwrap_or_default();
tikra_tokens(&format!("$\\mathit{{{a}}}$"))
}
};
let key = axis_key(e, "label");
e.tex.set_meaning(&Token::cs(¯o_name(&key)), macro_tokens(v), false);
Ok(())
}
fn k_goto(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let axis = current_axis(e);
let value = text_of(e, v)?;
e.goto(&axis, &value)
}
fn k_goto_pos(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let a = {
let k = axis_key(e, "attribute");
key_text(e, &k)
}
.unwrap_or_default();
let value = text_of(e, v)?;
set_key(e, &format!("/data point/{a}/out pos"), &value);
Ok(())
}
fn k_anchor_at(e: &mut Engine, _v: Option<Vec<Token>>) -> Result<(), Error> {
let which = if name_of(e).ends_with("min") { "min" } else { "max" };
let axis = current_axis(e);
e.anchor_at(&axis, which)
}
fn k_visualize_part(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let what = name_of(e);
let axis = current_axis(e);
let v = v.unwrap_or_default();
let dv = e.dv()?;
let list = match what.as_str() {
"visualize axis" => &mut dv.actions.axes,
"visualize ticks" => &mut dv.actions.ticks,
"visualize grid" => &mut dv.actions.grids,
_ => &mut dv.actions.labels,
};
list.push((axis, v));
Ok(())
}
fn k_ticks_or_grid(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let what = name_of(e);
let v = v.unwrap_or_default();
let subs: &[&str] = match what.as_str() {
"ticks" => &["ticks at"],
"grid" => &["grid at"],
_ => &["ticks at", "grid at"],
};
for sub in subs {
let key = axis_key(e, sub);
let mut old = keys::value(&e.tex, &key).unwrap_or_default();
old.push(Token::other(','));
old.extend(v.clone());
e.tex.set_meaning(&Token::cs(¯o_name(&key)), macro_tokens(old), false);
}
Ok(())
}
fn k_include_value(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let axis = current_axis(e);
add_hook(e, "before survey", command("tikra@dv@include", &[text_arg(&axis), v.unwrap_or_default()]))
}
fn k_range_end(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let which = if name_of(e).starts_with("min") { "min" } else { "max" };
let axis = current_axis(e);
add_hook(e, "at end survey", command("tikra@dv@setrange", &[text_arg(&axis), v.unwrap_or_default(), text_arg(which)]))
}
pub fn scan_point(e: &mut Engine, v: Vec<Token>) -> Result<Point, Error> {
let s = e.tex.expand_to_string(v)?;
let s = s.trim();
let inner = s.strip_prefix('(').and_then(|r| r.strip_suffix(')')).unwrap_or(s).to_string();
e.begin_group();
e.st.transform = crate::scene::Transform::default();
let r = e.coordinate(&inner);
e.end_group();
let p = r?.p;
Ok(Point::new(p.x / PT, p.y / PT))
}
pub fn qpoint(p: Point) -> String {
format!("\\pgfqpoint{{{}pt}}{{{}pt}}", super::num(p.x), super::num(p.y))
}
fn k_unit_vector(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let p = scan_point(e, v.unwrap_or_default())?;
let key = axis_key(e, "unit vector");
set_key(e, &key, &qpoint(p));
Ok(())
}
fn k_unit_vectors(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let args = crate::engine::options::split_arguments(&v.unwrap_or_default(), 2);
let a = scan_point(e, args[0].clone())?;
let b = scan_point(e, args[1].clone())?;
let (ka, kb) = (axis_key(e, "unit vector 0 degrees"), axis_key(e, "unit vector 90 degrees"));
set_key(e, &ka, &qpoint(a));
set_key(e, &kb, &qpoint(b));
Ok(())
}
fn k_unit_length(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let s = text_of(e, v)?;
let (length, units) = match s.split_once("per") {
Some((l, u)) => (l.trim().to_string(), u.trim().trim_end_matches("units").trim().to_string()),
None => (s.trim().to_string(), "1".to_string()),
};
let key = axis_key(e, "scaling");
set_key(e, &key, &format!("0 at 0 and {{{units}}} at {{{length}}}"));
Ok(())
}
fn k_radius_axes(e: &mut Engine, _v: Option<Vec<Token>>) -> Result<(), Error> {
let which = key_text(e, &format!("{DV}/@which radius axes at")).unwrap_or_default();
let style = key_text(e, &format!("{DV}/@which visualize radius axis")).unwrap_or_default();
let list = keys::value(&e.tex, &format!("{DV}/{which}")).unwrap_or_default();
for item in super::axes_split(&list) {
if keys::trim(&item).is_empty() {
continue;
}
let mut v = tikra_tokens(&format!("{style}="));
v.extend(group(item));
dv_keys(e, v)?;
}
Ok(())
}
fn k_visualize_as(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let kind = name_of(e);
let name = text_of(e, v)?;
let Some(k) = KINDS.iter().find(|k| k.key == kind) else { return Ok(()) };
match kind.as_str() {
"visualize as smooth line" => dv_text(e, &format!("visualize as line={{{name}}}"))?,
"visualize as smooth cycle" => dv_text(e, &format!("visualize as smooth line={{{name}}}"))?,
"visualize as rectangles" => {
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("rectangle"), text_arg(&name)]))?;
set_key(e, &format!("/data point/{name}/attribute 1"), "x");
set_key(e, &format!("/data point/{name}/attribute 2"), "y");
new_visualizer(e, &name, tikra_tokens(k.options), tikra_tokens(&format!("visualizer in legend={}", k.sample)))?;
}
_ => {
add_hook(e, "after survey", command("tikra@dv@make", &[text_arg("plot"), text_arg(&name), tikra_tokens(k.handlers)]))?;
new_visualizer(e, &name, tikra_tokens(k.options), tikra_tokens(&format!("visualizer in legend={}", k.sample)))?;
}
}
if let Some(h) = k.handler {
dv_text(e, &format!("{name}={{{h}}}"))?;
}
Ok(())
}
fn k_new_visualizer(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let args = crate::engine::options::split_arguments(&v.unwrap_or_default(), 3);
let name = e.tex.expand_to_string(args[0].clone())?.trim().to_string();
new_visualizer(e, &name, args[1].clone(), args[2].clone())
}
fn new_visualizer(e: &mut Engine, name: &str, options: Vec<Token>, legend: Vec<Token>) -> Result<(), Error> {
define_native(e, &format!("{DV}/{name}"), k_visualizer);
let n = {
let dv = e.dv()?;
dv.visualizers += 1;
dv.visualizers
};
set_key(e, &format!("/data point/set/{name}"), &n.to_string());
let base = format!("{DV}/visualizers/{name}");
e.tex.set_meaning(&Token::cs(¯o_name(&format!("{base}/options"))), macro_tokens(options), false);
let mut def = tikra_tokens(&format!("{base}/signal/.code=\\tikra@dv@stylesignal,{base}/styling/.style=,{base}/label in legend options/.style="));
def.extend(group(legend));
dv_keys(e, def)
}
fn k_visualizer(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = e.current_key.trim_start_matches(DV).trim_start_matches('/').to_string();
let old = e.tex.meaning_of("tikra@dv@visualizer");
e.tex.define_string("tikra@dv@visualizer", &name, false);
let r = e.keys_command(v.unwrap_or_default(), Some(&format!("{DV}/visualizer options")), true);
e.tex.set_meaning(&Token::cs("tikra@dv@visualizer"), old, false);
r?;
let mut set = tikra_tokens("\\pgfkeys");
set.extend(group(tikra_tokens(&format!("/data point/set={{{name}}}"))));
e.do_add_data(set)
}
fn current_visualizer(e: &mut Engine) -> String {
macro_text(e, "tikra@dv@visualizer")
}
fn k_handler(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let args = crate::engine::options::split_arguments(&v.unwrap_or_default(), 2);
let handler = e.tex.expand_to_string(args[0].clone())?.trim().to_string();
let name = current_visualizer(e);
let base = format!("{DV}/visualizers/{name}");
let mut v = tikra_tokens(&format!("{base}/styling/.append style={{/utils/exec={{\\def\\tikra@dv@handler{{{handler}}}}}}},{base}/label in legend options/.append style="));
v.extend(group(args[1].clone()));
dv_keys(e, v)
}
fn k_ignore_style_sheets(e: &mut Engine, _v: Option<Vec<Token>>) -> Result<(), Error> {
let name = current_visualizer(e);
dv_text(e, &format!("visualizers/{name}/signal/.code="))?;
let dv = e.dv()?;
dv.visualizers = dv.visualizers.saturating_sub(1);
Ok(())
}
fn k_style_sheet(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let sheet = text_of(e, v)?;
add_style_attribute(e, "/data point/set", &sheet)
}
fn k_style_sheet_handler(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let path = macro_text(e, "pgfkeyscurrentpath");
let sheet = text_of(e, v)?;
add_style_attribute(e, &path, &sheet)
}
fn add_style_attribute(e: &mut Engine, attribute: &str, sheet: &str) -> Result<(), Error> {
let Some(dv) = e.datavis.last_mut() else { return Ok(()) };
let id = dv.objects.len();
dv.objects.push(Object::StyleAttribute { attribute: attribute.to_string(), sheet: sheet.to_string() });
dv.connect(id, Slot::InvokeStyle, Signal::Style);
Ok(())
}
fn k_label_in_data(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = current_visualizer(e);
let mut options = tikra_tokens("auto,");
options.extend(v.unwrap_or_default());
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("count"), text_arg(&name)]))?;
add_hook(e, "before visualization", command("tikra@dv@make", &[text_arg("label"), text_arg(&name), options]))
}
fn k_label_when(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let s = text_of(e, v)?;
let (a, t) = s.split_once(" is").unwrap_or((&s, ""));
e.tex.define_string("tikra@dv@labelattr", a.trim(), false);
e.tex.define_string("tikra@dv@labelat", t.trim(), false);
Ok(())
}
fn k_label_at(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = current_visualizer(e);
e.tex.define_string("tikra@dv@labelattr", &format!("set/{name}/count"), false);
let count = e.object_of(&format!("{DV}/visualizers/{name}/count/obj"));
let total = match count.and_then(|c| e.datavis.last().and_then(|d| d.objects.get(c)).cloned()) {
Some(Object::Count { max, .. }) => max,
_ => 0.0,
};
let at = match name_of(e).as_str() {
"index" => text_of(e, v)?,
"pos" => {
let p = text_of(e, v)?;
format!("({p}*{})", super::num(total))
}
_ => {
let n = key_text(e, &format!("/data point/set/{name}")).unwrap_or_else(|| "1".into());
let all = e.datavis.last().map_or(1, |d| d.visualizers.max(1));
format!("(({n}-0.5)/{all}*{})", super::num(total))
}
};
e.tex.define_string("tikra@dv@labelat", &at, false);
Ok(())
}
fn k_new_legend(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = text_of(e, v)?;
let base = format!("{DV}/{name}");
if e.key_exists(&format!("{base}/at")) {
return Ok(());
}
for (sub, value) in [
("@columns or rows", "columns"),
("@ideal", "1"),
("max entries", "100"),
("@strategy", "down then right"),
("anchor", "west"),
("at", "(data visualization bounding box.east)"),
] {
set_key(e, &format!("{base}/{sub}"), value);
}
dv_text(e, &format!("{name}/matrix node styling/.style={{row sep=0pt,column sep=.8em}}"))?;
define_native(e, &base, k_legend);
add_hook(e, "before survey", command("tikra@dv@make", &[text_arg("legend"), text_arg(&name)]))?;
dv_text(e, &format!("{name}={{every new legend}}"))
}
fn k_legend_style(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
dv_text(e, "new legend")?;
let mut item = tikra_tokens("main legend=");
item.extend(group(v.unwrap_or_default()));
dv_keys(e, item)
}
fn k_legend(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let legend = e.current_key.clone();
let old = e.tex.meaning_of("tikra@dv@legend");
e.tex.define_string("tikra@dv@legend", &legend, false);
let r = e.keys_command(v.unwrap_or_default(), Some(&format!("{DV}/legend options")), true);
e.tex.set_meaning(&Token::cs("tikra@dv@legend"), old, false);
r
}
fn k_new_legend_entry(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let options = v.unwrap_or_default();
e.begin_group();
let r = e.keys_command(options.clone(), Some(&format!("{DV}/legend entry options")), true);
let legend = key_text(e, &format!("{DV}/legend entry options/legend")).unwrap_or_else(|| "main legend".into());
e.end_group();
r?;
dv_text(e, &format!("new legend={{{legend}}}"))?;
let mut entry = tikra_tokens(&format!("legend={{{legend}}},"));
entry.extend(options);
let code = command("tikra@dv@addentry", &[text_arg(&legend), entry]);
add_hook(e, "at start survey", code)
}
fn k_label_in_legend(e: &mut Engine, v: Option<Vec<Token>>) -> Result<(), Error> {
let name = current_visualizer(e);
let mut entry = tikra_tokens(&format!(
"visualizer in legend style={{/tikz/data visualization/visualizers/{name}/styling/.try}},/tikz/data visualization/every label in legend/.try,/tikz/.cd,every label/.try,/tikz/data visualization/legend entry options/.cd,setup={{/data point/set={{{name}}}}},/tikz/data visualization/visualizers/{name}/label in legend options/.try,"
));
entry.extend(v.unwrap_or_default());
let mut item = tikra_tokens("new legend entry=");
item.extend(group(entry));
dv_keys(e, item)
}