Jollywatt / typst-fletcher

Typst package for drawing diagrams with arrows, built on top of CeTZ.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

tikzcd syntax

jaroeichler opened this issue · comments

Instead of writing

let (src, img, quo) = ((0, 1), (1, 1), (0, 0))
node(src, $G$)
node(img, $im f$)
node(quo, $G slash ker(f)$)
edge(src, img, $f$, "->")
edge(quo, img, $tilde(f)$, "hook-->", label-side: right)
edge(src, quo, $pi$, "->>")

I'd like

G arrow(r, ->, f) arrow(d, ->>, pi) & im(f) \
G slash ker(f) arrow(ur, ->, tilde(f))

In tikzcd the nodes are put into a matrix environment. The arrows are paths from the current node with a given direction to the target node. Is it possible to do something similar here?

It could very well be possible. So far, I haven't quite figured out a syntax that I think is expressive enough. But here are my current thoughts about it:

  1. It would be nice to use a &-aligned math environment for convenient syntax. There is one hacky way I think this could be done:

    #let edge(..options) = metadata((kind: "edge", options: options))
    
    #let eq = $
    G edge("r", ->, f) edge("d", ->>, pi) & im(f) \
    G slash ker(f) edge("ur", ->, tilde(f))
    $
    

    If you look at #eq.fields(), you’ll see that the “dictionary-structs” that represent edges are accessible through the metadata elements, so it’s conceivable that this could be translated into an array of nodes and elements, like a normal diagram.

  2. As yet, I’ve not allowed the use of the nodes’ content where a coordinate is expected, but since this is all in math-mode, it might make sense do to that. That would allow things like:

    #fletcher.diagram($
        A edge(A, C, bend: #50deg) & B & C
    $)
    // would be equivalent to:
    #fletcher.diagram(
        node((0,0), $A$),
        node((0,1), $B$),
        node((0,2), $B$),
        edge((0,0), (0,2), bend: 50deg),
    )
    

    At least then you could form edges between non-adjacent nodes.

Overall, I’m a bit hesitant to make the notation too magical, but I think there’s still something there…

Maybe it's better to use matrix syntax then. At least, it's intended to be used for 2d arrays and should be easier to work with. Also, you should still be able to form edges between non-adjacent nodes

#fletcher.diagram(
  A edge(rr, bend: #50deg), B, C;
)

Another option would be to at least write out rr to (2, 0). This would be closer to the CeTZ sytax of line((), rel: (2, 0)).

This matrix/align notation gets confusing for bigger diagrams though. So maybe it's better to stick to coordinates and shorten the notation there. But I don't think calling nodes by their content is a good idea. You often have maps $A \to A$ or objects that don't make a good variable name like $H^i(X, R)$.

These are good comments. I’ve had a go at making something like this on the tikz-style branch I just added.

On that branch, you can do this:

#fletcher.diagram(axes: (ltr, ttb), $
  G edge(f, ->) edge(#(0,1), pi, ->>) & im(f) \
  G slash ker(f) edge(#(1,0), tilde(f), "hook'-->")
$)

instead of this:

#fletcher.diagram(axes: (ltr, ttb),
  node((0,0), $G$),
  edge((0,0), (1,0), $f$, "->"),
  edge((0,0), (0,1), $pi$, "->>"),
  node((1,0), $im(f)$),
  node((0,1), $G slash ker(f)$),
  edge((0,1), (1,0), $tilde(f)$, "hook'-->")
)

It’s still rough around the edges. (I might have to make axes: (ltr, ttb) the default, instead of (ltr, btt).)

The changes I made were:

  • Interpreting edge((x2, y2), ..opts) as edge(from: auto, to: (x2, y2), ..opts), that is, if you specify one coordinate for an edge, that is interpreted as its tip, and its base is whatever node coordinate was last specified.
  • Even more magically, interpreting edge(..opts) as edge(from: auto, to: auto, ..opts), where to is the coordinate of the next node specified. I’m not sure if this is best, because you could also say that to is the coordinate of the previous node, and from the second-previous.
  • Interpreting sym.arrow and friends as mark shorthands, to allow them to be written directly in math-mode like edge(->) instead of as strings edge("->"). For more complex arrows, you can always use a string.
  • And of course, allowing &-separated equations to specify nodes on a grid.

Things that are tricky:

  • Both of you suggested r as the most natural notation for (Δx, Δy) = (+1, 0). I don’t know exactly how that would work, since in math-mode edge(r, ..) means #edge($r$, ..). It seems like a hack to whitelist some single-letter math variables and interpret them as coordinates. Especially if coordinates are optional, that could lead to ambiguity.
  • I could export a whole bunch of coordinates, e.g., r = (rel: (1, 0)), ru = (rel: (1, 1)), u = (rel: (0, 1)), ……… but that's a lot of namespace pollution. Might be better to just have edge("r", ..). But then edge("r", "o->") would be hard to parse, with both arguments as strings. Etcetera. In the meantime, you have to use #(x, y).

Features I’m aiming for:

  • All the notational “shortcuts” like omitting coordinates should make sense and work in code-mode as well as math-mode.
  • You should be able to mix and match code-mode and math-mode in the same diagram, since both have advantages. For example, you can declare variables, have loops diagram(for x in range(4) { edge((0,0), (x,1), "->") }) etc in code-mode, whereas math-mode just gives you a grid and less $s.

I welcome any ideas!

This is done in v0.4.0 😃

You can now do:

G edge("r", ->, f) edge("d", ->>, pi) & im(f) \
G slash ker(f) edge("ur", ->, tilde(f))