cetz-package / cetz

CeTZ: ein Typst Zeichenpaket - A library for drawing stuff with Typst.

Home Page:https://cetz-package.github.io

Repository from Github https://github.comcetz-package/cetzRepository from Github https://github.comcetz-package/cetz

Marks at specific angles of circle

janosh opened this issue · comments

admittedly, this diagram is a bit of a mess right now:

Image

i'm trying to create marks at 6 points along a circle to achieve this output. i changed from circle to arc since circle doesn't seem to support arc at all. but even with arc, i only managed to place marks at start and end. i was looking through this PR but didn't spot an example of intermediate marks but might have missed it

#import "@preview/cetz:0.3.2": canvas, draw
#import "@preview/modpattern:0.1.0": modpattern

#set page(width: auto, height: auto, margin: 8pt)

#let hatched = modpattern(
  (.1cm, .1cm),
  std.line(start: (0%, 100%), end: (100%, 0%), stroke: 0.5pt),
  background: white,
)

#canvas({
  import draw: line, content, circle, arc, group, translate, mark, rotate

  // Define styles and constants
  let radius = 1.25 // \lrad in original
  let small-rad = 0.1 * radius // \srad in original
  let med-rad = 0.15 * radius // \mrad in original
  let arrow-style = (
    mark: (end: "stealth", fill: black, scale: .5),
    stroke: (paint: black, thickness: 0.75pt),
  )

  // Helper functions
  let cross(pos, label: none, rel-label: (0, 5pt)) = {
    content(pos, text(size: 16pt)[$times.circle$], stroke: none, fill: white, frame: "circle", padding: -2.5pt)
    if label != none {
      content((rel: rel-label, to: pos), $#label$)
    }
  }

  let dressed-vertex(pos, label: none, rel-label: none) = {
    circle(pos, radius: small-rad, fill: hatched, stroke: black)
    if label != none {
      content(
        if rel-label != none { (rel: rel-label, to: pos) } else { pos },
        label,
      )
    }
  }

  let momentum-label(pos, num) = {
    content(pos, $p_#num$, size: 8pt)
  }

  // Draw first diagram
  group({
    // Main loop with momentum labels
    arc(
      (-radius, 0),
      start: 0deg,
      stop: 360deg,
      radius: radius,
      stroke: 1pt,
      name: "loop",
      mark: ("mid": "stealth", fill: black, scale: .5),
      anchor: "arc-center",
    )

    // Add momentum arrows around loop
    for (ii, pos) in ((6, "0.0625"), (1, "0.1875"), (2, "0.3125"), (3, "0.4375"), (4, "0.625"), (5, "0.875")) {
      let percent = str(calc.round(float(pos) * 100, digits: 2)) + "%"
      let angle = float(pos) * 360
      let label-angle = (angle - 3) * 1deg
      let label-pos = (
        0.7 * radius * calc.cos(label-angle),
        0.7 * radius * calc.sin(label-angle),
      )
      momentum-label(label-pos, ii)
      mark(
        "loop." + percent, // Start from the loop edge
        (0, 0),
        symbol: "stealth",
        name: "p" + str(ii),
      )
    }

    // Add dressed vertices and cross
    cross((0, radius), label: $partial_k R_(k,i j) (p_1,p_2)$, rel-label: (0, 0.5))
    dressed-vertex(
      (radius * calc.cos(135deg), radius * calc.sin(135deg)),
      label: $G_(k,j k)(p_2,p_3)$,
      rel-label: (-1.2, 0.2),
    )
    dressed-vertex(
      (radius * calc.cos(45deg), radius * calc.sin(45deg)),
      label: $G_(k,n i)(p_6,p_1)$,
      rel-label: (1.2, 0.2),
    )
    dressed-vertex(
      (0, -radius),
      label: $G_(k,l m) (p_4,p_5)$,
      rel-label: (0, -0.6),
    )

    // External lines and labels
    let ext-len = 2 * radius
    line((-ext-len, 0), (-radius, 0), stroke: 1pt)
    line((radius, 0), (ext-len, 0), stroke: 1pt)
    content((-1.4 * radius, -0.3), $phi_a$)
    content((1.4 * radius, -0.3), $phi_b$)

    // External momentum arrows
    line((-1.9 * radius, 0.15), (-1.25 * radius, 0.15), ..arrow-style, name: "q1-line")
    content("q1-line", $q_1$, anchor: "south", padding: (0, 0, 2pt))
    line((1.25 * radius, 0.15), (1.9 * radius, 0.15), ..arrow-style, name: "q2-line")
    content("q2-line", $q_2$, anchor: "south", padding: (0, 0, 2pt))

    // Vertex labels with connecting lines
    let label-style = (stroke: gray + 0.4pt)
    content((-2.2 * radius, -1.2 * radius), $Gamma_(k,a k l)^(3)(q_1,p_3,-p_4)$)
    line((-1.6 * radius, -1 * radius), (-radius, 0), ..label-style)
    dressed-vertex((-radius, 0))

    content((2.3 * radius, -1.2 * radius), $Gamma_(k,b m n)^(3)(-q_2,p_5,-p_6)$)
    line((1.6 * radius, -1 * radius), (radius, 0), ..label-style)
    dressed-vertex((radius, 0))
  })
})

You can place marks at a percentage of the path:

#import "@preview/cetz:0.3.2": canvas, draw

#set page(width: auto, height: auto, margin: 8pt)

#canvas({
  import draw: *

  set-style(arc: (mark: (width: .2, length: .075)))
  arc((0, 0), name: "c", start: 0deg, stop: 360deg, anchor: "origin", mode: "PIE",
    mark: (
      end: (
        (pos: 25% - 25%/2, symbol: "barbed"),
        (pos: 25% + 25%/2, symbol: "barbed"),
        (pos: 50% + 25%/4, symbol: "barbed"),
        (pos: 75% - 25%/4, symbol: "barbed"),
        (pos: 75% + 25%/4, symbol: "barbed"),
        (pos: 75% + 25%/4*3, symbol: "barbed"),
      ),
      // Do not shorten the arc
      shorten-to: none,
    ))
})

(I discovered some bugs with arc anchors for 0-360 arcs.)

I guess the better alternative is to place marks on-top of a circle and use angle anchors:

#import "@preview/cetz:0.3.2": canvas, draw

#set page(width: auto, height: auto, margin: 8pt)

#canvas({
  import draw: *

  circle((0, 0), name: "c")

  for angle in (22.5deg, 90deg - 22.5deg, 90deg + 22.5deg, 180deg - 22.5deg, 225deg, 315deg) {
    mark(symbol: "barbed", width: .25, length: .1,
      (name: "c", anchor: angle),
      (name: "c", anchor: angle + .1deg))
  }
})

excellent, thank you very much! i cleaned up the code a little. final result looks great as usual.

i keep discovering new features in CeTZ. impressive how close it has already come to feature parity with TikZ. definitely surpassed it in developer experience for my use cases :)