Help drawing a soil profile diagram with inclined backfill in Typst

Hi everyone,

I’m trying to reproduce the attached figure in Typst. It’s a soil profile showing two layers (gravel and sand) with inclined backfill (β = 20°), and I’d appreciate any help or example code.
Capture

Here are the details I’d like to include:

  • Two soil layers:
    • Bottom layer (Gravier): γ = 21 kN/m³, φ’ = 40°, Kra2 = 0.266
    • Top layer (Sable): γ = 18 kN/m³, φ’ = 30°, Kra1 = 0.441
  • Inclined backfill with slope β = 20°
  • Layer thicknesses: z₂ = 2 m (Gravier), z₁ = 4 m (Sable), total H = 6 m
  • Labeled points: A (bottom), B (interface), and C (top)
  • Sloped lines for the soil surface and interface
  • Shaded color areas for the two layers (different colors)
  • Vertical dimension lines and labels for z₁, z₂, and H
#import "@preview/cetz:0.3.4"
#set page(width: auto, height: auto, margin: .5cm)

#cetz.canvas(length: 3cm, {
  import cetz.draw: *

  // Define slope
  let slope = calc.tan(20deg)

  // Line 1 from (0,0) to (3, 3*slope)
  line((0, 0), (1, 2 * slope), stroke: (paint: blue))

  // Line 2: parallel, offset in y by -1
  line((0, -1), (1,  2* slope - 1), stroke: (paint: red))
   set-style(stroke: (thickness: 1.2pt))
  line((0, 0), (0, -1.5), stroke: (paint: black))
})

Any help writing the Typst code to generate this would be greatly appreciated!

Thanks in advance!

1 Like

Hi. Please read the How to post in the Questions category. The category should be Questions.

#import "@preview/cetz:0.3.4"
#set page(width: auto, height: auto, margin: .5cm)

#set text(8pt)
#cetz.canvas(length: 1cm, {
  let olive = rgb("#a5a552")

  let pattern(degree, width: 10cm, dx: 0pt) = tiling(size: (width, 10cm), {
    let rect = rect(width: 8pt, stroke: olive)
    let rail = tiling(size: (11pt, 4pt), place(dx: 0.5pt, rect))
    rotate(degree, move(dx: dx, block(width: width, height: 10cm, fill: rail)))
  })

  import cetz.draw: *

  let draw-slope(pos, deg) = {
    line(pos, (rel: (1.5, 0)), name: "l")
    let label = $beta = #calc.round(deg.deg()) degree$
    content((rel: (0.3, -0.2), to: "l.mid"), text(0.8em, label))
    let mark = (symbol: ">", width: 4pt, scale: 0.4)
    arc("l.end", radius: 1.5, start: 0deg, stop: deg, mark: mark)
  }

  let draw-pattern(from, to, height: 0.3, pattern) = line(
    (rel: (0, -height), to: from),
    (rel: (0, height)),
    to,
    (rel: (0, -height)),
    close: true,
    stroke: none,
    fill: pattern,
  )

  let soil-info(name, body) = content(name, {
    set align(center)
    set par(spacing: 0.5em)
    set par(leading: 0.3em)
    underline(emph(name))
    parbreak()
    body
  })

  let height = 6
  let gravier-height = 2
  let beta = 20deg
  let slope = (beta, 5)

  // Sable background
  line(
    (0, gravier-height),
    (0, height),
    (rel: slope),
    (rel: (0, -(height - gravier-height))),
    fill: rgb("#ece9c0"),
    stroke: none,
    close: true,
    name: "Sable",
  )

  draw-slope("Sable.32%", beta)
  draw-pattern("Sable.25%", "Sable.31%", pattern(5deg, dx: -12mm))
  draw-pattern("Sable.42%", "Sable.49%", pattern(5deg, dx: -12mm))

  soil-info("Sable")[
    $
          gamma & = 18 [k N\/m^3] \
           phi' & = 30 degree     \
      K_(r a_1) & = 0.441         \
    $
  ]

  // Gravier background
  line(
    (0, 0),
    (0, gravier-height),
    (rel: slope),
    ((), "|-", (0, 0)),
    fill: rgb("#c7e6c7"),
    stroke: none,
    close: true,
    name: "Gravier",
  )

  draw-slope("Gravier.28%", beta)

  soil-info("Gravier")[
    $
          gamma & = 21 [k N\/m^3] \
           phi' & = 40 degree     \
      K_(r a_2) & = 0.266         \
    $
  ]

  // Right-side lines
  line((0, height), (rel: slope), stroke: olive)
  line((0, gravier-height), (rel: slope), stroke: red)

  // Left-side lines
  let right-length = 3
  line((0, 0), (rel: (-right-length, 0)), name: "l", stroke: olive)
  draw-pattern("l.12%", "l.90%", pattern(30deg, width: 25.8mm, dx: -28.8mm))
  line((0, height), (rel: (-right-length, 0)))

  let dimention-text(at, body) = {
    content(at, box(fill: white, inset: 0.1em, body))
  }
  let dimention-line(..args) = {
    let s = (symbol: ">", fill: black, stroke: (miter-limit: 100), width: 0.5mm)
    line(mark: s, ..args)
  }

  let x = -right-length + 0.2
  dimention-line((x, 0), (x, height), name: "H")
  dimention-text("H.mid")[$"H" = height thin "m"$]

  let x = -right-length / 2

  anchor("C", (rel: (-0.1, -0.1), to: (0, height)))
  content("C", anchor: "north-east")[$C$]

  dimention-line((x, gravier-height), (x, height), name: "z1")
  let pos = (rel: (-0.3, 0), to: "z1.mid")
  dimention-text(pos)[$z_1 = #(height - gravier-height) thin "m"$]

  line((0, gravier-height), (rel: (x - 0.1, 0)))

  anchor("B", (rel: (-0.1, -0.1), to: (0, gravier-height)))
  content("B", anchor: "north-east")[$B$]

  dimention-line((x, 0), (x, gravier-height), name: "z2")
  let pos = (rel: (-0.3, 0), to: "z2.mid")
  dimention-text(pos)[$z_2 = #gravier-height thin "m"$]

  anchor("A", (rel: (-0.1, -0.1), to: (0, 0)))
  content("A", anchor: "north-east")[$A$]

  line((0, 0), (0, height), stroke: 1.5pt)
})
Output

You can change these variables and pretty much everything will automatically adapt to new values:

  let height = 6
  let gravier-height = 2
  let beta = 20deg
  let slope = (beta, 5)
  let right-length = 3

The only thing that doesn’t work great is the pattern. It is pretty complex in terms of transformation, so I don’t know how to make it more adaptable. I also tried to copy the start/end look from the original image.

You can play with global length and font size to get better scaling of things.

1 Like

Thank you for your time.

Hi @TlidYou, thanks for your question! If you feel the response you got has sufficiently answered your question, be sure to give it a checkmark :ballot_box_with_check:. This will help others find the solution in the future. If something is missing, please let us know what so we can resolve your issue. Thanks!