Skip to content

Commit ecb2cb0

Browse files
authored
Merge pull request #107 from deffi/segment-relative-positions
Add support for label-pos: (segment, position)
2 parents bd202ee + 68ff5cf commit ecb2cb0

File tree

11 files changed

+188
-15
lines changed

11 files changed

+188
-15
lines changed

README.src.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Pull requests are most welcome!
4242
- Added `brace`, `bracket`, `paren` and `stretched-glyph` node shapes (#99).
4343
- Fix bug with anchors of absolutely-positioned enclose nodes (#95).
4444
- Fix some instabilities with edges related to division by zero (#100, #105).
45+
- Allow specifying label positions as `(segment, position)`
4546

4647
### 0.5.7
4748

docs/manual.pdf

3.35 KB
Binary file not shown.

src/draw.typ

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@
102102
/// the edge in $x y$ coordinates.
103103
#let place-edge-label-on-curve(edge, curve, debug: 0) = {
104104

105-
let curve-point = curve(edge.label-pos)
106-
let curve-point-ε = curve(edge.label-pos + 1e-3%)
105+
let curve-point = curve(edge.label-pos.position)
106+
let curve-point-ε = curve(edge.label-pos.position + 1e-3%)
107107

108108
let θ = wrap-angle-180(angle-between(curve-point, curve-point-ε))
109109
let θ-normal = θ + if edge.label-side == right { +90deg } else { -90deg }
@@ -244,7 +244,11 @@
244244
}
245245

246246
// Draw label
247-
if edge.label != none {
247+
// This edge only has a single segment, so don't draw the label unless it's
248+
// placed on segment 0. This means that when calling this function for the
249+
// individual segments of an edge (`draw-edge-polyline`), the `segment` field
250+
// of `label-pos` must be set to 0.
251+
if edge.label != none and edge.label-pos.segment == 0 {
248252

249253
// Choose label anchor based on edge direction,
250254
// preferring to place labels above the edge
@@ -308,7 +312,7 @@
308312
}
309313

310314
// Draw label
311-
if edge.label != none {
315+
if edge.label != none and edge.label-pos.segment == 0 {
312316

313317
if edge.label-side == auto {
314318
// Choose label side to be on outside of arc
@@ -499,9 +503,15 @@
499503
mark
500504
}).filter(mark => mark.pos != none)
501505

502-
let label-pos = lerp-scale(edge.label-pos, i)
503-
let label-options = if label-pos == none { (label: none) }
504-
else { (label-pos: label-pos, label: edge.label) }
506+
// If the current segment is the one where the label is placed, keep the
507+
// label (but change its segment to 0 because `draw-edge-line` will consider
508+
// this segment a single-segment edge and only draw labels on segment 0).
509+
// Otherwise, draw no label.
510+
let label-options = if i == edge.label-pos.segment {
511+
(label-pos: edge.label-pos + (segment: 0), label: edge.label)
512+
} else {
513+
(label: none)
514+
}
505515

506516

507517
draw-edge-line(

src/edge.typ

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,6 @@
322322

323323

324324

325-
326-
327325
/// Draw a connecting edge in a diagram.
328326
///
329327
///
@@ -429,13 +427,13 @@
429427
/// - roughly above the connector, in the case of straight lines; or
430428
/// - on the outside of the curve, in the case of arcs.
431429
///
432-
/// - label-pos (float, ratio, relative length): Position of the label along the
430+
/// - label-pos (float, ratio, relative length, array): Position of the label along the
433431
/// edge, from the start to end.
434432
///
435433
/// A number or ratio between zero and one is interpreted as a fraction of the
436434
/// edge length. Physical and relative relative lengths work too. For example,
437435
/// `100% - 1em` means `1em` from the end.
438-
///
436+
///
439437
/// #stack(
440438
/// dir: ltr,
441439
/// spacing: 1fr,
@@ -448,6 +446,12 @@
448446
/// For `"poly"` edges (see @edge-types), a number does not specify a fraction
449447
/// of the path length; instead, the $k$th vertex is at position $k/n$ where
450448
/// $n$ is the number of vertices. Each midpoint is then at $k/n + 0.5$.
449+
///
450+
/// As an alternative, the position can be specified by an array
451+
/// `(segment, position)`, where `segment` is an integer that indicates which
452+
/// segment the label is placed on (segment $s$ is the one between vertices
453+
/// $s$ and $s+1$). `position` is then relative to the specified segment; the
454+
/// segment midpoint is at `(s, 50%)`.
451455
///
452456
/// - label-sep (length): Separation between the connector and the label anchor.
453457
///
@@ -787,7 +791,7 @@
787791
let options = (
788792
vertices: vertices,
789793
label: label,
790-
label-pos: as-relative(label-pos),
794+
label-pos: label-pos,
791795
label-sep: label-sep,
792796
label-angle: label-angle,
793797
label-anchor: label-anchor,
@@ -835,8 +839,6 @@
835839
}
836840
options.vertices = options.vertices.map(interpret-coord-str)
837841

838-
839-
840842
if options.label-side not in (left, center, right, auto) {
841843
error("`label-side` must be one of `left`, `center`, `right`, or `auto`; got #0.", options.label-side)
842844
}
@@ -867,11 +869,60 @@
867869

868870

869871

872+
// Convert to `(segment, pos)`, where `segment` is an `int` and `pos` is a
873+
// `relative`. `pos` is relative to segment `segment`.
874+
//
875+
// Possible inputs:
876+
// * `(segment, pos)`, where `segment` is an int and `pos` is accepted by
877+
// `as-relative`. `pos` will be interpreted as relative to segment
878+
// `segment`.
879+
// * `pos`, where `pos` is accepted by `as-relative`. `pos` will be
880+
// interpreted as relative to the whole edge.
881+
#let normalize-position(position, n-segments) = {
882+
if type(position) == array and type(position.at(0)) == int {
883+
// Segment specified
884+
(
885+
segment: position.at(0),
886+
position: as-relative(position.at(1)),
887+
)
888+
889+
} else {
890+
// No segment specified
891+
position = as-relative(position)
892+
position = position.ratio * n-segments + position.length
893+
894+
// Which segment does the position refer to?
895+
// Use ceil-1 instead of floor to put positions that fall on the boundary
896+
// between two segment on the end of the first, not the beginning of the
897+
// second. There's no particular reason to do it like this, other than
898+
// compatibility.
899+
let segment = calc.ceil(float(position.ratio)) - 1
900+
if segment < 0 { segment = 0 }
901+
if segment > n-segments - 1 { segment = n-segments - 1 }
902+
903+
// Make the position relative to the selected segment. This applies only to
904+
// the `ratio` part of the `relative` position, not to the `length` part.
905+
let position = (position.ratio - 100% * segment) + position.length
906+
907+
(segment: segment, position: position)
908+
}
909+
}
910+
911+
912+
870913
#let resolve-edge-options(edge, options) = {
871914
// let to-pt(len) = to-abs-length(len, options.em-size)
872915

873916
edge += interpret-marks-arg(edge.marks)
874917

918+
// Determine the number of segments
919+
let n-segments = edge.vertices.len() - 1
920+
if edge.corner != none { n-segments += 1 }
921+
922+
// Now that we know the number of segments, we can normalize the label
923+
// positions
924+
edge.label-pos = normalize-position(edge.label-pos, n-segments)
925+
875926
if edge.stroke == none {
876927
// hack: for no stroke, it's easier to do the following.
877928
// then we have the guarantee that edge.stroke is actually

tests/edge/calculations/test.typ

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#set page(width: auto, height: auto, margin: 1em)
2+
#import "/src/edge.typ": normalize-position
3+
4+
// this test contains no visual output
5+
#show: none
6+
7+
#let assert-position-almost-equal(a, b) = {
8+
a.position = a.position + 0pt + 0%
9+
b.position = b.position + 0pt + 0%
10+
11+
let dl = calc.abs(a.position.length - b.position.length)
12+
let dr = calc.abs(a.position.ratio - b.position.ratio)
13+
14+
assert(a.segment == b.segment
15+
and dl < 1e-3pt
16+
and dr < 1e-3%
17+
, message: repr(a) + " != " + repr(b))
18+
}
19+
20+
= `normalize-position`
21+
22+
== Odd number of segments
23+
24+
#assert-position-almost-equal(normalize-position(-10%, 5), (segment: 0, position: -50%))
25+
#assert-position-almost-equal(normalize-position(0%, 5), (segment: 0, position: 0%))
26+
#assert-position-almost-equal(normalize-position(10%, 5), (segment: 0, position: 50%))
27+
#assert-position-almost-equal(normalize-position(20%, 5), (segment: 0, position: 100%))
28+
#assert-position-almost-equal(normalize-position(30%, 5), (segment: 1, position: 50%))
29+
#assert-position-almost-equal(normalize-position(40%, 5), (segment: 1, position: 100%))
30+
#assert-position-almost-equal(normalize-position(50%, 5), (segment: 2, position: 50%))
31+
#assert-position-almost-equal(normalize-position(60%, 5), (segment: 2, position: 100%))
32+
#assert-position-almost-equal(normalize-position(70%, 5), (segment: 3, position: 50%))
33+
#assert-position-almost-equal(normalize-position(80%, 5), (segment: 3, position: 100%))
34+
#assert-position-almost-equal(normalize-position(90%, 5), (segment: 4, position: 50%))
35+
#assert-position-almost-equal(normalize-position(100%, 5), (segment: 4, position: 100%))
36+
#assert-position-almost-equal(normalize-position(110%, 5), (segment: 4, position: 150%))
37+
38+
== Even number of segments
39+
40+
#assert-position-almost-equal(normalize-position(-10%, 4), (segment: 0, position: -40%))
41+
#assert-position-almost-equal(normalize-position(0%, 4), (segment: 0, position: 0%))
42+
#assert-position-almost-equal(normalize-position(10%, 4), (segment: 0, position: 40%))
43+
#assert-position-almost-equal(normalize-position(20%, 4), (segment: 0, position: 80%))
44+
#assert-position-almost-equal(normalize-position(30%, 4), (segment: 1, position: 20%))
45+
#assert-position-almost-equal(normalize-position(40%, 4), (segment: 1, position: 60%))
46+
#assert-position-almost-equal(normalize-position(50%, 4), (segment: 1, position: 100%))
47+
#assert-position-almost-equal(normalize-position(60%, 4), (segment: 2, position: 40%))
48+
#assert-position-almost-equal(normalize-position(70%, 4), (segment: 2, position: 80%))
49+
#assert-position-almost-equal(normalize-position(80%, 4), (segment: 3, position: 20%))
50+
#assert-position-almost-equal(normalize-position(90%, 4), (segment: 3, position: 60%))
51+
#assert-position-almost-equal(normalize-position(100%, 4), (segment: 3, position: 100%))
52+
#assert-position-almost-equal(normalize-position(110%, 4), (segment: 3, position: 140%))
53+
54+
== Different inputs
55+
56+
#assert-position-almost-equal(normalize-position(100%, 5), (segment: 4, position: 100%))
57+
#assert-position-almost-equal(normalize-position(1, 5), (segment: 4, position: 100%))
58+
#assert-position-almost-equal(normalize-position(1.0, 5), (segment: 4, position: 100%))
59+
60+
61+
== With length
62+
63+
#assert-position-almost-equal(normalize-position(100% + 10pt, 5), (segment: 4, position: 100% + 10pt))
64+
#assert-position-almost-equal(normalize-position(-10pt, 5), (segment: 0, position: 0% - 10pt))
65+
66+
#assert-position-almost-equal(normalize-position(1em, 5), (segment: 0, position: 0% + 1em))
67+
#assert-position-almost-equal(normalize-position(10pt - 1em, 5), (segment: 0, position: 0% + 10pt - 1em))
68+
#assert-position-almost-equal(normalize-position(10% + 10pt - 1em, 5), (segment: 0, position: 50% + 10pt - 1em))
69+
70+
== With segment
71+
72+
#assert-position-almost-equal(normalize-position((3, 27%), 5), (segment: 3, position: 27%))
73+
// Segment out of range
74+
#assert-position-almost-equal(normalize-position((5, 27%), 3), (segment: 5, position: 27%))
19.6 KB
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#set page(width: auto, height: auto, margin: 1em)
2+
#import "/src/exports.typ" as fletcher: diagram, node, edge
3+
4+
#diagram(
5+
debug: 0,
6+
7+
edge-corner-radius: 10pt,
8+
9+
{
10+
for i in (
11+
(0, -50%),
12+
(0, 0%),
13+
(0, 50%),
14+
(0, 100%),
15+
(0, 150%),
16+
17+
(1, 50%),
18+
19+
(2, -0.5),
20+
(2, 0),
21+
(2, 12pt),
22+
(2, 0.25),
23+
(2, 0.5),
24+
(2, 0.75),
25+
(2, 100%-6pt),
26+
(2, 100%+6pt),
27+
28+
(3, 50%),
29+
) {
30+
edge((0, 0), "d,drr,ddd", "-}>",
31+
[#i],
32+
label-side: center,
33+
label-pos: i,
34+
)
35+
}
36+
}
37+
)

tests/label/pos/ref/6.png

4.62 KB
Loading

tests/label/pos/ref/7.png

4.06 KB
Loading

tests/label/pos/ref/8.png

4.01 KB
Loading

0 commit comments

Comments
 (0)