|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | column-stratigraphic: Stratigraphic Column with Lithology Patterns |
3 | 3 | Library: altair 6.0.0 | Python 3.14.3 |
4 | 4 | Quality: 85/100 | Created: 2026-03-15 |
|
9 | 9 |
|
10 | 10 |
|
11 | 11 | # Data: Grand Canyon sedimentary section with 10 layers spanning Cambrian to Permian |
| 12 | +# Dramatic thickness variation (10-35 m) to showcase the format |
12 | 13 | layers = pd.DataFrame( |
13 | 14 | { |
14 | | - "top": [0, 15, 35, 55, 70, 90, 110, 135, 155, 175], |
15 | | - "bottom": [15, 35, 55, 70, 90, 110, 135, 155, 175, 200], |
| 15 | + "top": [0, 30, 45, 75, 85, 110, 120, 155, 170, 180], |
| 16 | + "bottom": [30, 45, 75, 85, 110, 120, 155, 170, 180, 200], |
16 | 17 | "lithology": [ |
17 | 18 | "Sandstone", |
18 | 19 | "Shale", |
|
66 | 67 |
|
67 | 68 | lithology_order = ["Sandstone", "Shale", "Limestone", "Siltstone", "Conglomerate"] |
68 | 69 |
|
69 | | -# Lithology pattern symbols for geological texture |
| 70 | +# Lithology pattern symbols — wider for better visibility |
70 | 71 | pattern_symbols = { |
71 | | - "Sandstone": "· · · · ·", |
72 | | - "Shale": "— — — —", |
73 | | - "Limestone": "▤ ▤ ▤ ▤", |
74 | | - "Siltstone": "╌ ╌ ╌ ╌", |
75 | | - "Conglomerate": "◯ ◯ ◯ ◯", |
| 72 | + "Sandstone": "· · · · · · · ·", |
| 73 | + "Shale": "— — — — — —", |
| 74 | + "Limestone": "▤ ▤ ▤ ▤ ▤ ▤", |
| 75 | + "Siltstone": "╌ ╌ ╌ ╌ ╌ ╌", |
| 76 | + "Conglomerate": "◯ ◯ ◯ ◯ ◯ ◯", |
76 | 77 | } |
77 | 78 |
|
78 | 79 | # Create multiple pattern rows per layer for denser texture fill |
79 | 80 | pattern_rows = [] |
80 | 81 | for _, row in layers.iterrows(): |
81 | 82 | layer_height = row["bottom"] - row["top"] |
82 | | - n_rows = max(2, int(layer_height / 7)) |
| 83 | + n_rows = max(2, int(layer_height / 6)) |
83 | 84 | spacing = layer_height / (n_rows + 1) |
84 | 85 | for i in range(n_rows): |
85 | 86 | depth = row["top"] + spacing * (i + 1) |
|
104 | 105 | age_df = pd.DataFrame(age_groups) |
105 | 106 |
|
106 | 107 | # Unconformity markers at major geological transitions |
107 | | -unconformity_df = pd.DataFrame({"depth": [110, 155], "label": ["— Unconformity —", "— Great Unconformity —"]}) |
| 108 | +unconformity_df = pd.DataFrame({"depth": [120, 170], "label": ["Unconformity", "Great Unconformity"]}) |
108 | 109 |
|
109 | | -# Shared scales |
110 | | -x_domain = [0, 16] |
| 110 | +# Shared scales — wider x domain for better canvas utilization |
| 111 | +x_domain = [0, 18] |
111 | 112 | x_axis_none = alt.Axis(labels=False, ticks=False, domain=False, grid=False) |
112 | 113 |
|
113 | | -# Layer rectangles |
| 114 | +# Layer rectangles — wider column (x: 2.5 to 10.5) |
114 | 115 | rects = ( |
115 | 116 | alt.Chart(layers) |
116 | 117 | .mark_rect(stroke="#2C3E50", strokeWidth=1.5) |
|
123 | 124 | labelFontSize=18, |
124 | 125 | titleFontSize=22, |
125 | 126 | tickCount=10, |
126 | | - gridColor="#E0E0E0", |
| 127 | + gridColor="#D5D8DC", |
127 | 128 | gridDash=[2, 4], |
128 | 129 | domainColor="#2C3E50", |
| 130 | + domainWidth=1.5, |
129 | 131 | ), |
130 | 132 | ), |
131 | 133 | y2="bottom:Q", |
|
138 | 140 | legend=alt.Legend( |
139 | 141 | titleFontSize=20, |
140 | 142 | labelFontSize=18, |
141 | | - symbolSize=500, |
| 143 | + symbolSize=600, |
142 | 144 | orient="bottom", |
143 | 145 | titlePadding=12, |
144 | 146 | direction="horizontal", |
145 | 147 | labelLimit=200, |
146 | 148 | symbolStrokeWidth=1.5, |
| 149 | + symbolStrokeColor="#2C3E50", |
147 | 150 | padding=20, |
148 | 151 | columns=5, |
149 | 152 | ), |
|
157 | 160 | alt.Tooltip("thickness:Q", title="Thickness (m)"), |
158 | 161 | ], |
159 | 162 | ) |
160 | | - .transform_calculate(x="3", x2="9") |
| 163 | + .transform_calculate(x="2.5", x2="10.5") |
161 | 164 | ) |
162 | 165 |
|
163 | | -# Dense pattern texture overlay |
| 166 | +# Dense pattern texture overlay — bolder and more prominent |
164 | 167 | pattern_text = ( |
165 | 168 | alt.Chart(pattern_df) |
166 | | - .mark_text(fontSize=16, color="#2C3E50", opacity=0.6) |
| 169 | + .mark_text(fontSize=17, color="#2C3E50", opacity=0.65, fontWeight="bold") |
167 | 170 | .encode(y=alt.Y("depth:Q"), x=alt.X("x_mid:Q", scale=alt.Scale(domain=x_domain)), text="pattern:N") |
168 | | - .transform_calculate(x_mid="6") |
| 171 | + .transform_calculate(x_mid="6.5") |
169 | 172 | ) |
170 | 173 |
|
171 | 174 | # Formation name labels to the right |
172 | 175 | formation_labels = ( |
173 | 176 | alt.Chart(layers) |
174 | 177 | .mark_text(fontSize=17, fontWeight="bold", align="left", color="#1B2631") |
175 | 178 | .encode(y=alt.Y("mid_depth:Q"), x=alt.X("x_pos:Q", scale=alt.Scale(domain=x_domain)), text="formation:N") |
176 | | - .transform_calculate(x_pos="9.4") |
| 179 | + .transform_calculate(x_pos="11.0") |
177 | 180 | ) |
178 | 181 |
|
179 | | -# Thickness annotations (subtle, on the right) |
| 182 | +# Thickness annotations — larger font for readability |
180 | 183 | thickness_labels = ( |
181 | 184 | alt.Chart(layers) |
182 | | - .mark_text(fontSize=13, align="right", color="#7F8C8D", fontStyle="italic") |
| 185 | + .mark_text(fontSize=16, align="right", color="#7F8C8D", fontStyle="italic") |
183 | 186 | .encode(y=alt.Y("mid_depth:Q"), x=alt.X("x_pos:Q", scale=alt.Scale(domain=x_domain)), text="label:N") |
184 | | - .transform_calculate(x_pos="15.8", label="datum.thickness + ' m'") |
| 187 | + .transform_calculate(x_pos="17.8", label="datum.thickness + ' m'") |
185 | 188 | ) |
186 | 189 |
|
187 | 190 | # Age period labels to the left |
188 | 191 | age_labels = ( |
189 | 192 | alt.Chart(age_df) |
190 | 193 | .mark_text(fontSize=18, fontStyle="italic", fontWeight="bold", align="right", color="#2C3E50") |
191 | 194 | .encode(y=alt.Y("mid_depth:Q"), x=alt.X("x_pos:Q", scale=alt.Scale(domain=x_domain)), text="age:N") |
192 | | - .transform_calculate(x_pos="2.0") |
| 195 | + .transform_calculate(x_pos="1.8") |
193 | 196 | ) |
194 | 197 |
|
195 | 198 | # Age bracket vertical lines |
196 | 199 | age_brackets_v = ( |
197 | 200 | alt.Chart(age_df) |
198 | 201 | .mark_rule(strokeWidth=2.5, color="#2C3E50") |
199 | 202 | .encode(y=alt.Y("top:Q"), y2="bottom:Q", x=alt.X("x_pos:Q", scale=alt.Scale(domain=x_domain))) |
200 | | - .transform_calculate(x_pos="2.5") |
| 203 | + .transform_calculate(x_pos="2.2") |
201 | 204 | ) |
202 | 205 |
|
203 | 206 | # Age bracket horizontal ticks (top and bottom of each age group) |
|
211 | 214 | alt.Chart(bracket_ticks_df) |
212 | 215 | .mark_rule(strokeWidth=2.5, color="#2C3E50") |
213 | 216 | .encode(y=alt.Y("depth:Q"), x=alt.X("x1:Q", scale=alt.Scale(domain=x_domain)), x2="x2:Q") |
214 | | - .transform_calculate(x1="2.5", x2="2.8") |
| 217 | + .transform_calculate(x1="2.2", x2="2.5") |
215 | 218 | ) |
216 | 219 |
|
217 | 220 | # Unconformity markers — red dashed lines at key geological transitions |
218 | 221 | unconformity_rules = ( |
219 | 222 | alt.Chart(unconformity_df) |
220 | 223 | .mark_rule(strokeWidth=4, color="#C0392B", strokeDash=[8, 4]) |
221 | 224 | .encode(y=alt.Y("depth:Q"), x=alt.X("x1:Q", scale=alt.Scale(domain=x_domain)), x2="x2:Q") |
222 | | - .transform_calculate(x1="3", x2="9") |
| 225 | + .transform_calculate(x1="2.5", x2="10.5") |
223 | 226 | ) |
224 | 227 |
|
| 228 | +# Unconformity labels — positioned to the right of column to avoid overlapping patterns |
225 | 229 | unconformity_labels_chart = ( |
226 | 230 | alt.Chart(unconformity_df) |
227 | | - .mark_text(fontSize=12, color="#C0392B", fontWeight="bold", align="center", dy=14) |
| 231 | + .mark_text(fontSize=13, color="#C0392B", fontWeight="bold", align="left", dy=-10) |
228 | 232 | .encode(y=alt.Y("depth:Q"), x=alt.X("x_mid:Q", scale=alt.Scale(domain=x_domain)), text="label:N") |
229 | | - .transform_calculate(x_mid="6") |
| 233 | + .transform_calculate(x_mid="11.0") |
230 | 234 | ) |
231 | 235 |
|
232 | 236 | # Combine all layers |
|
243 | 247 | + unconformity_labels_chart |
244 | 248 | ) |
245 | 249 | .properties( |
246 | | - width=1200, |
| 250 | + width=1400, |
247 | 251 | height=900, |
248 | 252 | title=alt.Title( |
249 | 253 | "column-stratigraphic · altair · pyplots.ai", |
|
0 commit comments