Skip to content

Commit 8fa25d0

Browse files
feat(matplotlib): implement column-stratigraphic
1 parent 4a54060 commit 8fa25d0

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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

Comments
 (0)