{"spec_id":"skewt-logp-atmospheric","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nskewt-logp-atmospheric: Skew-T Log-P Atmospheric Diagram\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-21\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import Patch\nfrom matplotlib.ticker import ScalarFormatter\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data: tropical atmospheric sounding — 19 levels from surface (1000 hPa) to stratosphere (50 hPa)\nnp.random.seed(42)\npressure = np.array([1000, 950, 925, 900, 850, 800, 750, 700, 650, 600, 500, 400, 300, 250, 200, 150, 100, 70, 50])\ntemperature = np.array([30, 26, 23, 21, 18, 14, 10, 6, 2, -3, -12, -22, -38, -52, -60, -65, -75, -65, -55])\ndewpoint = np.array([26, 22, 18, 15, 10, 4, -2, -10, -18, -25, -38, -52, -60, -70, -75, -80, -85, -90, -90])\n\n# Figure with logarithmic inverted pressure axis\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\nax.set_yscale(\"log\")\nax.set_ylim(1050, 45)\nax.set_xlim(-50, 58)\n\n# Skew-T transform: x_plot = T + tan(45°) * ln(p0/p)\nskew_slope = np.tan(np.radians(45))\np_bg = np.logspace(np.log10(45), np.log10(1050), 150)\n\n# Isotherms — seaborn lineplot with units for multi-line rendering (seaborn-idiomatic)\niso_frames = []\nfor t in np.arange(-80, 65, 10):\n    x_iso = t + skew_slope * np.log(1000 / p_bg)\n    iso_frames.append(pd.DataFrame({\"x\": x_iso, \"pressure\": p_bg, \"T\": t}))\ndf_iso = pd.concat(iso_frames, ignore_index=True)\nsns.lineplot(\n    data=df_iso,\n    x=\"x\",\n    y=\"pressure\",\n    units=\"T\",\n    estimator=None,\n    color=INK_SOFT,\n    linewidth=0.4,\n    alpha=0.30,\n    ax=ax,\n    zorder=1,\n    legend=False,\n)\n\n# Dry adiabats (potential temperature θ = const) — seaborn lineplot\ndry_frames = []\nfor theta in np.arange(250, 470, 20):\n    t_dry = theta * (p_bg / 1000) ** 0.286 - 273.15\n    x_dry = t_dry + skew_slope * np.log(1000 / p_bg)\n    dry_frames.append(pd.DataFrame({\"x\": x_dry, \"pressure\": p_bg, \"theta\": theta}))\ndf_dry = pd.concat(dry_frames, ignore_index=True)\nsns.lineplot(\n    data=df_dry,\n    x=\"x\",\n    y=\"pressure\",\n    units=\"theta\",\n    estimator=None,\n    color=IMPRINT[4],\n    linewidth=0.5,\n    alpha=0.50,\n    ax=ax,\n    zorder=1,\n    legend=False,\n)\n\n# Moist adiabats (equivalent potential temperature θ_e = const) — seaborn lineplot\nmoist_frames = []\nfor theta_e in np.arange(270, 400, 20):\n    t_moist = (theta_e - 30) * (p_bg / 1000) ** 0.30 - 273.15\n    x_moist = t_moist + skew_slope * np.log(1000 / p_bg)\n    moist_frames.append(pd.DataFrame({\"x\": x_moist, \"pressure\": p_bg, \"theta_e\": theta_e}))\ndf_moist = pd.concat(moist_frames, ignore_index=True)\nsns.lineplot(\n    data=df_moist,\n    x=\"x\",\n    y=\"pressure\",\n    units=\"theta_e\",\n    estimator=None,\n    color=IMPRINT[2],\n    linewidth=0.5,\n    alpha=0.40,\n    linestyle=\"--\",\n    ax=ax,\n    zorder=1,\n    legend=False,\n)\n\n# Mixing ratio lines (w = const, g/kg) — lower troposphere only — seaborn lineplot\np_mix = np.logspace(np.log10(400), np.log10(1050), 60)\nmix_frames = []\nfor w in [1, 2, 4, 7, 10, 16, 24]:\n    t_mix = 35 * np.log10(w) - 20 + 5 * np.log10(p_mix / 1000)\n    x_mix = t_mix + skew_slope * np.log(1000 / p_mix)\n    mix_frames.append(pd.DataFrame({\"x\": x_mix, \"pressure\": p_mix, \"w\": w}))\ndf_mix = pd.concat(mix_frames, ignore_index=True)\nsns.lineplot(\n    data=df_mix,\n    x=\"x\",\n    y=\"pressure\",\n    units=\"w\",\n    estimator=None,\n    color=IMPRINT[3],\n    linewidth=0.5,\n    alpha=0.40,\n    linestyle=\":\",\n    ax=ax,\n    zorder=1,\n    legend=False,\n)\n\n# Apply skew transform to sounding data\nx_temp = temperature + skew_slope * np.log(1000 / pressure)\nx_dew = dewpoint + skew_slope * np.log(1000 / pressure)\n\n# CAPE region: shade area between temperature and dewpoint profiles (T > Td = instability)\nax.fill_betweenx(pressure, x_dew, x_temp, alpha=0.12, color=IMPRINT[4], zorder=2)\n\ndf = pd.DataFrame(\n    {\n        \"x\": np.concatenate([x_temp, x_dew]),\n        \"pressure\": np.concatenate([pressure, pressure]),\n        \"profile\": [\"Temperature\"] * len(pressure) + [\"Dewpoint\"] * len(pressure),\n    }\n)\n\n# Plot sounding profiles with seaborn lineplot (hue + style + markers = seaborn-idiomatic)\nsns.lineplot(\n    data=df,\n    x=\"x\",\n    y=\"pressure\",\n    hue=\"profile\",\n    style=\"profile\",\n    markers={\"Temperature\": \"o\", \"Dewpoint\": \"s\"},\n    dashes={\"Temperature\": \"\", \"Dewpoint\": (5, 2)},\n    palette={\"Temperature\": IMPRINT[0], \"Dewpoint\": IMPRINT[1]},\n    linewidth=3,\n    markersize=6,\n    ax=ax,\n    zorder=5,\n    legend=False,\n)\n\n# Pressure axis ticks and formatting\nax.yaxis.set_major_formatter(ScalarFormatter())\ndisplay_ticks = [1000, 850, 700, 500, 400, 300, 250, 200, 150, 100, 70, 50]\nax.set_yticks(display_ticks)\nax.set_yticklabels([str(p) for p in display_ticks])\n\n# Labels and title\nax.set_xlabel(\"Temperature (°C)\", fontsize=10, color=INK)\nax.set_ylabel(\"Pressure (hPa)\", fontsize=10, color=INK)\nax.set_title(\"skewt-logp-atmospheric · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nsns.despine(ax=ax, top=True, right=True)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.yaxis.grid(True, alpha=0.10, linewidth=0.5, color=INK)\n\n# Legend — upper-left where 100-200 hPa data is sparse (reduces crowding with mixing ratio lines)\nlegend_handles = [\n    Line2D([0], [0], color=IMPRINT[0], lw=2.5, marker=\"o\", markersize=5, label=\"Temperature\"),\n    Line2D([0], [0], color=IMPRINT[1], lw=2.5, linestyle=(0, (5, 2)), marker=\"s\", markersize=5, label=\"Dewpoint\"),\n    Patch(facecolor=IMPRINT[4], alpha=0.30, edgecolor=\"none\", label=\"CAPE Region\"),\n    Line2D([0], [0], color=INK_SOFT, lw=0.8, alpha=0.6, label=\"Isotherms\"),\n    Line2D([0], [0], color=IMPRINT[4], lw=0.8, alpha=0.7, label=\"Dry Adiabats\"),\n    Line2D([0], [0], color=IMPRINT[2], lw=0.8, alpha=0.6, linestyle=\"--\", label=\"Moist Adiabats\"),\n    Line2D([0], [0], color=IMPRINT[3], lw=0.8, alpha=0.6, linestyle=\":\", label=\"Mixing Ratio\"),\n]\nax.legend(\n    handles=legend_handles, loc=\"upper left\", fontsize=8, framealpha=0.92, facecolor=ELEVATED_BG, edgecolor=INK_SOFT\n)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}