Can someone guide me towards creating a honeycomb like below, where I can add labels both outside and inside the shapes? I do not need any colors or fills for the moment.

Can someone guide me towards creating a honeycomb like below, where I can add labels both outside and inside the shapes? I do not need any colors or fills for the moment.

Are you aware of the polygon function and CeTZ packageâs cetz.draw.line()? It should be relatively easy to use either to draw this (CeTZ is probably a bit easier), using a bit of trigonometry. For this, youâll also want to use the calc.sin() and calc.cos() functions to determine coordinates.
Basically:
cx, cy), and a âradiusâx = cx + cos(angle)*radius and y = cy + sin(angle)*radius (the Wikipedia article on the unit circle may help making it more clear why this works)cx2 = cx1 + 1.5*radius, which is pretty easy to see. cy2 is the same as the first hexagonâs lower cornersâ y.cx.I think this should get you going. Feel free to ask follow-up questions, and when youâre done please post your result so that others can easily reuse it :)
Thank you @SillyFreak, I was not aware. I will try your suggestion, and return with the result.
#import "@preview/cetz:0.2.2"
#set page(height: auto, width: auto, margin: 0pt)
#let background-color = gray
#let fill-color = white
#let stroke-color = black
#let labels = ("STR", "DEX", "END", "INT", "EDU", "SOC")
// #let labels = ("STR", "DEX", "END") // This also works!
#let label-formatter(label) = text(font: "Liberation Sans", emph(strong(label)))
#let d = 1.8cm // The diameter of the circumcircle of the hexagon.
#let r = d / 2 // The radius of the circumcircle of the hexagon.
// The height of one of 6 triangles inside hexagon, i.e., half-height of hexagon.
#let h = r * calc.sqrt(3) / 2
// _____
// /| \
// / | \
//|\ | /
//| \|____/
//| |
//|ââ|
// Small height (of a small triangle inside the hexagon).
#let hh = calc.sqrt(r.cm() * r.cm() - h.cm() * h.cm()) * 1cm
#let hexagon-shape = polygon.regular(
vertices: 6,
size: d,
fill: fill-color,
stroke: stroke-color,
)
#let hexagon(rel-pos, name, label-pos, label) = {
import cetz.draw: *
let rel-to = if name == 1 { () } else { str(name - 1) }
let position = (rel: rel-pos, to: rel-to)
content(position, name: str(name), hexagon-shape)
let text-offset = 0.2
let text-offset = text-offset * if label-pos == top { 1 } else { -1 }
let side = if label-pos == top { "north" } else { "south" }
content(
(rel: (0, text-offset), to: str(name) + "." + side),
label-formatter(label),
)
}
#let hexagon-right-top(name, label) = {
hexagon((hh + r, h), name, top, label)
}
#let hexagon-right-bottom(name, label) = {
hexagon((hh + r, -h), name, bottom, label)
}
#let img = cetz.canvas(background: background-color, {
import cetz.draw: *
let last = labels.len()
// Draw each hexagon and its label.
for (i, label) in labels.enumerate() {
if calc.odd(i + 1) {
hexagon-right-top(i + 1, label)
} else {
hexagon-right-bottom(i + 1, label)
}
}
// Draw background horizontal stripe.
on-layer(-1, {
let extend = 1
line(
(rel: (-extend, 0), to: "1.west"),
if calc.even(labels.len()) {
(rel: (extend, 0), to: str(last) + ".north-east")
} else { () },
(rel: (extend, 0), to: str(last) + ".east"),
if calc.odd(labels.len()) {
(rel: (extend, 0), to: str(last) + ".south-east")
} else { () },
(rel: (-extend, 0), to: "1.south-west"),
close: true,
fill: fill-color,
stroke: fill-color,
)
})
})
// Add vertical padding.
#let img = block(inset: (y: 2mm), fill: background-color, img)
#img

I tried to optimize the code, but I have very little overall cetz experience. Maybe @jwolf or someone else more experienced can get a cleaner/shorter solution, but I think the algorithm more or less will be the same.
Thanks a lot for contributing @Andrew your solution seems slightly simpler than the one I came up with ![]()
But now that I have spent all that time Iâm going to post my solution anyways.
First I tried the solution with sin() and cos() that @SillyFreak posted, but I think my math was way off because I never ended up with a real hexagon. But instead someone had posted the vertices on a webpage: (1, 0), (1/2, sqrt3 / 2), (-1/2, sqrt3 / 2), (-1, 0), (-1/2, -sqrt3 / 2), (1/2, -sqrt3 / 2)
So with this I succeeded.
#import "@preview/cetz:0.2.2"
#set page(
paper: "a3",
flipped: true,
)
#set text(
font: "IBM Plex Sans",
)
#cetz.canvas(
length: 20pt,
{
import cetz.draw: *
// grid((-1.5,-3), (9,2), stroke: luma(240), step: .25)
// grid((-1.5, -3), (9, 2), stroke: luma(160), step: 5)
let centery = 0
let centerx = 0
let angle = 60
let radius = 0.1
// Vertices at
// (1, 0)
// (1/2, sqrt3 / 2)
// (-1/2, sqrt3 / 2)
// (-1, 0)
// (-1/2, -sqrt3 / 2)
// (1/2, -sqrt3 / 2)
let hex(x, y, property, level) = {
let ax = 1
let ay = 0
let a = (x + ax, y + ay)
let bx = 1 / 2
let by = calc.sqrt(3) / 2
let b = (x + bx, y + by)
let cx = -(1 / 2)
let cy = by
let c = (x + cx, y + cy)
let dx = -1
let dy = 0
let d = (x + dx, y + dy)
let ex = -(1 / 2)
let ey = -by
let e = (x + ex, y + ey)
let fx = 1 / 2
let fy = -by
let f = (x + fx, y + fy)
// circle(a, radius: radius, stroke: 1pt + blue)
// circle(b, radius: radius, stroke: blue)
// circle(c, radius: radius, stroke: blue)
// circle(d, radius: radius, stroke: purple)
// circle(e, radius: radius, stroke: purple)
// circle(f, radius: radius, stroke: purple)
line(a, b, c, d, e, f, stroke: 1.5pt, close: true)
content((x + 0,y + 0), [#level])
content((x + 0,y + 1.2), [#text(style: "italic", weight: "bold", [#property])])
}
hex(0,0, "STR", "10")
hex(1.5,-calc.sqrt(3)/2, "DEX", "10")
hex(3,0, "END", "10")
hex(4.5,-calc.sqrt(3)/2, "INT", "10")
hex(6.0,0, "EDU", "10")
hex(7.5,-calc.sqrt(3)/2, "SOC", "10")
})
Youâre welcome. I can make my solution even simpler if you donât need documentation, readability, and flexibility (but I donât recommend it because it lacks all of that):
#set page(height: auto, width: auto, margin: 0pt)
#import "@preview/cetz:0.2.2"
#let d = 1.8cm
#let hexagon(rel-pos, name, label) = {
cetz.draw.content(
(rel: rel-pos, to: if name == 1 { () } else { str(name - 1) }),
name: str(name),
polygon.regular(vertices: 6, size: d, fill: white, stroke: black),
)
let text-offset = 0.2 * if calc.odd(name) { 1 } else { -1 }
let side = if calc.odd(name) { "north" } else { "south" }
cetz.draw.content(
(rel: (0, text-offset), to: str(name) + "." + side),
text(font: "Liberation Sans", emph(strong(label))),
)
}
#block(inset: (y: 2mm), fill: gray, cetz.canvas(background: gray, {
let r = d / 2
let h = r * calc.sqrt(3) / 2
let hh = calc.sqrt(r.cm() * r.cm() - h.cm() * h.cm()) * 1cm
hexagon((hh + r, h), 1)[STR]
hexagon((hh + r, -h), 2)[DEX]
hexagon((hh + r, h), 3)[END]
hexagon((hh + r, -h), 4)[INT]
hexagon((hh + r, h), 5)[EDU]
hexagon((hh + r, -h), 6)[SOC]
cetz.draw.on-layer(-1, {
cetz.draw.rect(
(rel: (-1, 0), to: "1.west"),
(rel: (1, 0), to: "6.east"),
fill: white,
stroke: white,
)
})
}))
I also saw some optimizations like not needing the label-pos parameter and substituting line() with rect().