mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Graph Data's Transpose row/column and Switch x/y, as https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-graph-data.html describes, a `|` in a label as a line break, as https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-graph-labels-and-data-sets.html describes, and the pie graph options of https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/format-pie-graphs.html - graph.setData {transpose: true} swaps rows and columns (after any new data in the same call); a graph with more categories than a graph can have series (256) is refused rather than cut. {switchXY: true} swaps each scatter series' y and x columns, labels included. - A `|` in a category or series label starts a new line: legend rows grow with their lines, labels beside an axis or in a wedge centre theirs. A label of only `|` is blank. - Pie graphs: pieLegend none|standard|wedges (Legends in Wedges puts the series labels inside their wedges, in white on dark wedges, drawn over the pies), piePosition ratio|even|stacked and pieSort all|first|none, as dropdowns in the Graph Type dialog, which lists them for pie graphs only. Own choices, as the page doesn't say: Position and Sort keep today's drawing by default (Even, None); Ratio sizes pies by area, the biggest keeping the full size; Stacked pies share a centre, each drawn as the ring outside the next smaller one so none covers another, with their names over them. New fields are written only when set. Graphs with default options draw the same art as before.
150 lines
7.5 KiB
Rust
150 lines
7.5 KiB
Rust
//! The generic parameter dialog (`ui.paramDialog`): edits a command's parameters (`__command`,
|
||
//! headed `__label`) and runs it on OK, with the fixed ones in `__params` (not shown) added.
|
||
|
||
use serde_json::Value;
|
||
|
||
use super::{DialogSpec, form};
|
||
use crate::VectorcraftApp;
|
||
use crate::state::Dialog;
|
||
|
||
pub(super) const SPEC: DialogSpec = DialogSpec {
|
||
heading: |d| tl!(&d.str("__label")).to_string(),
|
||
body: |app, ui, d| {
|
||
let command = d.str("__command");
|
||
let lengths = lengths(&command);
|
||
form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), &|_| 0, app.session.general_unit());
|
||
false
|
||
},
|
||
confirm,
|
||
..DialogSpec::FORM
|
||
};
|
||
|
||
/// The parameters of the commands this dialog edits that are distances.
|
||
fn lengths(command: &str) -> &'static [&'static str] {
|
||
match command {
|
||
"graph.create" => &["width", "height"],
|
||
"shape.flare" => &["diameter", "pathLength"],
|
||
"perspective.grid.set" => &["cell", "distance"],
|
||
"object.repeat.options" => &["radius", "hSpacing", "vSpacing"],
|
||
"text.areaOptions" => &["width", "height", "gutter", "inset", "firstBaselineMin"],
|
||
"type.pathOptions" => &["spacing"],
|
||
_ => &[],
|
||
}
|
||
}
|
||
|
||
/// The parameters of the commands this dialog edits that pick one of some values.
|
||
fn choices(command: &str, key: &str) -> Option<form::Choices> {
|
||
match (command, key) {
|
||
("type.pathOptions", "effect") => Some(crate::menus::PATH_EFFECTS),
|
||
("type.pathOptions", "alignToPath") => Some(PATH_ALIGN),
|
||
("text.areaOptions", "fit") => Some(AREA_FIT),
|
||
("text.areaOptions", "firstBaseline") => Some(FIRST_BASELINE),
|
||
("text.areaOptions", "verticalAlign") => Some(VERTICAL_ALIGN),
|
||
("graph.setType", "valueAxis") => Some(GRAPH_VALUE_AXIS),
|
||
("graph.setType", "tickLength" | "rightTickLength" | "categoryTickLength") => Some(GRAPH_TICK_LENGTH),
|
||
("graph.setType", "pieLegend") => Some(PIE_LEGEND),
|
||
("graph.setType", "piePosition") => Some(PIE_POSITION),
|
||
("graph.setType", "pieSort") => Some(PIE_SORT),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Type on a Path Options › Align to Path.
|
||
const PATH_ALIGN: form::Choices = &[("Ascender", "ascender"), ("Descender", "descender"), ("Center", "center"), ("Baseline", "baseline")];
|
||
|
||
/// Area Type Options › Fit.
|
||
const AREA_FIT: form::Choices = &[("None", "none"), ("Auto Size", "autoHeight"), ("Shrink Text to Fit", "shrinkText")];
|
||
|
||
/// Area Type Options › First Baseline.
|
||
const FIRST_BASELINE: form::Choices =
|
||
&[("Ascent", "ascent"), ("Cap Height", "capHeight"), ("x Height", "xHeight"), ("Leading", "leading"), ("Fixed", "fixed")];
|
||
|
||
/// Area Type Options › Align (vertical alignment of the lines in each row/column).
|
||
const VERTICAL_ALIGN: form::Choices = &[("Top", "top"), ("Center", "center"), ("Bottom", "bottom"), ("Justify", "justify")];
|
||
|
||
/// Graph Type › Value Axis (series picked on both axes show none, and OK leaves each where it is).
|
||
const GRAPH_VALUE_AXIS: form::Choices = &[("On Left Side", "left"), ("On Right Side", "right"), ("On Both Sides", "both")];
|
||
|
||
/// Graph Type › Legend, Position and Sort for pie graphs.
|
||
const PIE_LEGEND: form::Choices = &[("No Legend", "none"), ("Standard Legend", "standard"), ("Legends in Wedges", "wedges")];
|
||
const PIE_POSITION: form::Choices = &[("Ratio", "ratio"), ("Even", "even"), ("Stacked", "stacked")];
|
||
const PIE_SORT: form::Choices = &[("All", "all"), ("First", "first"), ("None", "none")];
|
||
|
||
/// Graph Type › Tick Marks › Length.
|
||
const GRAPH_TICK_LENGTH: form::Choices = &[("None", "none"), ("Short", "short"), ("Full Width", "full")];
|
||
|
||
/// Closes before running, so a dialog the command opens stays open.
|
||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||
let cmd = d.str("__command");
|
||
let mut params = form::params(d);
|
||
if let (Some(o), Some(Value::Object(fixed))) = (params.as_object_mut(), d.fields.get("__params")) {
|
||
o.extend(fixed.clone());
|
||
}
|
||
app.ui.dialog = None;
|
||
// A tool's click-to-size shape (Flare) goes on the active perspective plane while the grid shows.
|
||
let at = |x: &str, y: &str| Some(vectorcraft_geom::Point::new(params.get(x)?.as_f64()?, params.get(y)?.as_f64()?));
|
||
if let Some((c, p)) =
|
||
at("cx", "cy").or_else(|| at("x", "y")).and_then(|pt| vectorcraft_engine::perspective_click(&app.session, &cmd, ¶ms, pt))
|
||
{
|
||
return app.run(&c, p);
|
||
}
|
||
app.run(&cmd, params)
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use serde_json::json;
|
||
use vectorcraft_doc::{NodeKind, VerticalAlign};
|
||
use vectorcraft_engine::Session;
|
||
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn area_type_options_choices_match_the_params_doc() {
|
||
let doc = vectorcraft_engine::find_command("text.areaOptions").unwrap().params;
|
||
for key in ["firstBaseline", "verticalAlign"] {
|
||
let values: Vec<&str> = choices("text.areaOptions", key).unwrap().iter().map(|(_, v)| *v).collect();
|
||
assert!(doc.contains(&format!("{key}?: {}", values.join("|"))), "{key}: {values:?}");
|
||
}
|
||
assert!(choices("text.areaOptions", "firstBaselineMin").is_none(), "a distance, not a choice");
|
||
assert!(choices("text.areaOptions", "inset").is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn area_type_options_dialog_sets_the_vertical_alignment() {
|
||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
||
let id = app.run("text.create", json!({"x": 20, "y": 20, "text": "Hello", "area": {"width": 200, "height": 200}})).unwrap()["id"]
|
||
.as_u64()
|
||
.unwrap();
|
||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||
crate::menus::invoke(&mut app, "text.areaOptions", json!({}));
|
||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("verticalAlign"), "top");
|
||
let ctx = egui::Context::default();
|
||
crate::theme::install_fonts(&ctx);
|
||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(&mut app, ui.ctx()));
|
||
out.textures_delta.clear();
|
||
app.ui.dialog.as_mut().unwrap().fields.insert("verticalAlign".into(), json!("center"));
|
||
crate::dialogs::confirm(&mut app).unwrap();
|
||
let NodeKind::Text(t) = &app.session.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().kind else { panic!("text") };
|
||
assert_eq!(t.area.vertical_align, VerticalAlign::Center);
|
||
}
|
||
|
||
/// Graph Type › Value Axis is a dropdown of the values `graph.setType` takes, and OK puts the
|
||
/// value axis on the side picked.
|
||
#[test]
|
||
fn graph_type_value_axis_is_a_dropdown_of_its_sides() {
|
||
let doc = vectorcraft_engine::find_command("graph.setType").unwrap().params;
|
||
let values: Vec<&str> = choices("graph.setType", "valueAxis").unwrap().iter().map(|(_, v)| *v).collect();
|
||
assert!(doc.contains(&format!("valueAxis?: {}", values.join("|"))), "{values:?}");
|
||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
||
app.run("graph.create", json!({"type": "column", "x": 0, "y": 0, "width": 200, "height": 150})).unwrap();
|
||
crate::menus::graph_dialog(&mut app, "graph.setType").unwrap();
|
||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("valueAxis"), "left");
|
||
app.ui.dialog.as_mut().unwrap().fields.insert("valueAxis".into(), json!("both"));
|
||
crate::dialogs::confirm(&mut app).unwrap();
|
||
assert_eq!(app.session.execute("graph.setType", &json!({})).unwrap()["valueAxis"], "both");
|
||
}
|
||
}
|