Jollywatt / typst-fletcher

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

Overlay or section node #5

Closed jomaway closed 2 months ago

jomaway commented 5 months ago

I tried to create a node which goes around multiple other nodes and edges, to show that those nodes are belonging together. But it always extended the edges so it didn't reach the other node.

Bildschirmfoto vom 2024-02-01 10-02-06

It would be nice if there was a kind of node or section or overlay or whatever you want to call it to add this feature. Or maybe i just missing something.

Jollywatt commented 5 months ago

It's true that this feature isn't really supported yet. However, you can still use cetz to draw things with complete control (as very briefly mentioned in the manual under “CeTZ integration”)

But this is fairly advanced usage, so here's a demonstration that you might adapt to your use case. Below, all the nodes of a certain colour are selected and put in a bounding box, drawn directly with cetz. You can move the nodes or change their colours from in-group to out-group and the bounding box updates.

#import "@preview/fletcher:0.4.0" as fletcher: node, edge, vector, cetz

#let get-bounding-box(points) = {
  let (xs, ys) = array.zip(..points)
  let p1 = (calc.min(..xs), calc.min(..ys))
  let p2 = (calc.max(..xs), calc.max(..ys))
  (
    center: vector.scale(vector.add(p1, p2), 0.5),
    size: vector.sub(p2, p1)
  )
}

// draw a bounding rectangle around nodes
#let enclose-nodes(nodes) = {
  let points = nodes.map(node => node.real-pos)
  let (center, size) = get-bounding-box(points)

  let clearance = 8mm
  cetz.draw.content(
    center,
    rect(
      width: size.at(0) + 2*clearance,
      height: size.at(1) + 2*clearance,
      stroke: 0.5pt + red,
      radius: clearance,
    )
  )
}

#let in-group = orange.lighten(60%)
#let out-group = blue.lighten(60%)

#fletcher.diagram(
  node((-1,0), `R0`, fill: out-group, radius: 5mm),
  edge("o-o"),
  node((0, 0), `R1`, fill: in-group, radius: 5mm),
  edge("o-o"),
  node((1,.5), `R2`, fill: in-group, radius: 5mm),
  edge("o-o"),
  node((1,-1), `R3`, fill: in-group, radius: 5mm),

  render: (grid, nodes, edges, options) => {
    // lookup nodes by color
    let group = nodes.filter(node => node.fill == in-group)
    cetz.canvas({
      // draw the diagram as usual
      fletcher.draw-diagram(grid, nodes, edges, options)
      // draw a custom group rectangle
      enclose-nodes(group)
    })
  }
)

I suspect that this might be a common use case, so this could be added to fletcher itself…

jomaway commented 5 months ago

Thanks for the quick solution.

Jollywatt commented 2 months ago

As of 6916777724c11569668ee99a5aa4858b591f9543, which added the enclose option to node(), you can now do this:

#diagram({
    let c = rgb(..orange.components().slice(0,3), 50%)
    edge("l", "o-o")
    node((0,0), `R1`, radius: 5mm, fill: c)
    edge("o-o")
    node((1,0), `R2`, radius: 5mm, fill: c)
    edge("u", "o-o")
    edge("o-o")
    node(`L7`, enclose: ((0,0), (1,0)), stroke: red + 0.5pt, extrude: (0,2))
})

…to get this:

Screen Shot 2024-04-08 at 9 25 33 PM