Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern-filled shapes #805

Open
janosh opened this issue Jan 27, 2025 · 6 comments
Open

Pattern-filled shapes #805

janosh opened this issue Jan 27, 2025 · 6 comments

Comments

@janosh
Copy link
Contributor

janosh commented Jan 27, 2025

i would like to add diagonal lines to a rectangle representing a wall to reproduce this TikZ figure:

Image

here's what i currently have

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

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

#let lennard-jones(x, A, B, alpha) = {
  -A * calc.exp(-alpha * x) + B * calc.exp(-2 * alpha * x)
}

#canvas({
  import draw: line, content, rect, on-layer

  let arrow-style = (mark: (end: ">", fill: black, scale: 0.7))
  let wall-width = 0.5

  // Draw axes
  line((-0.1, 0), (6.2, 0), ..arrow-style, name: "x-axis")
  line((0, -0.7), (0, 4), ..arrow-style, name: "y-axis")

  // Add axis labels
  content((rel: (-0.2, 0.3), to: "x-axis.end"), $x$, name: "x-label")
  content((rel: (0.5, 0), to: "y-axis.end"), $U(x)$, name: "y-label")

  // Draw wall with hatching
  on-layer(
    1, // draw wall above pressure lines
    rect(
      (0, 0),
      (wall-width, 3.6),
      fill: rgb("eee"),
      pattern: "north-east-lines",
      stroke: 0.75pt,
    ),
  )

  // Draw potential curves
  let samples = 100
  let dx = (6 - wall-width) / samples

  // First curve (blue)
  let (A1, B1, alpha1) = (10, 25, 1)

  for ii in range(samples - 1) {
    let x1 = wall-width + ii * dx
    let x2 = x1 + dx
    let y1 = lennard-jones(x1, A1, B1, alpha1)
    let y2 = lennard-jones(x2, A1, B1, alpha1)
    line(
      (x1, y1),
      (x2, y2),
      stroke: blue,
    )
  }
  content((4.2, -0.6), text(fill: blue)[$1 / alpha << ell$], name: "alpha-label")

  // Second curve (orange)
  let (A2, B2, alpha2) = (15, 120, 3)

  for ii in range(samples - 1) {
    let x1 = wall-width + ii * dx
    let x2 = x1 + dx
    let y1 = lennard-jones(x1, A2, B2, alpha2)
    let y2 = lennard-jones(x2, A2, B2, alpha2)
    line(
      (x1, y1),
      (x2, y2),
      stroke: orange,
      name: "orange-line-" + str(ii),
    )
  }
  content((4.2, 0.4), text(fill: orange)[$1 / alpha << ell$], name: "alpha-label")
})
@johannes-wolf
Copy link
Member

johannes-wolf commented Jan 27, 2025

You can use Typst' pattern (or modpattern package):
It does not look perfect, as the pattern gets clipped but for thin lines it looks ok:

#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((.2cm, .2cm), {
  std.line(start: (0%, 100%), end: (100%, 0%))
})

#let lennard-jones(x, A, B, alpha) = {
  -A * calc.exp(-alpha * x) + B * calc.exp(-2 * alpha * x)
}

#canvas({
  import draw: line, content, rect, on-layer

  let arrow-style = (mark: (end: ">", fill: black, scale: 0.7))
  let wall-width = 0.5

  // Draw axes
  line((-0.1, 0), (6.2, 0), ..arrow-style, name: "x-axis")
  line((0, -0.7), (0, 4), ..arrow-style, name: "y-axis")

  // Add axis labels
  content((rel: (-0.2, 0.3), to: "x-axis.end"), $x$, name: "x-label")
  content((rel: (0.5, 0), to: "y-axis.end"), $U(x)$, name: "y-label")

  // Draw wall with hatching
  on-layer(
    1, // draw wall above pressure lines
    rect(
      (0, 0),
      (wall-width, 3.6),
      fill: hatched,
      pattern: "north-east-lines",
      stroke: 0.75pt,
    ),
  )

  // Draw potential curves
  let samples = 100
  let dx = (6 - wall-width) / samples

  // First curve (blue)
  let (A1, B1, alpha1) = (10, 25, 1)

  for ii in range(samples - 1) {
    let x1 = wall-width + ii * dx
    let x2 = x1 + dx
    let y1 = lennard-jones(x1, A1, B1, alpha1)
    let y2 = lennard-jones(x2, A1, B1, alpha1)
    line(
      (x1, y1),
      (x2, y2),
      stroke: blue,
    )
  }
  content((4.2, -0.6), text(fill: blue)[$1 / alpha << ell$], name: "alpha-label")

  // Second curve (orange)
  let (A2, B2, alpha2) = (15, 120, 3)

  for ii in range(samples - 1) {
    let x1 = wall-width + ii * dx
    let x2 = x1 + dx
    let y1 = lennard-jones(x1, A2, B2, alpha2)
    let y2 = lennard-jones(x2, A2, B2, alpha2)
    line(
      (x1, y1),
      (x2, y2),
      stroke: orange,
      name: "orange-line-" + str(ii),
    )
  }
  content((4.2, 0.4), text(fill: orange)[$1 / alpha << ell$], name: "alpha-label")
})

@janosh
Copy link
Contributor Author

janosh commented Jan 27, 2025

looks great, thank you! are there plans to in-house this? i.e. should i leave this issue open?

@johannes-wolf
Copy link
Member

looks great, thank you! are there plans to in-house this? i.e. should i leave this issue open?

You mean providing some patterns? I think we could add something like cetz.patterns.lines.with(angle: ..., stroke: ...). This would be (more) useful as a non-cetz library, since it is not cetz specific, imho.

@janosh
Copy link
Contributor Author

janosh commented Feb 2, 2025

not sure if this is an issue in cetz, modpattern or my code. i tried to use the same approach for filling a shape with diagonal lines as you showed me above, but this time for circles representing propagator vertices in Feynman diagrams. instead of diagonal lines, i just get a black fill. i tried changing the stroke width and line separation but makes no difference.

Image

#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(
  (.2cm, .2cm),
  std.line(start: (0%, 100%), end: (100%, 0%), stroke: 0.5pt),
)

#canvas({
  import draw: line, content, circle, rect, group

  // Define styles and constants
  let unit = 1
  let vertex-radius = 0.2 * unit
  let cross-radius = 0.15 * unit
  let ext-len = 2 * unit

  // Helper function for cross markers
  let cross(pos, label: none, label-offset: 2, rel-label: (0, -0.5)) = {
    let rad = cross-radius
    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),
        eval(label, mode: "math"),
      )
    }
  }

  // Helper function for hatched vertices
  let vertex(pos, label: none, rel-label: (0.35, 0.35)) = {
    circle(pos, radius: vertex-radius, fill: hatched, stroke: black)

    if label != none {
      content(
        (rel: rel-label, to: pos),
        eval(label, mode: "math"),
      )
    }
  }

  // Diagram 1
  group(
    name: "diagram1",
    {
      // Main circle
      circle((0, 0), radius: unit, stroke: 1pt)

      // External lines
      line((-ext-len, 0), (-unit, 0), stroke: 1pt)
      line((unit, 0), (ext-len, 0), stroke: 1pt)

      // Cross marker
      cross((0, unit), label: "partial_k R_k")

      // Vertices
      vertex((-unit, 0), label: "Gamma_k^(3)", rel-label: (-0.35, 0.35))
      vertex((unit, 0), label: "Gamma_k^(3)")
    },
  )

  // Diagram 2
  group(
    name: "diagram2",
    {
      // Move right by 5 units
      let offset = (5, 0)

      // Main circle
      circle((offset.at(0), 0), radius: unit, stroke: 1pt)

      // External lines
      line((-ext-len + offset.at(0), 0), (-unit + offset.at(0), 0), stroke: 1pt)
      line((unit + offset.at(0), 0), (ext-len + offset.at(0), 0), stroke: 1pt)

      // Cross marker
      cross((offset.at(0), -unit), label: "partial_k R_k", rel-label: (0, 0.5))

      // Vertices
      vertex((-unit + offset.at(0), 0), label: "Gamma_k^(3)", rel-label: (-0.35, 0.35))
      vertex((unit + offset.at(0), 0), label: "Gamma_k^(3)")
    },
  )

  // Diagram 3
  group(
    name: "diagram3",
    {
      // Move right by 10 units
      let offset = (10, 0)

      // Main circle
      circle((offset.at(0), 0), radius: unit, stroke: 1pt)

      // External line
      line(
        (-ext-len + offset.at(0), -unit),
        (ext-len + offset.at(0), -unit),
        stroke: 1pt,
      )

      // Cross marker
      cross((offset.at(0), unit), label: "partial_k R_k")

      // Vertex
      vertex((offset.at(0), -unit), label: "Gamma_k^(4)")
    },
  )
})

@johannes-wolf
Copy link
Member

johannes-wolf commented Feb 2, 2025

I've tested the code and it gives the following output, both web and local installation (Typst 12 + dev, PDF + PNG):
Image

@janosh
Copy link
Contributor Author

janosh commented Feb 2, 2025

oh, seems to have been a temporary glitch. i was looking at it with tinymist's Preview feature. glad i took that screenshot to be sure i wasn't just imagining it. i previewed the file again and now it looks correct 🤷
thanks for the quick response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants