|
| 1 | +"""pyplots.ai |
| 2 | +column-stratigraphic: Stratigraphic Column with Lithology Patterns |
| 3 | +Library: matplotlib | Python 3.13 |
| 4 | +Quality: pending | Created: 2026-03-15 |
| 5 | +""" |
| 6 | + |
| 7 | +import matplotlib.patches as mpatches |
| 8 | +import matplotlib.pyplot as plt |
| 9 | + |
| 10 | + |
| 11 | +# Data - synthetic sedimentary borehole section (depth increasing downward, younger at top) |
| 12 | +layers = [ |
| 13 | + {"top": 0, "bottom": 12, "lithology": "conglomerate", "formation": "Ogallala Fm", "age": "Miocene"}, |
| 14 | + {"top": 12, "bottom": 30, "lithology": "sandstone", "formation": "Arikaree Fm", "age": "Miocene"}, |
| 15 | + {"top": 30, "bottom": 50, "lithology": "siltstone", "formation": "White River Fm", "age": "Oligocene"}, |
| 16 | + {"top": 50, "bottom": 72, "lithology": "shale", "formation": "Chadron Fm", "age": "Eocene"}, |
| 17 | + {"top": 72, "bottom": 95, "lithology": "limestone", "formation": "Niobrara Fm", "age": "Cretaceous"}, |
| 18 | + {"top": 95, "bottom": 118, "lithology": "shale", "formation": "Carlile Shale", "age": "Cretaceous"}, |
| 19 | + {"top": 118, "bottom": 140, "lithology": "limestone", "formation": "Greenhorn Fm", "age": "Cretaceous"}, |
| 20 | + {"top": 140, "bottom": 158, "lithology": "sandstone", "formation": "Dakota Fm", "age": "Cretaceous"}, |
| 21 | + {"top": 158, "bottom": 175, "lithology": "siltstone", "formation": "Morrison Fm", "age": "Jurassic"}, |
| 22 | + {"top": 175, "bottom": 200, "lithology": "sandstone", "formation": "Entrada Fm", "age": "Jurassic"}, |
| 23 | +] |
| 24 | + |
| 25 | +# Lithology styles: color, hatch pattern |
| 26 | +lithology_styles = { |
| 27 | + "sandstone": {"color": "#F5DEB3", "hatch": "...", "edgecolor": "#8B7355"}, |
| 28 | + "shale": {"color": "#A9A9A9", "hatch": "---", "edgecolor": "#555555"}, |
| 29 | + "limestone": {"color": "#87CEEB", "hatch": "++", "edgecolor": "#4682B4"}, |
| 30 | + "siltstone": {"color": "#C4B69C", "hatch": "//", "edgecolor": "#8B7D6B"}, |
| 31 | + "conglomerate": {"color": "#DEB887", "hatch": "ooo", "edgecolor": "#8B6914"}, |
| 32 | +} |
| 33 | + |
| 34 | +# Plot |
| 35 | +fig, ax = plt.subplots(figsize=(10, 16)) |
| 36 | + |
| 37 | +column_left = 0.0 |
| 38 | +column_width = 3.0 |
| 39 | +max_depth = 200 |
| 40 | + |
| 41 | +for layer in layers: |
| 42 | + top = layer["top"] |
| 43 | + bottom = layer["bottom"] |
| 44 | + thickness = bottom - top |
| 45 | + style = lithology_styles[layer["lithology"]] |
| 46 | + |
| 47 | + rect = mpatches.FancyBboxPatch( |
| 48 | + (column_left, top), |
| 49 | + column_width, |
| 50 | + thickness, |
| 51 | + boxstyle="square,pad=0", |
| 52 | + facecolor=style["color"], |
| 53 | + edgecolor=style["edgecolor"], |
| 54 | + linewidth=1.5, |
| 55 | + hatch=style["hatch"], |
| 56 | + ) |
| 57 | + ax.add_patch(rect) |
| 58 | + |
| 59 | + mid_depth = (top + bottom) / 2 |
| 60 | + ax.text( |
| 61 | + column_left + column_width + 0.3, |
| 62 | + mid_depth, |
| 63 | + layer["formation"], |
| 64 | + fontsize=14, |
| 65 | + va="center", |
| 66 | + ha="left", |
| 67 | + fontweight="medium", |
| 68 | + ) |
| 69 | + |
| 70 | +# Age labels on the left with bracket lines |
| 71 | +age_spans = {} |
| 72 | +for layer in layers: |
| 73 | + age = layer["age"] |
| 74 | + if age not in age_spans: |
| 75 | + age_spans[age] = {"top": layer["top"], "bottom": layer["bottom"]} |
| 76 | + else: |
| 77 | + age_spans[age]["top"] = min(age_spans[age]["top"], layer["top"]) |
| 78 | + age_spans[age]["bottom"] = max(age_spans[age]["bottom"], layer["bottom"]) |
| 79 | + |
| 80 | +for age, span in age_spans.items(): |
| 81 | + mid = (span["top"] + span["bottom"]) / 2 |
| 82 | + ax.annotate(age, xy=(-0.3, mid), fontsize=13, va="center", ha="right", fontstyle="italic", color="#333333") |
| 83 | + ax.plot([-0.15, -0.05], [span["top"], span["top"]], color="#333333", linewidth=1.0, clip_on=False) |
| 84 | + ax.plot([-0.15, -0.05], [span["bottom"], span["bottom"]], color="#333333", linewidth=1.0, clip_on=False) |
| 85 | + ax.plot([-0.1, -0.1], [span["top"], span["bottom"]], color="#333333", linewidth=1.0, clip_on=False) |
| 86 | + |
| 87 | +# Legend |
| 88 | +legend_handles = [] |
| 89 | +for lith, style in lithology_styles.items(): |
| 90 | + patch = mpatches.Patch( |
| 91 | + facecolor=style["color"], |
| 92 | + edgecolor=style["edgecolor"], |
| 93 | + hatch=style["hatch"], |
| 94 | + label=lith.capitalize(), |
| 95 | + linewidth=1.0, |
| 96 | + ) |
| 97 | + legend_handles.append(patch) |
| 98 | + |
| 99 | +ax.legend( |
| 100 | + handles=legend_handles, |
| 101 | + loc="lower right", |
| 102 | + fontsize=13, |
| 103 | + framealpha=0.9, |
| 104 | + edgecolor="#cccccc", |
| 105 | + title="Lithology", |
| 106 | + title_fontsize=14, |
| 107 | +) |
| 108 | + |
| 109 | +# Style |
| 110 | +ax.set_xlim(-2.5, column_left + column_width + 4.5) |
| 111 | +ax.set_ylim(max_depth, 0) |
| 112 | +ax.set_ylabel("Depth (m)", fontsize=20) |
| 113 | +ax.set_title("column-stratigraphic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium", pad=20) |
| 114 | +ax.tick_params(axis="y", labelsize=16) |
| 115 | +ax.set_xticks([]) |
| 116 | +ax.spines["top"].set_visible(False) |
| 117 | +ax.spines["right"].set_visible(False) |
| 118 | +ax.spines["bottom"].set_visible(False) |
| 119 | +ax.yaxis.grid(True, alpha=0.15, linewidth=0.8) |
| 120 | + |
| 121 | +plt.tight_layout() |
| 122 | +plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments