|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | column-stratigraphic: Stratigraphic Column with Lithology Patterns |
3 | 3 | Library: matplotlib 3.10.8 | Python 3.14.3 |
4 | 4 | Quality: 87/100 | Created: 2026-03-15 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import matplotlib.patches as mpatches |
8 | 8 | import matplotlib.pyplot as plt |
| 9 | +import numpy as np |
9 | 10 |
|
10 | 11 |
|
11 | 12 | # Data - synthetic sedimentary borehole section (depth increasing downward, younger at top) |
|
31 | 32 | "conglomerate": {"color": "#DEB887", "hatch": "ooo", "edgecolor": "#8B6914"}, |
32 | 33 | } |
33 | 34 |
|
| 35 | +# Age group background colors for subtle shading |
| 36 | +age_colors = { |
| 37 | + "Miocene": "#FFF8E7", |
| 38 | + "Oligocene": "#F5F0E0", |
| 39 | + "Eocene": "#EDE8D8", |
| 40 | + "Cretaceous": "#E8EEF5", |
| 41 | + "Jurassic": "#F0E8E0", |
| 42 | +} |
| 43 | + |
34 | 44 | # Plot |
35 | | -fig, ax = plt.subplots(figsize=(10, 16)) |
| 45 | +fig, ax = plt.subplots(figsize=(12, 16)) |
36 | 46 |
|
37 | | -column_left = 0.0 |
38 | | -column_width = 3.0 |
| 47 | +column_left = 1.5 |
| 48 | +column_width = 5.0 |
39 | 49 | max_depth = 200 |
40 | 50 |
|
| 51 | +# Compute age spans |
| 52 | +age_spans = {} |
| 53 | +for layer in layers: |
| 54 | + age = layer["age"] |
| 55 | + if age not in age_spans: |
| 56 | + age_spans[age] = {"top": layer["top"], "bottom": layer["bottom"]} |
| 57 | + else: |
| 58 | + age_spans[age]["top"] = min(age_spans[age]["top"], layer["top"]) |
| 59 | + age_spans[age]["bottom"] = max(age_spans[age]["bottom"], layer["bottom"]) |
| 60 | + |
| 61 | +# Draw subtle age-group background shading |
| 62 | +for age, span in age_spans.items(): |
| 63 | + bg_rect = mpatches.FancyBboxPatch( |
| 64 | + (column_left - 0.1, span["top"]), |
| 65 | + column_width + 0.2, |
| 66 | + span["bottom"] - span["top"], |
| 67 | + boxstyle="square,pad=0", |
| 68 | + facecolor=age_colors[age], |
| 69 | + edgecolor="none", |
| 70 | + zorder=0, |
| 71 | + ) |
| 72 | + ax.add_patch(bg_rect) |
| 73 | + |
| 74 | +# Draw lithology layers |
41 | 75 | for layer in layers: |
42 | 76 | top = layer["top"] |
43 | 77 | bottom = layer["bottom"] |
|
53 | 87 | edgecolor=style["edgecolor"], |
54 | 88 | linewidth=1.5, |
55 | 89 | hatch=style["hatch"], |
| 90 | + zorder=1, |
56 | 91 | ) |
57 | 92 | ax.add_patch(rect) |
58 | 93 |
|
59 | 94 | mid_depth = (top + bottom) / 2 |
60 | 95 | ax.text( |
61 | | - column_left + column_width + 0.3, |
| 96 | + column_left + column_width + 0.4, |
62 | 97 | mid_depth, |
63 | 98 | layer["formation"], |
64 | | - fontsize=14, |
| 99 | + fontsize=16, |
65 | 100 | va="center", |
66 | 101 | ha="left", |
67 | | - fontweight="medium", |
| 102 | + fontweight="semibold", |
| 103 | + color="#2C2C2C", |
68 | 104 | ) |
69 | 105 |
|
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"]) |
| 106 | +# Unconformity between Eocene (Chadron Fm, bottom=72) and Cretaceous (Niobrara Fm, top=72) |
| 107 | +unconformity_depth = 72 |
| 108 | +x_wave = np.linspace(column_left, column_left + column_width, 80) |
| 109 | +y_wave = unconformity_depth + 0.8 * np.sin(x_wave * 4) |
| 110 | +ax.plot(x_wave, y_wave, color="#B22222", linewidth=2.5, zorder=3) |
| 111 | +ax.text( |
| 112 | + column_left + column_width + 0.4, |
| 113 | + unconformity_depth, |
| 114 | + "unconformity", |
| 115 | + fontsize=14, |
| 116 | + va="center", |
| 117 | + ha="left", |
| 118 | + fontstyle="italic", |
| 119 | + color="#B22222", |
| 120 | + fontweight="medium", |
| 121 | +) |
79 | 122 |
|
| 123 | +# Age labels on the left with bracket lines |
| 124 | +bracket_x = column_left - 1.2 |
80 | 125 | for age, span in age_spans.items(): |
81 | 126 | 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) |
| 127 | + ax.text( |
| 128 | + column_left - 1.8, |
| 129 | + mid, |
| 130 | + age, |
| 131 | + fontsize=16, |
| 132 | + va="center", |
| 133 | + ha="center", |
| 134 | + fontstyle="italic", |
| 135 | + color="#333333", |
| 136 | + fontweight="medium", |
| 137 | + clip_on=False, |
| 138 | + ) |
| 139 | + ax.plot( |
| 140 | + [bracket_x, bracket_x + 0.4], |
| 141 | + [span["top"] + 0.5, span["top"] + 0.5], |
| 142 | + color="#555555", |
| 143 | + linewidth=1.2, |
| 144 | + clip_on=False, |
| 145 | + ) |
| 146 | + ax.plot( |
| 147 | + [bracket_x, bracket_x + 0.4], |
| 148 | + [span["bottom"] - 0.5, span["bottom"] - 0.5], |
| 149 | + color="#555555", |
| 150 | + linewidth=1.2, |
| 151 | + clip_on=False, |
| 152 | + ) |
| 153 | + ax.plot( |
| 154 | + [bracket_x + 0.2, bracket_x + 0.2], |
| 155 | + [span["top"] + 0.5, span["bottom"] - 0.5], |
| 156 | + color="#555555", |
| 157 | + linewidth=1.2, |
| 158 | + clip_on=False, |
| 159 | + ) |
86 | 160 |
|
87 | 161 | # Legend |
88 | 162 | legend_handles = [] |
|
98 | 172 |
|
99 | 173 | ax.legend( |
100 | 174 | handles=legend_handles, |
101 | | - loc="lower right", |
102 | | - fontsize=13, |
103 | | - framealpha=0.9, |
104 | | - edgecolor="#cccccc", |
| 175 | + loc="upper center", |
| 176 | + bbox_to_anchor=(0.55, -0.03), |
| 177 | + fontsize=16, |
| 178 | + framealpha=0.95, |
| 179 | + edgecolor="#bbbbbb", |
| 180 | + fancybox=True, |
| 181 | + shadow=True, |
105 | 182 | title="Lithology", |
106 | | - title_fontsize=14, |
| 183 | + title_fontsize=17, |
| 184 | + borderpad=1.0, |
| 185 | + ncol=5, |
107 | 186 | ) |
108 | 187 |
|
109 | 188 | # Style |
110 | | -ax.set_xlim(-2.5, column_left + column_width + 4.5) |
| 189 | +ax.set_xlim(column_left - 2.8, column_left + column_width + 4.5) |
111 | 190 | 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) |
| 191 | +ax.set_ylabel("Depth (m)", fontsize=20, labelpad=10) |
| 192 | +ax.set_title("column-stratigraphic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium", pad=25) |
| 193 | +ax.tick_params(axis="y", labelsize=16, length=6) |
115 | 194 | ax.set_xticks([]) |
116 | 195 | ax.spines["top"].set_visible(False) |
117 | 196 | ax.spines["right"].set_visible(False) |
118 | 197 | ax.spines["bottom"].set_visible(False) |
119 | | -ax.yaxis.grid(True, alpha=0.15, linewidth=0.8) |
| 198 | +ax.yaxis.grid(True, alpha=0.12, linewidth=0.8, linestyle="--") |
120 | 199 |
|
121 | 200 | plt.tight_layout() |
122 | 201 | plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments