Stratigraphic Column with Lithology Patterns — Plotly

A vertical column visualization showing geological rock layers with standardized lithology patterns, formation names, ages, and thickness scales. Each layer is represented as a stacked rectangular block filled with a distinctive pattern (e.g., brick pattern for limestone, dots for sandstone, dashes for shale) following FGDC/USGS conventions. This plot is essential for communicating subsurface geology and sedimentary sequences in a compact, standardized format.

Stratigraphic Column with Lithology Patterns rendered with Plotly

Python source (Plotly)

""" anyplot.ai
column-stratigraphic: Stratigraphic Column with Lithology Patterns
Library: plotly 6.8.0 | Python 3.13.14
Quality: 91/100 | Updated: 2026-06-17
"""

import os

import plotly.graph_objects as go


# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"
INK_RGB = "26,26,23" if THEME == "light" else "240,239,232"

# Constant data-mark styling (kept identical across themes so the column reads the same)
PATTERN_FG = "rgba(26,26,23,0.5)"
BAR_EDGE = "rgba(26,26,23,0.55)"

# Data - synthetic sedimentary section based on Western Interior Seaway formations
layers = [
    {"top": 0, "bottom": 15, "lithology": "Sandstone", "formation": "Dakota Fm", "age": "Late Cretaceous"},
    {"top": 15, "bottom": 30, "lithology": "Shale", "formation": "Graneros Sh", "age": "Late Cretaceous"},
    {"top": 30, "bottom": 50, "lithology": "Limestone", "formation": "Greenhorn Ls", "age": "Late Cretaceous"},
    {"top": 50, "bottom": 62, "lithology": "Shale", "formation": "Carlile Sh", "age": "Late Cretaceous"},
    {"top": 62, "bottom": 78, "lithology": "Siltstone", "formation": "Niobrara Fm", "age": "Late Cretaceous"},
    {"top": 78, "bottom": 100, "lithology": "Limestone", "formation": "Fort Hays Ls", "age": "Late Cretaceous"},
    {"top": 100, "bottom": 125, "lithology": "Sandstone", "formation": "Fox Hills Ss", "age": "Maastrichtian"},
    {"top": 125, "bottom": 148, "lithology": "Conglomerate", "formation": "Lance Fm", "age": "Maastrichtian"},
    {"top": 148, "bottom": 170, "lithology": "Shale", "formation": "Fort Union Fm", "age": "Paleocene"},
    {"top": 170, "bottom": 195, "lithology": "Sandstone", "formation": "Wasatch Fm", "age": "Eocene"},
]

# Lithology fills from the Imprint palette (canonical order, brand green first),
# each paired with a distinct fill pattern approximating FGDC/USGS map symbols.
lithology_styles = {
    "Sandstone": {"color": "#009E73", "pattern_shape": ".", "pattern_size": 9},
    "Shale": {"color": "#C475FD", "pattern_shape": "-", "pattern_size": 7},
    "Limestone": {"color": "#4467A3", "pattern_shape": "+", "pattern_size": 9},
    "Siltstone": {"color": "#BD8233", "pattern_shape": "/", "pattern_size": 7},
    "Conglomerate": {"color": "#2ABCCD", "pattern_shape": "x", "pattern_size": 11},
}

# Subtle theme-adaptive shading deepens with each younger period (top -> bottom)
age_alpha = {"Late Cretaceous": 0.035, "Maastrichtian": 0.06, "Paleocene": 0.085, "Eocene": 0.11}

# Plot
fig = go.Figure()

# Group consecutive layers by age for boundary markers and left-side labels
age_groups = []
current_age = layers[0]["age"]
current_top = layers[0]["top"]
prev_bottom = layers[0]["bottom"]
for layer in layers:
    if layer["age"] != current_age:
        age_groups.append({"age": current_age, "top": current_top, "bottom": prev_bottom})
        current_age = layer["age"]
        current_top = layer["top"]
    prev_bottom = layer["bottom"]
age_groups.append({"age": current_age, "top": current_top, "bottom": prev_bottom})

# Subtle age-period background shading
for group in age_groups:
    fig.add_shape(
        type="rect",
        x0=0.62,
        x1=1.38,
        y0=group["top"],
        y1=group["bottom"],
        fillcolor=f"rgba({INK_RGB},{age_alpha.get(group['age'], 0.04)})",
        line={"width": 0},
        layer="below",
    )

# Age boundary lines (heavier dotted lines at period transitions)
for i in range(1, len(age_groups)):
    boundary_depth = age_groups[i]["top"]
    fig.add_shape(
        type="line",
        x0=0.62,
        x1=1.38,
        y0=boundary_depth,
        y1=boundary_depth,
        line={"color": INK_SOFT, "width": 2, "dash": "dot"},
        layer="above",
    )

# Layer bars
for layer in layers:
    style = lithology_styles[layer["lithology"]]
    thickness = layer["bottom"] - layer["top"]
    mid_depth = (layer["top"] + layer["bottom"]) / 2

    fig.add_trace(
        go.Bar(
            x=[1],
            y=[thickness],
            base=layer["top"],
            orientation="v",
            marker={
                "color": style["color"],
                "pattern": {
                    "shape": style["pattern_shape"],
                    "size": style["pattern_size"],
                    "solidity": 0.6,
                    "fgcolor": PATTERN_FG,
                },
                "line": {"color": BAR_EDGE, "width": 1.5},
            },
            width=0.6,
            showlegend=False,
            hovertemplate=(
                f"<b>{layer['formation']}</b><br>"
                f"Lithology: {layer['lithology']}<br>"
                f"Depth: {layer['top']}-{layer['bottom']} m<br>"
                f"Thickness: {thickness} m<br>"
                f"Age: {layer['age']}"
                "<extra></extra>"
            ),
        )
    )

    # Formation name + lithology label (right of the column)
    fig.add_annotation(
        x=1.46,
        y=mid_depth,
        text=f"<b>{layer['formation']}</b><br><i>{layer['lithology']}</i>",
        showarrow=False,
        font={"size": 11, "color": INK},
        xanchor="left",
        yanchor="middle",
    )

# Age period labels on the left side
for group in age_groups:
    mid = (group["top"] + group["bottom"]) / 2
    fig.add_annotation(
        x=0.54,
        y=mid,
        text=f"<b>{group['age']}</b>",
        showarrow=False,
        font={"size": 11, "color": INK_SOFT},
        xanchor="right",
        yanchor="middle",
    )

# Legend for lithology types with pattern swatches
for lithology, style in lithology_styles.items():
    fig.add_trace(
        go.Bar(
            x=[None],
            y=[None],
            marker={
                "color": style["color"],
                "pattern": {
                    "shape": style["pattern_shape"],
                    "size": style["pattern_size"],
                    "solidity": 0.6,
                    "fgcolor": PATTERN_FG,
                },
                "line": {"color": BAR_EDGE, "width": 1},
            },
            name=lithology,
            showlegend=True,
        )
    )

# Style
fig.update_layout(
    autosize=False,
    title={
        "text": "column-stratigraphic · python · plotly · anyplot.ai",
        "font": {"size": 16, "color": INK},
        "x": 0.5,
        "xanchor": "center",
    },
    yaxis={
        "title": {"text": "Depth (m)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "range": [198, -3],
        "dtick": 20,
        "gridcolor": GRID,
        "gridwidth": 1,
        "zeroline": False,
        "linecolor": INK_SOFT,
        "side": "left",
    },
    xaxis={"showticklabels": False, "showgrid": False, "zeroline": False, "range": [0.3, 2.2], "fixedrange": True},
    plot_bgcolor=PAGE_BG,
    paper_bgcolor=PAGE_BG,
    font={"color": INK},
    barmode="overlay",
    bargap=0,
    legend={
        "title": {"text": "<b>Lithology</b>", "font": {"size": 13, "color": INK}},
        "font": {"size": 11, "color": INK_SOFT},
        "x": 0.99,
        "y": 0.99,
        "xanchor": "right",
        "yanchor": "top",
        "bgcolor": ELEVATED_BG,
        "bordercolor": INK_SOFT,
        "borderwidth": 1,
    },
    margin={"l": 140, "r": 40, "t": 80, "b": 60},
)

# Save
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Part of Stratigraphic Column with Lithology Patterns on anyplot.ai.

Other implementations