How to draw in absolute page coordinates in CeTZ?

Hi everyone! I’m writing an article about astronomy, and I’d like a page-wide schematic picture of the solar system on my title page. This is my code so far:

#import "@preview/cetz:0.3.4" as cz
#set page(margin: 0pt)
#cz.canvas(
  length: 2cm,
  {
    import cz.draw: *
    let sunpos = (3,-7)
    let sunrad = .8
    let fontsize = 30pt

    // Drawing the Sun
    circle(name: "sun", sunpos, radius: sunrad, fill: black)
    content("sun", text("S", size: fontsize, fill: white, stroke: white))

    // Drawing the orbits
    circle(sunpos, radius: 2, name: "orbit-mercury")
    circle(sunpos, radius: 3, name: "orbit-venus")
    circle(sunpos, radius: 4, name: "orbit-earth")
    circle(sunpos, radius: 5, name: "orbit-mars")
  }
)
#pagebreak()

This yields the following PDF page:

The problem is that the canvas automatically fits the image to the top and left edges of the page, and so the Sun ends up equidistant from these edges, despite supposedly having coordinates (3,-7). Is it possible to draw shapes where I actually want them to appear on the page, and just crop them if they go out of bounds? Ideally, I would like something like this (this was drawn in Asymptote):

Hi thornoa,

As mentioned in the CeTZ manual, clipping is currently not supported. This means you can’t define a drawing box that restricts where lines are drawn—everything will be rendered regardless of the canvas boundaries.

A workaround is to place your canvas in the center of the page and shift it slightly, so the page edges effectively act as a “clip” for your solar system. You can even set the canvas as the page background:

#import "@preview/cetz:0.3.4" as cz
#set page(margin: 0pt, background: 
  move(
    dx: 5cm,
    dy: 5cm,
    cz.canvas(
      length: 4cm,
      {
        import cz.draw: *
        let sunpos = (3, -7)
        let sunrad = 0.8
        let fontsize = 30pt

        // Drawing the Sun
        circle(name: "sun", sunpos, radius: sunrad, fill: black)
        content("sun", text("S", size: fontsize, fill: white, stroke: white))

        // Drawing the orbits
        circle(sunpos, radius: 2, name: "orbit-mercury")
        circle(sunpos, radius: 3, name: "orbit-venus")
        circle(sunpos, radius: 4, name: "orbit-earth")
        circle(sunpos, radius: 5, name: "orbit-mars")
      }
    )
  )
)
#pagebreak()
#set page(background: none)

Which results in this:

4 Likes

Thank you! That’s a great solution.

1 Like

Yeah, since CeTZ is a package for drawing, it will show a full drawing, so to use negative value to move the drawing into the opposite direction you would have to use move(), although depending on what you need, a place(dx: ..., dy: ..., cetz.canvas()) can be a better solution since it will by default start in the top left corner bypassing page margins if used as background or foreground.