Jollywatt/typst-fletcher

Self-edges only work on top or bottom

Closed this issue · 3 comments

When using an edge to point from a node to itself, with bend: 130deg the bend will come either out of the top of the bottom (depending on if bend is negative) however there does not seem to be a way to make the bend come out of the side, either left or right.

There is a hack you can use which is why I've not bothered to add a new controller for this:

#import "@preview/fletcher:0.4.5": diagram, node, edge

#diagram({
  node((0,0), [Hello], stroke: 1pt, shape: circle)

  edge((0,-.1), (0,+.1), "-|>", bend: 120deg)

  let polar(r, theta) = (r*calc.cos(theta), r*calc.sin(theta))
  let theta = 45deg
  edge(polar(0.1, theta), polar(-0.1, theta), "-|>", bend: 130deg)
})
Screenshot 2024-05-29 at 22 23 21

By adjusting the edge‘s vertices by small amounts, you change the angle of the line that connects them; this line is then bent into the final arc that you see.

I haven't thought of a good interface for this. We could add a loop-angle option to edge, but then it wouldn't make sense for non-loop edges, and edge() already has lots of options. I want to keep it simple. (Ideas are welcome!) At the very least, there should be some examples in the manual, though.

Unless I'm interpreting it wrong, the hack for rotating the angle using polar coordinates seems to only work for (0,0).

#import "@preview/fletcher:0.5.1" as fletcher: diagram, node, edge

#let diagram = diagram.with(
  node-shape: circle,
  node-stroke: 1pt,
) 

#let edge = edge.with(
  marks: "-|>"
)

#diagram(
  let polar(r, theta) = (r*calc.cos(theta), r*calc.sin(theta)),
  let theta = 45deg,

  node((0,0), [0,0]),
  edge(polar(0.1, theta), polar(-0.1, theta), bend: 120deg),

  node((1,0), [1,0]),
  edge(polar(1.1, theta), polar(0.9, theta), bend: 120deg),

  node((-1,0), [-1,0]),
  edge(polar(-0.9, theta), polar(-1.1, theta), bend: 120deg),
)

example

I also prefer being able to use node names.

I do think a proper option for changing the angle of a self loop is warranted. We can have it throw an error if both the value is not none and the first and last coordinates are not the same.

@EnzoMoser You can use relative coordinates such as (rel: polar(0.1, theta), to: (x, y)) to create polar displacements about (x, y) instead of the origin (0, 0). You could wrap this in a function like so:

#import "@preview/fletcher:0.5.1" as fletcher: diagram, node, edge

#let diagram = diagram.with(
  node-shape: circle,
  node-stroke: 1pt,
) 

#let edge = edge.with(
  marks: "-|>"
)

#let polar-nudge(origin, theta, r: 0.1) = (
  (rel: (+r*calc.cos(theta), +r*calc.sin(theta)), to: origin),
  (rel: (-r*calc.cos(theta), -r*calc.sin(theta)), to: origin),
)

#diagram(
  let theta = 45deg,

  node((0,0), [0,0]),
  edge(..polar-nudge((0,0), theta), bend: 120deg),

  node((1,0), [1,0]),
  edge(..polar-nudge((1,0), theta), bend: 120deg),

  node((-1,0), [-1,0]),
  edge(..polar-nudge((-1,0), theta), bend: 120deg),
)
Screenshot 2024-10-01 at 11 24 10

I do agree with you, though, that a loop-angle option is warranted. And I think it's a good idea to make it error when it is specified if the edge is not a loop.

I'm afraid I'm not very available to contribute much to fletcher at the moment, though. If you have the capacity, feel free to have a go at this feature.