How to draw hetu?

Could you please help to use fletcher/cetz to draw this picture?

I agree with How to draw bagua with fletcher/CeTZ? - #2 by nleanba, but just for fun:

Code
#let r = 10pt
#set page(height: auto, width: auto, margin: 2 * r)

// Debug:
// #set grid(stroke: green + 0.5pt)

#let span-x(n, fill: white) = grid(
  columns: n,
  column-gutter: r,
  row-gutter: -2 * r,
  grid.cell(colspan: n, box(height: 2 * r, line(length: n * 3 * r - r))),
  ..range(n).map(_ => circle(radius: r, fill: fill, stroke: black)),
)
#let span-y(n, fill: white) = grid(
  columns: 2,
  column-gutter: -2 * r,
  box(width: 2 * r, line(length: n * 3 * r - r, angle: 90deg)),
  grid(
    columns: 1,
    row-gutter: r,
    ..range(n).map(_ => circle(radius: r, fill: fill, stroke: black)),
  ),
)
#let the-center = box(
  stroke: black,
  inset: -r,
  {
    set align(center)
    set par(spacing: 0pt)
    span-x(5, fill: black)
    v(2 * r)
    span-y(3)
    v(-5 * r)
    span-x(3)
    v(5 * r)
    span-x(5, fill: black)
  },
)

#grid(
  columns: (1, 1, 7, 1, 1).map(x => x * 2 * r),
  align: center + horizon,
  column-gutter: 2 * r,

  grid.cell(colspan: 1, {}),
  grid.cell(colspan: 3, span-x(7)),
  grid.cell(colspan: 1, {}),
  grid.cell(colspan: 5, v(2 * r)),

  grid.cell(colspan: 2, {}),
  span-x(2, fill: black),
  grid.cell(colspan: 2, {}),

  span-y(8, fill: black),
  span-y(3),
  the-center,
  span-y(4, fill: black),
  span-y(9),

  grid.cell(colspan: 2, {}),
  span-x(1),
  grid.cell(colspan: 2, {}),

  grid.cell(colspan: 5, v(2 * r)),
  grid.cell(colspan: 1, {}),
  grid.cell(colspan: 3, span-x(6, fill: black)),
  grid.cell(colspan: 1, {}),
)

I use grids instead of CeTZ, because itโ€™s easier to align items without calculating coordinates. The following is a debug version showing green strokes of the grids.

4 Likes

If you want to use CeTZ this might be a good start:

#import "@preview/cetz:0.4.1": *

#set page(width: auto, height: auto)


#canvas(length: 1cm, {
  // Import all drawing functions from the draw module
  import draw: *

  // Define a function to draw a line of circles between two points.
  // If 'filled' is true, the circles are black; otherwise, they are white.
  let circle-line(start, stop, filled: false) = {
    // Check if the line is vertical (x-coordinates are equal)
    if (start.at(0) == stop.at(0)) {
      // Find the minimum y-coordinate to start from
      let start-y = calc.min(start.at(1), stop.at(1))
      // Calculate the number of steps (distance between y-coordinates)
      let steps = calc.abs(start.at(1) - stop.at(1))
      // Draw the main line between start and stop
      line(start, stop)
      // Draw circles at each integer y-position along the line
      for i in range(steps + 1) {
        circle(
          (start.at(0), start-y + i),
          radius: 0.33cm,
          fill: if filled { black } else { white }
        )
      }
    } else {
      // The line is horizontal (y-coordinates are equal)
      // Find the minimum x-coordinate to start from
      let start-x = calc.min(start.at(0), stop.at(0))
      // Calculate the number of steps (distance between x-coordinates)
      let steps = calc.abs(start.at(0) - stop.at(0))
      // Draw the main line between start and stop
      line(start, stop)
      // Draw circles at each integer x-position along the line
      for i in range(steps + 1) {
        circle(
          (start-x + i, start.at(1)),
          radius: 0.33cm,
          fill: if filled { black } else { white }
        )
      }
    }
  }

  circle-line((-1, 0), (1, 0))
  circle-line((0, -1), (0, 1))
  
  circle-line((-2, 3), (2, 3), filled: true)
  circle-line((-2, -3), (2, -3), filled: true)
  
  line((-2, 3), (-2, -3))
  line((2, 3), (2, -3))
})

2 Likes

Super cool! Thank you!

1 Like