{"spec_id":"column-stratigraphic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncolumn-stratigraphic: Stratigraphic Column with Lithology Patterns\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-17\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — first lithology is always brand green; matte-red (#AE3030)\n# is deliberately skipped here and reserved as the semantic boundary marker.\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#2ABCCD\", \"#954477\"]\nUNCONF_RED = \"#AE3030\"  # Imprint matte-red — semantic unconformity anchor\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"axes.linewidth\": 1.0,\n        \"patch.linewidth\": 1.0,\n        \"hatch.linewidth\": 0.8,\n    },\n)\n\n# Data — synthetic sedimentary section (Western Interior Seaway)\nlayers = [\n    {\"top\": 0, \"bottom\": 15, \"lithology\": \"sandstone\", \"formation\": \"Dakota Fm\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 15, \"bottom\": 30, \"lithology\": \"shale\", \"formation\": \"Graneros Sh\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 30, \"bottom\": 52, \"lithology\": \"limestone\", \"formation\": \"Greenhorn Ls\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 52, \"bottom\": 65, \"lithology\": \"shale\", \"formation\": \"Carlile Sh\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 65, \"bottom\": 78, \"lithology\": \"siltstone\", \"formation\": \"Niobrara Fm\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 78, \"bottom\": 100, \"lithology\": \"limestone\", \"formation\": \"Fort Hays Ls\", \"age\": \"Late Cretaceous\"},\n    {\"top\": 100, \"bottom\": 118, \"lithology\": \"conglomerate\", \"formation\": \"Morrison Fm\", \"age\": \"Late Jurassic\"},\n    {\"top\": 118, \"bottom\": 140, \"lithology\": \"sandstone\", \"formation\": \"Entrada Ss\", \"age\": \"Middle Jurassic\"},\n    {\"top\": 140, \"bottom\": 162, \"lithology\": \"shale\", \"formation\": \"Chinle Fm\", \"age\": \"Late Triassic\"},\n    {\"top\": 162, \"bottom\": 180, \"lithology\": \"dolomite\", \"formation\": \"Kaibab Fm\", \"age\": \"Early Permian\"},\n]\n\ndf = pd.DataFrame(layers)\ndf[\"thickness\"] = df[\"bottom\"] - df[\"top\"]\ndf[\"mid_depth\"] = (df[\"top\"] + df[\"bottom\"]) / 2\n\n# Lithology styling — Imprint colors + hatch approximations of FGDC/USGS symbols\nlith_order = [\"sandstone\", \"shale\", \"limestone\", \"siltstone\", \"conglomerate\", \"dolomite\"]\nlith_hatch = {\n    \"sandstone\": \"...\",  # stipple dots\n    \"shale\": \"---\",  # horizontal dashes\n    \"limestone\": \"+++\",  # brick / grid\n    \"siltstone\": \"//\",  # diagonal dashes\n    \"conglomerate\": \"ooo\",  # clasts / pebbles\n    \"dolomite\": \"xxx\",  # rhombic\n}\nlith_label = {name: name.title() for name in lith_order}\nlith_color = {name: IMPRINT_PALETTE[i] for i, name in enumerate(lith_order)}\n\ntotal_depth = 180\ncol_width = 0.5\n\n# Geological-age extents (min top → max bottom across that age's layers)\nage_order = [\"Late Cretaceous\", \"Late Jurassic\", \"Middle Jurassic\", \"Late Triassic\", \"Early Permian\"]\nage_spans = {age: (df[df[\"age\"] == age][\"top\"].min(), df[df[\"age\"] == age][\"bottom\"].max()) for age in age_order}\nage_tint = dict(zip(age_order, IMPRINT_PALETTE[:5], strict=True))\n\n# Plot — three panels: geological age | stratigraphic column | layer thickness\nfig, (ax_age, ax, ax_thick) = plt.subplots(\n    1, 3, figsize=(8, 4.5), dpi=400, width_ratios=[0.16, 0.50, 0.34], facecolor=PAGE_BG\n)\nfor a in (ax_age, ax, ax_thick):\n    a.set_facecolor(PAGE_BG)\n\n# Stratigraphic column — bars drawn directly at true depth coordinates (no\n# categorical-to-continuous repositioning). barh centers on mid_depth.\nfor _, row in df.iterrows():\n    ax.barh(\n        row[\"mid_depth\"],\n        col_width,\n        height=row[\"thickness\"],\n        color=lith_color[row[\"lithology\"]],\n        hatch=lith_hatch[row[\"lithology\"]],\n        edgecolor=INK,\n        linewidth=1.1,\n        zorder=3,\n    )\n\nax.set_ylim(total_depth, 0)  # depth increases downward (borehole convention)\nax.set_xlim(0, 1.6)\n\n# Formation names to the right of each layer\nfor _, row in df.iterrows():\n    ax.text(\n        col_width + 0.07,\n        row[\"mid_depth\"],\n        row[\"formation\"],\n        fontsize=7,\n        fontweight=\"medium\",\n        va=\"center\",\n        ha=\"left\",\n        color=INK,\n    )\n\n# Major unconformities — wavy red boundary lines with labels\nunconformities = [(100, \"Cretaceous–Jurassic\"), (140, \"Jurassic–Triassic\"), (162, \"Triassic–Permian\")]\nfor depth, label in unconformities:\n    x_wave = np.linspace(0, col_width, 120)\n    y_wave = depth + 1.1 * np.sin(x_wave * 45)\n    ax.plot(x_wave, y_wave, color=UNCONF_RED, linewidth=1.6, zorder=5, solid_capstyle=\"round\")\n    ax.text(\n        col_width / 2,\n        depth - 1.6,\n        label,\n        fontsize=6,\n        fontweight=\"bold\",\n        fontstyle=\"italic\",\n        va=\"bottom\",\n        ha=\"center\",\n        color=UNCONF_RED,\n        zorder=6,\n        bbox={\"facecolor\": ELEVATED_BG, \"edgecolor\": \"none\", \"alpha\": 0.85, \"pad\": 1.0},\n    )\n\nax.set_xticks([])\nax.set_yticks([])\nax.set_xlabel(\"\")\nax.set_ylabel(\"\")\nsns.despine(ax=ax, left=True, bottom=True, top=True, right=True)\n\n# Age panel — depth scale, period brackets, age labels\nax_age.set_xlim(0, 1)\nax_age.set_ylim(total_depth, 0)\nfor age in age_order:\n    top_d, bot_d = age_spans[age]\n    mid_y = (top_d + bot_d) / 2\n    # Faint Imprint tint groups each period across both panels\n    ax_age.axhspan(top_d, bot_d, color=age_tint[age], alpha=0.06, zorder=0)\n    ax.axhspan(top_d, bot_d, color=age_tint[age], alpha=0.05, zorder=0)\n    # Period bracket\n    ax_age.plot([0.82, 0.82], [top_d + 1, bot_d - 1], color=INK_SOFT, linewidth=1.4)\n    ax_age.plot([0.76, 0.88], [top_d + 1, top_d + 1], color=INK_SOFT, linewidth=1.2)\n    ax_age.plot([0.76, 0.88], [bot_d - 1, bot_d - 1], color=INK_SOFT, linewidth=1.2)\n    ax_age.text(\n        0.36,\n        mid_y,\n        age.replace(\" \", \"\\n\"),\n        fontsize=8,\n        fontweight=\"medium\",\n        fontstyle=\"italic\",\n        va=\"center\",\n        ha=\"center\",\n        color=INK,\n    )\n\nax_age.set_ylabel(\"Depth (m)\", fontsize=10, color=INK, labelpad=4)\nax_age.set_yticks(np.arange(0, total_depth + 1, 20))\nax_age.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax_age.set_xticks([])\nsns.despine(ax=ax_age, top=True, right=True, bottom=True)\nax_age.spines[\"left\"].set_color(INK_SOFT)\n\n# Thickness panel — seaborn strip plot of layer thickness by lithology\nthickness_df = df[[\"lithology\", \"thickness\"]].copy()\nsns.stripplot(\n    data=thickness_df,\n    x=\"thickness\",\n    y=\"lithology\",\n    ax=ax_thick,\n    hue=\"lithology\",\n    palette=lith_color,\n    order=lith_order,\n    hue_order=lith_order,\n    size=10,\n    marker=\"D\",\n    edgecolor=INK,\n    linewidth=0.8,\n    jitter=False,\n    legend=False,\n)\nax_thick.set_xlabel(\"Thickness (m)\", fontsize=9, color=INK)\nax_thick.set_ylabel(\"\")\nax_thick.set_yticks(range(len(lith_order)))\nax_thick.set_yticklabels([lith_label[name] for name in lith_order])\nax_thick.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax_thick.set_title(\"Layer Thickness\", fontsize=9, fontweight=\"medium\", color=INK, pad=6)\nax_thick.set_axisbelow(True)\nax_thick.xaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nsns.despine(ax=ax_thick, top=True, right=True)\nax_thick.spines[\"left\"].set_color(INK_SOFT)\nax_thick.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Lithology legend (decodes column hatching) — figure-level strip at the bottom\nlegend_handles = [\n    mpatches.Patch(facecolor=lith_color[name], edgecolor=INK, hatch=lith_hatch[name], label=lith_label[name])\n    for name in lith_order\n]\nlegend = fig.legend(\n    handles=legend_handles,\n    loc=\"lower center\",\n    bbox_to_anchor=(0.53, 0.005),\n    ncol=6,\n    fontsize=7,\n    title=\"Lithology\",\n    title_fontsize=8,\n    frameon=True,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    framealpha=0.95,\n    columnspacing=1.1,\n    handlelength=1.4,\n)\nlegend.get_title().set_color(INK)\nfor text in legend.get_texts():\n    text.set_color(INK)\n\n# Title\nfig.suptitle(\n    \"column-stratigraphic · python · seaborn · anyplot.ai\", fontsize=14, fontweight=\"medium\", color=INK, y=0.955\n)\n\nfig.subplots_adjust(left=0.07, right=0.99, top=0.86, bottom=0.18, wspace=0.07)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}