Jollywatt / typst-fletcher

Typst package for drawing diagrams with arrows, built on top of CeTZ.
MIT License
341 stars 6 forks source link

add support for nodes spanning multiple grid cells #22

Closed PanieriLorenzo closed 5 months ago

PanieriLorenzo commented 6 months ago

I would like to draw nodes that can span multiple grid cells. When I change the size of a node, it makes the cells bigger rather than spanning multiple cells. I get that this behavior makes sense in a lot of cases, but sometimes I wish I could override this behavior somehow.

My specific use-case is drawing block diagrams for DSP algorithms. I sometimes have blocks with a lot of inputs and would like to draw them as parallel arrows left-to-right, like parallel "lanes", entering into a single blocks spanning multiple "lanes".

The Typst package Quill can do something similar, but it's specifically for drawing quantum circuits, and doesn't really fit my use case. Here's a diagram similar to what I'm thinking of, from Quill's docs:

image

PanieriLorenzo commented 6 months ago

I managed to do it using the render function, but it's still a bit of a hassle. Essentially just made a bounding box and the nodes in said box transparent.

image

Jollywatt commented 6 months ago

It's worth mentioning that you can get around this by dividing the coordinates by the node's span, so that in the quantum circuit example the y-coordinates are all -0.33, 0, +0.33 instead of -1, 0, +1. But that's kinda a hack.

Otherwise, I could maybe add:

Open to ideas:)

PanieriLorenzo commented 6 months ago

Seeing as my workaround is essentially what #5 describes, it sounds like a good idea :) I used cetz in the render function to draw a box around nodes with a certain color, then I made those same nodes invisible. Perhaps if it was easier to add some sort of arbitrary tag on nodes, they could be grouped by these tags?

Something like:

// ...
node((0,0), "foo", tag: "my-tag"),
node((0,1), "bar", tag: "my-tag"),
group(by: "my-tag", /* etc */),
// ...
Jollywatt commented 6 months ago

Perhaps if it was easier to add some sort of arbitrary tag on nodes, they could be grouped by these tags?

You're in luck - version 0.4.3 introduced node names, which don't have to be unique. Of course, that only works if you weren't already giving them other names.

And I agree - since this kind of solution has come up twice now, I think it should be built in!

Here's an example:

```typ #import "@preview/fletcher:0.4.3" as fletcher: diagram, node, edge, hide // return the four corner points of a rectangle #let corner-points(center, size) = { let (x, y) = center let (w, h) = size ( (x - w/2, y - h/2), (x - w/2, y + h/2), (x + w/2, y - h/2), (x + w/2, y + h/2), ) } // draw a bounding rectangle around nodes #let enclose-nodes(nodes, ..args) = { let points = nodes.map(node => { corner-points(node.final-pos, node.size) }).join() let (center, size) = fletcher.bounding-rect(points) fletcher.cetz.draw.content( center, rect( width: size.at(0), height: size.at(1), ..args ) ) } #diagram( node-stroke: 1pt, node((0, 0), [A]), edge("r", "=>"), node((0, 1), [XYZ]), edge("r", "->"), hide({ node((1, 0), width: 1cm, height: 1cm, name: ) node((1, 1), width: 1cm, height: 1cm, name: ) }), node((1,0.5), $Sigma$, stroke: none), node((2, 0), [ABC]), edge("l", "<="), node((2, 1), [1234567890]), edge("l", "<-"), render: (grid, nodes, edges, options) => { // lookup nodes by name let group = nodes.filter(node => node.name == ) fletcher.cetz.canvas({ // draw the diagram as usual fletcher.draw-diagram(grid, nodes, edges, debug: options.debug) // draw a custom group rectangle enclose-nodes(group) }) }) ```
PanieriLorenzo commented 6 months ago

that's perfect, makes it much more readable, thanks!

Jollywatt commented 5 months ago

On the main branch, you can now do this:

#diagram(
  node-stroke: .7pt,
  edge-stroke: .7pt,
  spacing: 10pt,

  node((0,1), [volume]),
  node((0,2), [gain]),
  node((0,3), [fine]),

  edge((0,1), "r", "->", snap-to: (auto, <bar>)),
  edge((0,2), "r", "->", snap-to: (auto, <bar>)),
  edge((0,3), "r", "->", snap-to: (auto, <bar>)),

  // a node that encloses/spans multiple grid points,
  node($Sigma$, enclose: ((1,1), (1,3)), inset: 10pt, name: <bar>),

  edge((1,1), "r,u", "->", snap-to: (<bar>, auto)),
  node((2,0), $ times $, radius: 8pt),
)

…to get this:

Screen Shot 2024-04-08 at 9 00 59 PM

Future things to consider:

PanieriLorenzo commented 5 months ago

amazing thanks!