mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 08:45:25 +00:00
Merge pull request #7692 from dotCooCoo/fix/graph-labels-embedded-newline-middle
Graphs: centered labels with a line break in their text stay centered
This commit is contained in:
@@ -149,20 +149,22 @@ impl Gen<'_> {
|
||||
refresh_bounds(&mut t);
|
||||
t
|
||||
}
|
||||
/// A category or series label: a `|` in it starts a new line (Add graph labels). `middle`: the lines are centred
|
||||
/// on `at` (labels beside an axis or in a wedge) instead of running down from it.
|
||||
/// A category or series label: a `|` in it starts a new line (Add graph labels), as does a line break a quoted CSV
|
||||
/// cell keeps. `middle`: the lines are centred on `at` (labels beside an axis or in a wedge) instead of running down
|
||||
/// from it.
|
||||
pub(super) fn label(&mut self, role: Role, at: Point, s: &str, justify: Justify, middle: bool) -> Arc<Node> {
|
||||
let lines = s.split('|').count();
|
||||
let text = s.replace('|', "\n");
|
||||
let lines = text.split('\n').count();
|
||||
// Default leading puts the first and last baselines this far above and below `at`.
|
||||
let half = (lines - 1) as f64 * label_size(self.styles, role) * 1.2 / 2.0;
|
||||
let start = if middle { Point::new(at.x, at.y - half) } else { at };
|
||||
let mut t = self.text_object(role, start, &s.replace('|', "\n"), justify);
|
||||
let mut t = self.text_object(role, start, &text, justify);
|
||||
// Kept leading or paragraph spacing moves the lines: put the middle of the first and last baselines on `at`. An
|
||||
// empty line has no baseline: an empty first or last line leaves the label where the default leading puts it.
|
||||
if middle && kept_para(self.styles, role).is_some() {
|
||||
let ys: Vec<f64> = t.baselines().map(|(a, _)| a.y).collect();
|
||||
let ends_set = s.split('|').next().is_some_and(|l| !l.is_empty()) && s.split('|').next_back().is_some_and(|l| !l.is_empty());
|
||||
let set = s.split('|').filter(|l| !l.is_empty()).count();
|
||||
let ends_set = text.split('\n').next().is_some_and(|l| !l.is_empty()) && text.split('\n').next_back().is_some_and(|l| !l.is_empty());
|
||||
let set = text.split('\n').filter(|l| !l.is_empty()).count();
|
||||
if let (Some(first), Some(last), true) = (ys.first(), ys.last(), ends_set && ys.len() == set) {
|
||||
t.xf = Affine::translate((0.0, half - (first + last) / 2.0)) * t.xf;
|
||||
}
|
||||
|
||||
@@ -7142,6 +7142,43 @@ fn a_centered_label_with_an_empty_middle_line_and_kept_leading_stays_centered()
|
||||
assert!((after - before).abs() < 0.01, "the three-line category label keeps its middle: {before} {after}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_centered_label_with_a_line_break_in_its_text_is_set_as_with_a_bar() {
|
||||
// A quoted CSV cell keeps its line breaks, so they reach the label as well as the `|` it is written with.
|
||||
for (bar, quoted, text) in [("Q|1", "\"Q\n1\"", "Q\n1"), ("Q|||1", "\"Q\n||1\"", "Q\n\n\n1")] {
|
||||
let label_middle = |s: &Session, id: NodeId| {
|
||||
let n = s.doc().unwrap().doc.node(id).unwrap().clone();
|
||||
let label =
|
||||
group(&n, "Axes").children().unwrap().iter().find(|c| matches!(&c.kind, NodeKind::Text(t) if t.plain_text() == text)).unwrap();
|
||||
let b = label.geometric_bounds().unwrap();
|
||||
(b.y0 + b.y1) / 2.0
|
||||
};
|
||||
let drawn = |cat: &str, leading: bool| {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 800, "height": 800})).unwrap();
|
||||
let csv = format!(",2024,2025\n{cat},3,2\nQ2,5,4\nQ3,-1,6");
|
||||
let id = NodeId(
|
||||
s.execute("graph.create", &json!({"type": "bar", "x": 100, "y": 100, "width": 300, "height": 200, "csv": csv})).unwrap()["id"]
|
||||
.as_u64()
|
||||
.unwrap(),
|
||||
);
|
||||
if leading {
|
||||
let l = text_id(&s, id, "Axes", text);
|
||||
s.execute("select.set", &json!({"ids": [l.0]})).unwrap();
|
||||
s.execute("text.setFormat", &json!({"autoLeading": 400})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
s.execute("graph.setData", &json!({"id": id.0, "csv": csv})).unwrap();
|
||||
}
|
||||
label_middle(&s, id)
|
||||
};
|
||||
for leading in [false, true] {
|
||||
let (by_bar, by_break) = (drawn(bar, leading), drawn(quoted, leading));
|
||||
assert!((by_bar - by_break).abs() < 0.01, "{text:?} sits where {bar:?} does (kept leading: {leading}): {by_bar} {by_break}");
|
||||
}
|
||||
assert!((drawn(quoted, true) - drawn(quoted, false)).abs() < 0.01, "{text:?} keeps its middle with kept leading");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_legend_across_top_measures_its_labels_with_system_layout() {
|
||||
let mut s = Session::new();
|
||||
|
||||
Reference in New Issue
Block a user