{"spec_id":"column-stratigraphic","library":"makie","language":"julia","code":"# anyplot.ai\n# column-stratigraphic: Stratigraphic Column with Lithology Patterns\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-06-17\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens (Imprint palette, theme-adaptive chrome) ------------------\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\n# Imprint categorical palette — first series is always brand green\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#AE3030\",  # 5 — matte red (deferred semantic anchor — skipped here)\n    colorant\"#2ABCCD\",  # 6 — cyan\n    colorant\"#954477\",  # 7 — rose\n    colorant\"#99B314\",  # 8 — lime\n]\n\nconst FILL_ALPHA = THEME == \"light\" ? 0.42 : 0.52\npattern_ink = RGBAf(INK.r, INK.g, INK.b, 0.82)\n\n# Coal is rendered with a theme-stable dark fill so it always reads as\n# semantic black/coal (rather than flipping to the near-white INK token in\n# dark mode); its cleat partings use a fixed pale stroke that shows on it in\n# both themes.\nconst COAL_FILL  = colorant\"#2E2E28\"\nconst COAL_CLEAT = RGBAf(0.80, 0.79, 0.73, 0.65)\n\n# --- Synthetic borehole section (depth increases downward, metres) ----------\ntops       = [0.0, 24.0, 58.0, 82.0, 120.0, 140.0, 176.0, 200.0, 214.0]\nbottoms    = [24.0, 58.0, 82.0, 120.0, 140.0, 176.0, 200.0, 214.0, 232.0]\nlithology  = [\"Sandstone\", \"Shale\", \"Limestone\", \"Siltstone\", \"Conglomerate\",\n              \"Dolostone\", \"Sandstone\", \"Coal\", \"Shale\"]\nformation  = [\"Aeolian Sand Mbr\", \"Marl Bay Shale\", \"Reefal Limestone\",\n              \"Prodelta Siltstone\", \"Basal Conglomerate\", \"Tidal Dolostone\",\n              \"Fluvial Sandstone\", \"Cyclothem Coal\", \"Black Basinal Shale\"]\nage        = [\"Neogene\", \"Paleogene\", \"Late Cretaceous\", \"Early Cretaceous\",\n              \"Jurassic\", \"Triassic\", \"Permian\", \"Pennsylvanian\", \"Mississippian\"]\n\n# Lithology → fill colour (canonical Imprint order; coal reads as semantic black)\nlith_order = [\"Sandstone\", \"Shale\", \"Limestone\", \"Siltstone\",\n              \"Conglomerate\", \"Dolostone\", \"Coal\"]\nlith_color = Dict(\n    \"Sandstone\"    => IMPRINT_PALETTE[1],\n    \"Shale\"        => IMPRINT_PALETTE[2],\n    \"Limestone\"    => IMPRINT_PALETTE[3],\n    \"Siltstone\"    => IMPRINT_PALETTE[4],\n    \"Conglomerate\" => IMPRINT_PALETTE[6],\n    \"Dolostone\"    => IMPRINT_PALETTE[7],\n    \"Coal\"         => COAL_FILL,\n)\n\n# Depth of the unconformity (drawn as a wavy boundary instead of a straight rule)\nconst UNCONFORMITY = 120.0\n\n# --- Lithology pattern painter ----------------------------------------------\n# Draws an FGDC/USGS-style texture clipped to the rectangle [x0,x1]×[ytop,ybot].\n# Reused for the column blocks and the legend swatches.\nfunction add_pattern!(ax, lith, x0, x1, ytop, ybot; ink)\n    w  = x1 - x0\n    h  = ybot - ytop\n    px = w * 0.05\n\n    if lith == \"Sandstone\"\n        # stipple dots\n        n = clamp(round(Int, w * h * 0.55), 12, 360)\n        xs = x0 .+ px .+ rand(n) .* (w - 2px)\n        ys = ytop .+ rand(n) .* h\n        scatter!(ax, xs, ys; marker = :circle, markersize = 2.6,\n                 color = ink, strokewidth = 0)\n\n    elseif lith == \"Shale\"\n        # fine continuous laminae\n        segs = Point2f[]\n        yy = ytop + 4.0\n        while yy < ybot - 0.5\n            push!(segs, Point2f(x0 + px, yy), Point2f(x1 - px, yy))\n            yy += 4.6\n        end\n        linesegments!(ax, segs; color = ink, linewidth = 0.8)\n\n    elseif lith == \"Limestone\"\n        # brick: horizontal courses + staggered vertical joints\n        bh = 7.5\n        bw = (x1 - x0) / 4\n        segs = Point2f[]\n        yy = ytop\n        row = 0\n        while yy <= ybot + 0.01\n            yclip = min(yy, ybot)\n            push!(segs, Point2f(x0, yclip), Point2f(x1, yclip))\n            yy += bh\n            row += 1\n        end\n        yy = ytop\n        row = 0\n        while yy < ybot - 0.5\n            off = isodd(row) ? bw / 2 : 0.0\n            xx = x0 + off\n            while xx <= x1 + 0.01\n                if xx >= x0 && xx <= x1\n                    push!(segs, Point2f(xx, yy), Point2f(xx, min(yy + bh, ybot)))\n                end\n                xx += bw\n            end\n            yy += bh\n            row += 1\n        end\n        linesegments!(ax, segs; color = ink, linewidth = 0.9)\n\n    elseif lith == \"Siltstone\"\n        # broken, staggered dashes\n        dyl = 6.0\n        dl  = w / 6\n        gap = dl * 0.7\n        segs = Point2f[]\n        yy = ytop + dyl\n        row = 0\n        while yy < ybot - 0.5\n            off = isodd(row) ? (dl + gap) / 2 : 0.0\n            xx = x0 + px + off\n            while xx < x1 - px\n                push!(segs, Point2f(xx, yy), Point2f(min(xx + dl, x1 - px), yy))\n                xx += dl + gap\n            end\n            yy += dyl\n            row += 1\n        end\n        linesegments!(ax, segs; color = ink, linewidth = 1.0)\n\n    elseif lith == \"Conglomerate\"\n        # scattered clasts (open circles of varying size) over a faint matrix\n        n = clamp(round(Int, w * h * 0.10), 5, 70)\n        cx = x0 .+ 1.5px .+ rand(n) .* (w - 3px)\n        cy = ytop .+ 0.06h .+ rand(n) .* (h * 0.88)\n        sz = 8.0 .+ rand(n) .* 12.0\n        scatter!(ax, cx, cy; marker = :circle, markersize = sz,\n                 color = RGBAf(0, 0, 0, 0), strokecolor = ink, strokewidth = 1.2)\n        nm = clamp(round(Int, w * h * 0.18), 8, 120)\n        scatter!(ax, x0 .+ px .+ rand(nm) .* (w - 2px), ytop .+ rand(nm) .* h;\n                 marker = :circle, markersize = 1.4, color = ink, strokewidth = 0)\n\n    elseif lith == \"Dolostone\"\n        # rhombic texture: staggered open diamonds\n        dx = w / 4\n        dy = 9.0\n        cx = Float64[]\n        cy = Float64[]\n        yy = ytop + dy / 2\n        row = 0\n        while yy < ybot - dy / 4\n            off = isodd(row) ? dx / 2 : 0.0\n            xx = x0 + dx / 2 + off\n            while xx < x1 + 0.01\n                if xx >= x0 && xx <= x1\n                    push!(cx, xx)\n                    push!(cy, yy)\n                end\n                xx += dx\n            end\n            yy += dy\n            row += 1\n        end\n        scatter!(ax, cx, cy; marker = :diamond, markersize = 11,\n                 color = RGBAf(0, 0, 0, 0), strokecolor = ink, strokewidth = 1.0)\n\n    elseif lith == \"Coal\"\n        # solid block already filled; add a few light cleat partings\n        segs = Point2f[]\n        for f in (0.3, 0.55, 0.78)\n            yy = ytop + f * h\n            push!(segs, Point2f(x0 + px, yy), Point2f(x1 - px, yy))\n        end\n        linesegments!(ax, segs; color = COAL_CLEAT, linewidth = 1.2)\n    end\nend\n\n# --- Figure -----------------------------------------------------------------\ntitle_str = \"column-stratigraphic · julia · makie · anyplot.ai\"\ntitle_sz  = round(Int, 20 * min(1.0, 67 / length(title_str)))\n\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    backgroundcolor   = PAGE_BG,\n    title             = title_str,\n    titlesize         = title_sz,\n    titlecolor        = INK,\n    titlegap          = 14,\n    ylabel            = \"Depth (m)\",\n    ylabelsize        = 15,\n    ylabelcolor       = INK,\n    yticks            = 0:40:240,\n    yticklabelsize    = 13,\n    yticklabelcolor   = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    leftspinecolor    = INK_SOFT,\n    topspinevisible   = false,\n    rightspinevisible = false,\n    bottomspinevisible = false,\n    xgridvisible      = false,\n    ygridvisible      = false,\n)\n\nhidexdecorations!(ax)\nxlims!(ax, 0.0, 11.8)\nylims!(ax, 240.0, -24.0)   # inverted y: 0 m at top, deepest layer at the bottom\n\n# Column geometry\nconst X0 = 2.3\nconst X1 = 4.7\nconst X_AGE  = 2.05   # age labels — right-aligned, to the left of the column\nconst X_FORM = 5.0    # formation labels — left-aligned, to the right of the column\n\n# --- Layers: tinted fill + lithology pattern --------------------------------\nfor i in eachindex(tops)\n    ytop = tops[i]\n    ybot = bottoms[i]\n    lith = lithology[i]\n    c    = lith_color[lith]\n    fillc = lith == \"Coal\" ? RGBAf(c.r, c.g, c.b, 1.0) :\n            RGBAf(c.r, c.g, c.b, FILL_ALPHA)\n\n    poly!(ax, Rect2f(X0, ytop, X1 - X0, ybot - ytop);\n          color = fillc, strokewidth = 0)\n    add_pattern!(ax, lith, X0, X1, ytop, ybot; ink = pattern_ink)\nend\n\n# --- Layer boundaries (solid) + one wavy unconformity -----------------------\nboundary_segs = Point2f[]\nfor d in unique(vcat(tops, bottoms[end]))\n    d == UNCONFORMITY && continue\n    push!(boundary_segs, Point2f(X0, d), Point2f(X1, d))\nend\nlinesegments!(ax, boundary_segs; color = INK_SOFT, linewidth = 1.6)\n\n# Wavy unconformity line\nwx = range(X0, X1, length = 90)\nwy = UNCONFORMITY .+ 2.6 .* sin.((wx .- X0) .* (2π * 3 / (X1 - X0)))\nlines!(ax, collect(wx), collect(wy); color = INK_SOFT, linewidth = 2.4)\n\n# Column outline (left/right edges)\nlines!(ax, [X0, X0], [tops[1], bottoms[end]]; color = INK_SOFT, linewidth = 1.6)\nlines!(ax, [X1, X1], [tops[1], bottoms[end]]; color = INK_SOFT, linewidth = 1.6)\n\n# --- Age (left) and formation (right) labels --------------------------------\nfor i in eachindex(tops)\n    ymid = (tops[i] + bottoms[i]) / 2\n\n    text!(ax, X_AGE, ymid; text = age[i],\n          align = (:right, :center), color = INK_SOFT, fontsize = 12.5)\n\n    label = string(formation[i], \"\\n\", Int(tops[i]), \"–\", Int(bottoms[i]), \" m\")\n    text!(ax, X_FORM, ymid; text = label,\n          align = (:left, :center), color = INK, fontsize = 13)\nend\n\n# Unconformity annotation: a thin leader nudges the label clear of the column\n# edge into the open whitespace, between the two adjacent formation rows.\nlines!(ax, [X1, 6.9], [UNCONFORMITY, UNCONFORMITY];\n       color = INK_MUTED, linewidth = 1.0, linestyle = :dot)\ntext!(ax, 7.0, UNCONFORMITY; text = \"unconformity\",\n      align = (:left, :center), color = INK_MUTED, fontsize = 11.5)\n\n# --- Lithology legend (pattern key, upper right) ----------------------------\ntext!(ax, 8.9, -14.0; text = \"Lithology\", align = (:left, :center),\n      color = INK, fontsize = 14)\n\nsw_x0 = 8.9\nsw_x1 = 10.0\nsw_h  = 17.0\nfor (k, lith) in enumerate(lith_order)\n    yc = 6.0 + (k - 1) * 29.0\n    yt = yc - sw_h / 2\n    yb = yc + sw_h / 2\n    c  = lith_color[lith]\n    fillc = lith == \"Coal\" ? RGBAf(c.r, c.g, c.b, 1.0) :\n            RGBAf(c.r, c.g, c.b, FILL_ALPHA)\n\n    poly!(ax, Rect2f(sw_x0, yt, sw_x1 - sw_x0, sw_h);\n          color = fillc, strokecolor = INK_SOFT, strokewidth = 1.2)\n    add_pattern!(ax, lith, sw_x0, sw_x1, yt, yb; ink = pattern_ink)\n\n    text!(ax, sw_x1 + 0.15, yc; text = lith,\n          align = (:left, :center), color = INK_SOFT, fontsize = 13)\nend\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}