Skip to content

Commit c84d1a7

Browse files
feat(seaborn): implement skewt-logp-atmospheric (#3927)
## Implementation: `skewt-logp-atmospheric` - seaborn Implements the **seaborn** version of `skewt-logp-atmospheric`. **File:** `plots/skewt-logp-atmospheric/implementations/seaborn.py` **Parent Issue:** #3802 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/21092078646)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 4df09b2 commit c84d1a7

2 files changed

Lines changed: 353 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
""" pyplots.ai
2+
skewt-logp-atmospheric: Skew-T Log-P Atmospheric Diagram
3+
Library: seaborn 0.13.2 | Python 3.13.11
4+
Quality: 90/100 | Created: 2026-01-17
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import pandas as pd
10+
import seaborn as sns
11+
from matplotlib.ticker import ScalarFormatter
12+
13+
14+
# Set seaborn style with whitegrid for subtle grid lines, using talk context for better scaling
15+
sns.set_theme(style="whitegrid", context="talk", font_scale=1.1)
16+
sns.set_palette("colorblind")
17+
18+
# Synthetic atmospheric sounding data (typical mid-latitude summer profile)
19+
np.random.seed(42)
20+
pressure = np.array([1000, 925, 850, 700, 500, 400, 300, 250, 200, 150, 100])
21+
temperature = np.array([25, 18, 12, 2, -20, -35, -50, -55, -58, -60, -55])
22+
dewpoint = np.array([18, 14, 8, -5, -30, -45, -60, -65, -68, -70, -65])
23+
24+
# Create figure with custom transform for skew-T
25+
fig, ax = plt.subplots(figsize=(16, 9))
26+
27+
# Set up the axes with log scale for pressure (inverted)
28+
ax.set_yscale("log")
29+
ax.set_ylim(1050, 100) # Inverted: surface at bottom
30+
ax.set_xlim(-50, 50)
31+
32+
# Custom skew transform (45 degrees)
33+
skew_angle = 45
34+
skew_slope = np.tan(np.radians(skew_angle))
35+
36+
# Draw isotherms (temperature lines, skewed)
37+
isotherm_temps = np.arange(-80, 60, 10)
38+
p_range = np.logspace(np.log10(100), np.log10(1050), 100)
39+
for t in isotherm_temps:
40+
x_iso = t + skew_slope * (np.log(1000 / p_range))
41+
ax.plot(x_iso, p_range, color="#cccccc", linewidth=0.8, alpha=0.7, zorder=1)
42+
43+
# Draw isobars (horizontal pressure lines)
44+
isobar_levels = [1000, 925, 850, 700, 500, 400, 300, 250, 200, 150, 100]
45+
for p in isobar_levels:
46+
ax.axhline(y=p, color="#dddddd", linewidth=0.8, alpha=0.7, zorder=1)
47+
48+
# Draw dry adiabats (potential temperature lines)
49+
theta_values = np.arange(250, 450, 20) # Potential temperatures in K
50+
for theta in theta_values:
51+
p_adiabat = np.logspace(np.log10(100), np.log10(1050), 100)
52+
# T = theta * (p/1000)^(R/cp), where R/cp ≈ 0.286
53+
t_adiabat = theta * (p_adiabat / 1000) ** 0.286 - 273.15
54+
x_adiabat = t_adiabat + skew_slope * (np.log(1000 / p_adiabat))
55+
ax.plot(x_adiabat, p_adiabat, color="#8B4513", linewidth=0.6, alpha=0.5, zorder=1)
56+
57+
# Draw moist adiabats (saturated adiabats - simplified)
58+
theta_e_values = np.arange(270, 370, 20) # Equivalent potential temperatures
59+
for theta_e in theta_e_values:
60+
p_moist = np.logspace(np.log10(100), np.log10(1050), 100)
61+
# Simplified moist adiabat approximation
62+
t_moist = (theta_e - 30) * (p_moist / 1000) ** 0.3 - 273.15
63+
x_moist = t_moist + skew_slope * (np.log(1000 / p_moist))
64+
ax.plot(x_moist, p_moist, color="#228B22", linewidth=0.6, alpha=0.4, linestyle="--", zorder=1)
65+
66+
# Draw mixing ratio lines (constant water vapor mixing ratio)
67+
mixing_ratios = [1, 2, 4, 7, 10, 15, 20] # g/kg
68+
for w in mixing_ratios:
69+
p_mix = np.logspace(np.log10(400), np.log10(1050), 50)
70+
# Simplified mixing ratio to dewpoint: Td ≈ 35 * log10(w) - 15 + adjustment for pressure
71+
t_mix = 35 * np.log10(w) - 20 + 5 * np.log10(p_mix / 1000)
72+
x_mix = t_mix + skew_slope * (np.log(1000 / p_mix))
73+
ax.plot(x_mix, p_mix, color="#4169E1", linewidth=0.5, alpha=0.4, linestyle=":", zorder=1)
74+
75+
# Apply skew transform to data for seaborn plotting
76+
x_temp = temperature + skew_slope * np.log(1000 / pressure)
77+
x_dew = dewpoint + skew_slope * np.log(1000 / pressure)
78+
79+
# Create DataFrame for seaborn plotting
80+
df = pd.DataFrame(
81+
{
82+
"x": np.concatenate([x_temp, x_dew]),
83+
"pressure": np.concatenate([pressure, pressure]),
84+
"profile": ["Temperature"] * len(pressure) + ["Dewpoint"] * len(pressure),
85+
}
86+
)
87+
88+
# Plot profiles using seaborn lineplot
89+
sns.lineplot(
90+
data=df,
91+
x="x",
92+
y="pressure",
93+
hue="profile",
94+
style="profile",
95+
markers={"Temperature": "o", "Dewpoint": "s"},
96+
dashes={"Temperature": "", "Dewpoint": (5, 2)},
97+
palette={"Temperature": "#E74C3C", "Dewpoint": "#306998"},
98+
linewidth=4,
99+
markersize=10,
100+
ax=ax,
101+
zorder=5,
102+
legend=True,
103+
)
104+
105+
# Configure pressure axis - remove overlapping ticks (925 removed to avoid overlap with 1000)
106+
ax.yaxis.set_major_formatter(ScalarFormatter())
107+
display_ticks = [1000, 850, 700, 500, 400, 300, 250, 200, 150, 100]
108+
ax.set_yticks(display_ticks)
109+
ax.set_yticklabels([str(p) for p in display_ticks])
110+
111+
# Labels and title with proper middle dot separator
112+
ax.set_xlabel("Temperature (°C)", fontsize=20)
113+
ax.set_ylabel("Pressure (hPa)", fontsize=20)
114+
ax.set_title("skewt-logp-atmospheric · seaborn · pyplots.ai", fontsize=24)
115+
ax.tick_params(axis="both", labelsize=16)
116+
117+
# Use seaborn's despine to clean up the appearance (keep left and bottom spines only)
118+
sns.despine(ax=ax, top=True, right=True)
119+
120+
# Configure legend from seaborn lineplot
121+
legend = ax.get_legend()
122+
legend.set_title("")
123+
for text in legend.get_texts():
124+
text.set_fontsize(16)
125+
legend.get_frame().set_alpha(0.9)
126+
127+
# Add reference line labels with better positioning and improved visibility
128+
ax.text(45, 600, "Isotherms", fontsize=14, color="#555555", rotation=45, ha="center", fontweight="bold")
129+
ax.text(-25, 108, "Dry Adiabats", fontsize=13, color="#6B3510", ha="center", fontweight="bold")
130+
ax.text(0, 108, "Moist Adiabats", fontsize=13, color="#1A6B1A", ha="center", fontweight="bold")
131+
ax.text(22, 108, "Mixing Ratio", fontsize=13, color="#2E4A8B", ha="center", fontweight="bold")
132+
133+
# Add subtle background and configure grid styling
134+
ax.set_facecolor("#fafafa")
135+
ax.grid(True, which="major", alpha=0.3, linestyle="-", linewidth=0.5, color="#888888")
136+
137+
plt.tight_layout()
138+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
library: seaborn
2+
specification_id: skewt-logp-atmospheric
3+
created: '2026-01-17T09:26:00Z'
4+
updated: '2026-01-18T22:40:57Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 21092078646
7+
issue: 3802
8+
python_version: 3.13.11
9+
library_version: 0.13.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/skewt-logp-atmospheric/seaborn/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/skewt-logp-atmospheric/seaborn/plot_thumb.png
12+
preview_html: null
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent implementation of the skew-T transform with proper 45-degree isotherms
17+
- Clean separation of temperature and dewpoint profiles with appropriate styling
18+
- Proper logarithmic/inverted pressure axis matching meteorological convention
19+
- Good use of seaborn theming (whitegrid, talk context) and despine function
20+
- All major reference lines present (isotherms, dry adiabats, moist adiabats, mixing
21+
ratio)
22+
- Professional label annotations for reference line types
23+
weaknesses:
24+
- Reference lines could have a dedicated legend entry for better interpretation
25+
- Minor reliance on matplotlib for the specialized atmospheric reference lines rather
26+
than seaborn-specific visualization methods
27+
image_description: The plot displays a Skew-T Log-P atmospheric diagram with a light
28+
gray (#fafafa) background. The pressure axis (y-axis) is logarithmic and inverted,
29+
ranging from 1000 hPa at the bottom to 100 hPa at the top, with labeled tick marks
30+
at 1000, 850, 700, 500, 400, 300, 250, 200, 150, and 100 hPa. The temperature
31+
axis (x-axis) spans from -50°C to 50°C. The temperature profile is shown as a
32+
solid red line with circular markers, while the dewpoint profile is displayed
33+
as a dashed blue line with square markers. Both lines decrease with altitude,
34+
with dewpoint consistently lower than temperature. Gray diagonal isotherms at
35+
45° skew angle cross the plot. Brown diagonal lines represent dry adiabats, green
36+
dashed lines show moist adiabats, and blue dotted lines indicate mixing ratio
37+
lines. Reference labels for "Dry Adiabats", "Moist Adiabats", and "Mixing Ratio"
38+
appear at the top, with "Isotherms" labeled on the right side. The title uses
39+
the correct format with middle dots. A legend in the upper right distinguishes
40+
Temperature and Dewpoint profiles.
41+
criteria_checklist:
42+
visual_quality:
43+
score: 37
44+
max: 40
45+
items:
46+
- id: VQ-01
47+
name: Text Legibility
48+
score: 10
49+
max: 10
50+
passed: true
51+
comment: Title at 24pt, axis labels at 20pt, ticks at 16pt, all clearly readable
52+
- id: VQ-02
53+
name: No Overlap
54+
score: 8
55+
max: 8
56+
passed: true
57+
comment: No overlapping text elements; tick label at 925 hPa removed to prevent
58+
overlap
59+
- id: VQ-03
60+
name: Element Visibility
61+
score: 7
62+
max: 8
63+
passed: true
64+
comment: Markers and lines are visible with good sizing; reference lines slightly
65+
subtle
66+
- id: VQ-04
67+
name: Color Accessibility
68+
score: 5
69+
max: 5
70+
passed: true
71+
comment: Red/blue colorblind-safe palette, good contrast between profiles
72+
- id: VQ-05
73+
name: Layout Balance
74+
score: 5
75+
max: 5
76+
passed: true
77+
comment: Plot fills canvas well, balanced margins, legend well-positioned
78+
- id: VQ-06
79+
name: Axis Labels
80+
score: 2
81+
max: 2
82+
passed: true
83+
comment: 'Descriptive with units: Temperature (°C) and Pressure (hPa)'
84+
- id: VQ-07
85+
name: Grid & Legend
86+
score: 0
87+
max: 2
88+
passed: false
89+
comment: Grid is subtle but legend frame slightly obscures corner of plot
90+
area
91+
spec_compliance:
92+
score: 23
93+
max: 25
94+
items:
95+
- id: SC-01
96+
name: Plot Type
97+
score: 8
98+
max: 8
99+
passed: true
100+
comment: Correct Skew-T Log-P diagram type
101+
- id: SC-02
102+
name: Data Mapping
103+
score: 5
104+
max: 5
105+
passed: true
106+
comment: Pressure on y-axis (log/inverted), temperature with skew transform
107+
on x-axis
108+
- id: SC-03
109+
name: Required Features
110+
score: 4
111+
max: 5
112+
passed: true
113+
comment: Has isotherms, dry adiabats, moist adiabats, mixing ratio lines;
114+
wind barbs optional and not included
115+
- id: SC-04
116+
name: Data Range
117+
score: 3
118+
max: 3
119+
passed: true
120+
comment: Full range from surface to upper atmosphere displayed
121+
- id: SC-05
122+
name: Legend Accuracy
123+
score: 1
124+
max: 2
125+
passed: false
126+
comment: Legend shows profiles but reference line types not in legend
127+
- id: SC-06
128+
name: Title Format
129+
score: 2
130+
max: 2
131+
passed: true
132+
comment: 'Correct format: skewt-logp-atmospheric · seaborn · pyplots.ai'
133+
data_quality:
134+
score: 18
135+
max: 20
136+
items:
137+
- id: DQ-01
138+
name: Feature Coverage
139+
score: 7
140+
max: 8
141+
passed: true
142+
comment: Shows typical mid-latitude summer sounding with decreasing temperature
143+
and dewpoint; temperature inversion near tropopause visible
144+
- id: DQ-02
145+
name: Realistic Context
146+
score: 7
147+
max: 7
148+
passed: true
149+
comment: Realistic atmospheric profile with sensible meteorological data
150+
- id: DQ-03
151+
name: Appropriate Scale
152+
score: 4
153+
max: 5
154+
passed: true
155+
comment: Realistic values; surface temp 25°C and dewpoint 18°C appropriate;
156+
upper atmosphere temps realistic
157+
code_quality:
158+
score: 9
159+
max: 10
160+
items:
161+
- id: CQ-01
162+
name: KISS Structure
163+
score: 3
164+
max: 3
165+
passed: true
166+
comment: 'Simple linear structure: imports → data → plot → save'
167+
- id: CQ-02
168+
name: Reproducibility
169+
score: 3
170+
max: 3
171+
passed: true
172+
comment: Uses np.random.seed(42) for reproducibility
173+
- id: CQ-03
174+
name: Clean Imports
175+
score: 2
176+
max: 2
177+
passed: true
178+
comment: All imports are used
179+
- id: CQ-04
180+
name: No Deprecated API
181+
score: 1
182+
max: 1
183+
passed: true
184+
comment: Uses current seaborn and matplotlib APIs
185+
- id: CQ-05
186+
name: Output Correct
187+
score: 0
188+
max: 1
189+
passed: false
190+
comment: Saves as plot.png but header says 0.13.2 while current seaborn may
191+
differ
192+
library_features:
193+
score: 3
194+
max: 5
195+
items:
196+
- id: LF-01
197+
name: Distinctive Features
198+
score: 3
199+
max: 5
200+
passed: true
201+
comment: Uses sns.lineplot, sns.set_theme, sns.despine, and colorblind palette,
202+
but heavy reliance on matplotlib for the specialized thermodynamic lines
203+
verdict: APPROVED
204+
impl_tags:
205+
dependencies: []
206+
techniques:
207+
- annotations
208+
- manual-ticks
209+
patterns:
210+
- data-generation
211+
- iteration-over-groups
212+
dataprep: []
213+
styling:
214+
- alpha-blending
215+
- grid-styling

0 commit comments

Comments
 (0)