{"spec_id":"skewt-logp-atmospheric","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nskewt-logp-atmospheric: Skew-T Log-P Atmospheric Diagram\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 80/100 | Updated: 2026-05-21\n\"\"\"\n\nimport os\nimport sys\n\n\n# Running as pygal.py: remove script dir from sys.path so 'import pygal' finds the package\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or os.getcwd()) != _this_dir]\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nNEUTRAL = \"#1A1A1A\" if THEME == \"light\" else \"#E8E8E0\"  # position-8 adaptive neutral\n\n# Position 7 (yellow #954477) is prohibited for thin lines on light surfaces;\n# replace with NEUTRAL for the -20°C isotherm reference line\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", NEUTRAL)\n\n# Data — realistic mid-latitude atmospheric sounding\nnp.random.seed(42)\npressure = np.array([1000, 925, 850, 700, 500, 400, 300, 250, 200, 150, 100])\ntemperature = np.array([25, 20, 15, 5, -15, -28, -45, -52, -58, -62, -55])\ndewpoint = np.array([18, 15, 12, -2, -22, -38, -55, -60, -65, -70, -65])\n\nlog_p = np.log10(1000.0 / pressure)\nskew_factor = 35\ntemp_skewed = temperature + skew_factor * log_p\ndewpoint_skewed = dewpoint + skew_factor * log_p\n\n# Physical constants for moist adiabatic lapse rate\nLV = 2.501e6  # Latent heat of vaporization (J/kg)\nRD = 287.05  # Gas constant for dry air (J/kg/K)\nRV = 461.5  # Gas constant for water vapor (J/kg/K)\nCP = 1004.0  # Specific heat of dry air (J/kg/K)\nG = 9.81  # Gravity (m/s^2)\n\n\ndef moist_adiabat(T0_C, pressures):\n    \"\"\"Integrate proper moist adiabatic lapse rate (MALR) along pressure levels.\"\"\"\n    temps = [T0_C]\n    T = T0_C + 273.15\n    for i in range(len(pressures) - 1):\n        p = pressures[i]\n        es = 6.112 * np.exp(17.67 * (T - 273.15) / ((T - 273.15) + 243.5))\n        ws = 0.622 * es / (p - es)  # saturation mixing ratio kg/kg (p, es both hPa)\n        malr = G * (1 + LV * ws / (RD * T)) / (CP + LV**2 * ws / (RV * T**2))  # K/m\n        # dT/dp_hPa = MALR * Rd*T / (g*p_hPa) — hPa units cancel via hydrostatic\n        T += malr * RD * T / (G * p) * (pressures[i + 1] - p)\n        temps.append(T - 273.15)\n    return np.array(temps)\n\n\n# Style\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n)\n\n# Reduced Y-axis label set — skip 925/400/250/150 to avoid crowding at log-compressed bottom\nkey_pressures = [1000, 850, 700, 500, 300, 200, 100]\ny_labels = [{\"value\": float(np.log10(1000.0 / p)), \"label\": str(p)} for p in key_pressures]\n\n# Plot\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"skewt-logp-atmospheric · python · pygal · anyplot.ai\",\n    x_title=\"Temperature (°C, skewed 45°)\",\n    y_title=\"Pressure (hPa)\",\n    show_dots=True,\n    dots_size=6,\n    stroke_style={\"width\": 4},\n    show_x_guides=True,\n    show_y_guides=True,\n    show_legend=True,\n    legend_at_bottom=True,\n    range=(0, 1.02),\n    xrange=(-50, 40),  # narrowed from 75 — reference lines reach at most x≈35\n    y_labels=y_labels,\n    truncate_legend=-1,\n    legend_box_size=28,\n    margin_bottom=220,\n)\n\n# Temperature profile (green #009E73 — series 1, primary data)\ntemp_points = [(float(temp_skewed[i]), float(log_p[i])) for i in range(len(pressure))]\nchart.add(\"Temperature\", temp_points, stroke_style={\"width\": 7})\n\n# Dewpoint profile (vermillion #C475FD — series 2)\ndewpoint_points = [(float(dewpoint_skewed[i]), float(log_p[i])) for i in range(len(pressure))]\nchart.add(\"Dewpoint\", dewpoint_points, stroke_style={\"width\": 6, \"dasharray\": \"15,8\"})\n\n# Dry adiabat θ=300K (blue #4467A3 — series 3)\ntheta = 300\ndry_adiabat_points = []\nfor p in np.linspace(1000, 100, 30):\n    lp = np.log10(1000.0 / p)\n    t_adiabat = theta * (p / 1000.0) ** 0.286 - 273.15\n    t_skewed = t_adiabat + skew_factor * lp\n    if -50 <= t_skewed <= 40:\n        dry_adiabat_points.append((float(t_skewed), float(lp)))\nchart.add(\"Dry Adiabat θ=300K\", dry_adiabat_points, show_dots=False, stroke_style={\"width\": 3, \"dasharray\": \"6,4\"})\n\n# Moist adiabat (reddish purple #BD8233 — series 4, proper MALR integration)\nmoist_pressures = np.linspace(1000, 150, 25)\nmoist_temps = moist_adiabat(20.0, moist_pressures)\nmoist_points = []\nfor T_c, p in zip(moist_temps, moist_pressures, strict=False):\n    lp = np.log10(1000.0 / p)\n    t_skewed = T_c + skew_factor * lp\n    if -50 <= t_skewed <= 40:\n        moist_points.append((float(t_skewed), float(lp)))\nchart.add(\"Moist Adiabat\", moist_points, show_dots=False, stroke_style={\"width\": 3, \"dasharray\": \"10,5\"})\n\n# Mixing ratio r=10g/kg (orange #AE3030 — series 5)\nmr_points = []\nmr = 10\nfor p in np.linspace(1000, 300, 20):\n    lp = np.log10(1000.0 / p)\n    e = mr * p / (622 + mr)\n    if e > 0:\n        td = (243.5 * np.log(e / 6.112)) / (17.67 - np.log(e / 6.112))\n        td_skewed = td + skew_factor * lp\n        if -50 <= td_skewed <= 40:\n            mr_points.append((float(td_skewed), float(lp)))\nchart.add(\"Mixing Ratio r=10g/kg\", mr_points, show_dots=False, stroke_style={\"width\": 2, \"dasharray\": \"4,6\"})\n\n# 0°C Isotherm (sky blue #2ABCCD — series 6)\niso_0_points = []\nfor lp in np.linspace(0, 1.0, 20):\n    t_skewed = 0 + skew_factor * lp\n    if -50 <= t_skewed <= 40:\n        iso_0_points.append((float(t_skewed), float(lp)))\nchart.add(\"0°C Isotherm\", iso_0_points, show_dots=False, stroke_style={\"width\": 2, \"dasharray\": \"8,4\"})\n\n# -20°C Isotherm (NEUTRAL — series 7, replaces yellow which has insufficient contrast on light bg)\niso_m20_points = []\nfor lp in np.linspace(0, 1.0, 20):\n    t_skewed = -20 + skew_factor * lp\n    if -50 <= t_skewed <= 40:\n        iso_m20_points.append((float(t_skewed), float(lp)))\nchart.add(\"-20°C Isotherm\", iso_m20_points, show_dots=False, stroke_style={\"width\": 2, \"dasharray\": \"8,4\"})\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}