{"spec_id":"phase-diagram-pt","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nphase-diagram-pt: Thermodynamic Phase Diagram (Pressure-Temperature)\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-08\n\"\"\"\n\n# Ensure we import the installed pygal package, not this file\nimport importlib.util\nimport os\nimport sys\nimport xml.etree.ElementTree as ET\n\nimport cairosvg\nimport numpy as np\n\n\npygal_spec = importlib.util.find_spec(\"pygal\")\nif pygal_spec and pygal_spec.origin != __file__:\n    import pygal\n    from pygal.style import Style\nelse:\n    _cwd = os.getcwd()\n    sys.path = [p for p in sys.path if os.path.abspath(p) != _cwd]\n    try:\n        import pygal\n        from pygal.style import Style\n    finally:\n        sys.path.insert(0, _cwd)\n\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\"\n\n# Imprint palette — theme-independent categorical colors\nIMPRINT_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Water phase diagram (physically accurate constants)\ntriple_t = 273.16  # K\ntriple_p = 611.73  # Pa\ncritical_t = 647.1  # K\ncritical_p = 22.064e6  # Pa\nR = 8.314  # J/(mol·K)\n\n# Sublimation curve (Solid-Gas): from 200 K to triple point\nsublimation_temps = np.linspace(200, triple_t, 80)\nL_sub = 51059  # J/mol — sublimation enthalpy of water\nsublimation_pressures = triple_p * np.exp((L_sub / R) * (1 / triple_t - 1 / sublimation_temps))\n\n# Vaporization curve (Liquid-Gas): from triple point to critical point\nvaporization_temps = np.linspace(triple_t, critical_t, 100)\nL_vap = 40700  # J/mol — vaporization enthalpy of water\nvaporization_pressures = triple_p * np.exp((L_vap / R) * (1 / triple_t - 1 / vaporization_temps))\n\n# Melting curve (Solid-Liquid): water has anomalous negative slope\nmelting_pressures = np.logspace(np.log10(triple_p), np.log10(critical_p * 5), 80)\nmelting_temps = triple_t - (melting_pressures - triple_p) * 7.5e-8\n\n\n# Pressure formatter — handles scalar and (x, y) tuple passed by pygal's XY formatter\ndef fmt_p(v):\n    p = v[1] if isinstance(v, (list, tuple)) else v\n    return f\"{p / 1e6:.1f} MPa\" if p >= 1e6 else (f\"{p / 1e3:.1f} kPa\" if p >= 1e3 else f\"{p:.1f} Pa\")\n\n\n# Title: 46 chars < 67 baseline → no scaling needed, use default 66\ntitle = \"phase-diagram-pt · python · pygal · anyplot.ai\"\n\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_PALETTE,\n    font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    title_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    value_font_size=36,\n    legend_font_size=44,\n    legend_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    label_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    major_label_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    value_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    stroke_width=5,\n    opacity=0.92,\n    opacity_hover=1.0,\n    guide_stroke_color=INK_MUTED,\n    guide_stroke_dasharray=\"6,6\",\n    tooltip_font_size=36,\n    tooltip_font_family=\"DejaVu Sans, Helvetica, Arial, sans-serif\",\n    tooltip_border_radius=8,\n)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"Temperature (K)\",\n    y_title=\"Pressure (Pa)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_box_size=22,\n    dots_size=2,\n    stroke=True,\n    show_x_guides=True,\n    show_y_guides=True,\n    logarithmic=True,\n    explicit_size=True,\n    truncate_legend=-1,\n    spacing=20,\n    margin=40,\n    margin_bottom=120,\n    margin_left=220,\n    margin_right=160,\n    tooltip_fancy_mode=True,\n    tooltip_border_radius=8,\n    interpolate=\"cubic\",\n    interpolation_precision=200,\n    xrange=(180, 720),\n    print_values=False,\n    human_readable=True,\n    x_value_formatter=lambda x: f\"{x:.0f} K\",\n    value_formatter=fmt_p,\n    y_labels=[1, 100, 1e4, 1e6, 1e8],\n    y_label_rotation=0,\n)\n\n# Series 1–3: phase boundary curves — Imprint positions 1 (green), 2 (lavender), 3 (blue)\nsublimation_points = [\n    {\"value\": (float(t), float(p)), \"label\": f\"Sublimation: {t:.0f} K, {fmt_p(p)}\"}\n    for t, p in zip(sublimation_temps[::4], sublimation_pressures[::4], strict=True)\n]\nchart.add(\n    \"Solid ↔ Gas (Sublimation)\",\n    sublimation_points,\n    show_dots=False,\n    stroke_style={\"width\": 8, \"linecap\": \"round\", \"linejoin\": \"round\"},\n    formatter=fmt_p,\n)\n\nvaporization_points = [\n    {\"value\": (float(t), float(p)), \"label\": f\"Vaporization: {t:.0f} K, {fmt_p(p)}\"}\n    for t, p in zip(vaporization_temps[::5], vaporization_pressures[::5], strict=True)\n]\nchart.add(\n    \"Liquid ↔ Gas (Vaporization)\",\n    vaporization_points,\n    show_dots=False,\n    stroke_style={\"width\": 8, \"linecap\": \"round\", \"linejoin\": \"round\"},\n    formatter=fmt_p,\n)\n\nmelting_points = [\n    {\"value\": (float(t), float(p)), \"label\": f\"Melting: {t:.2f} K, {fmt_p(p)}\"}\n    for t, p in zip(melting_temps[::4], melting_pressures[::4], strict=True)\n]\nchart.add(\n    \"Solid ↔ Liquid (Melting)\",\n    melting_points,\n    show_dots=False,\n    stroke_style={\"width\": 8, \"linecap\": \"round\", \"linejoin\": \"round\"},\n    formatter=fmt_p,\n)\n\n# Landmark points — series order controls legend swatch via palette cycling.\n# Series 4 → palette[3] = ochre #BD8233 → Critical Point (no per-point override needed)\n# Series 5 → palette[4] = matte red #AE3030 → Triple Point (no per-point override needed)\nchart.add(\n    f\"Critical Point ({critical_t} K, {fmt_p(critical_p)})\",\n    [\n        {\n            \"value\": (float(critical_t), float(critical_p)),\n            \"label\": f\"Critical Point — liquid-gas distinction vanishes\\n{critical_t} K, {fmt_p(critical_p)}\",\n        }\n    ],\n    dots_size=18,\n    stroke=False,\n    formatter=fmt_p,\n)\n\nchart.add(\n    f\"Triple Point ({triple_t} K, {triple_p:.0f} Pa)\",\n    [\n        {\n            \"value\": (float(triple_t), float(triple_p)),\n            \"label\": f\"Triple Point — all three phases coexist\\n{triple_t} K, {triple_p:.0f} Pa\",\n        }\n    ],\n    dots_size=18,\n    stroke=False,\n    formatter=fmt_p,\n)\n\n# SVG post-processing: frame removal, phase region labels, callout annotations\nsvg_string = chart.render(is_unicode=True)\nroot = ET.fromstring(svg_string)\nns = \"http://www.w3.org/2000/svg\"\n\n# Remove default chart frame border via CSS override\nstyle_elems = root.findall(f\".//{{{ns}}}style\")\nframe_css = \"\\nrect.background { stroke: none !important; stroke-width: 0 !important; }\"\nif style_elems:\n    style_elems[0].text = (style_elems[0].text or \"\") + frame_css\n\n# Phase region labels — semi-transparent, color-coded by boundary membership\nphase_labels = [\n    {\"text\": \"SOLID\", \"x\": \"600\", \"y\": \"580\", \"size\": \"52\", \"color\": IMPRINT_PALETTE[2], \"opacity\": \"0.35\"},\n    {\"text\": \"LIQUID\", \"x\": \"1370\", \"y\": \"400\", \"size\": \"52\", \"color\": IMPRINT_PALETTE[1], \"opacity\": \"0.35\"},\n    {\"text\": \"GAS\", \"x\": \"1890\", \"y\": \"1050\", \"size\": \"52\", \"color\": IMPRINT_PALETTE[0], \"opacity\": \"0.35\"},\n    {\"text\": \"SUPERCRITICAL\", \"x\": \"2730\", \"y\": \"155\", \"size\": \"36\", \"color\": IMPRINT_PALETTE[3], \"opacity\": \"0.30\"},\n    {\"text\": \"FLUID\", \"x\": \"2730\", \"y\": \"193\", \"size\": \"36\", \"color\": IMPRINT_PALETTE[3], \"opacity\": \"0.30\"},\n]\n\nfor lbl in phase_labels:\n    el = ET.SubElement(root, f\"{{{ns}}}text\")\n    el.set(\"x\", lbl[\"x\"])\n    el.set(\"y\", lbl[\"y\"])\n    el.set(\n        \"style\",\n        f\"font-family:DejaVu Sans,Helvetica,Arial,sans-serif;\"\n        f\"font-size:{lbl['size']}px;fill:{lbl['color']};font-weight:700;\"\n        f\"letter-spacing:4px;opacity:{lbl['opacity']};text-anchor:middle;\",\n    )\n    el.text = lbl[\"text\"]\n\n# Callout annotations for thermodynamic landmarks\n# Triple point: approx SVG (760, 1040) — callout to upper-right\n# Critical point: approx SVG (2640, 250) — callout to lower-left\ncallouts = [\n    # (x_dot, y_dot, x_end, y_end, x_txt, y_txt1, y_txt2, label, sublabel, anchor)\n    (760, 1040, 860, 960, 875, 954, 994, \"Triple Point\", \"273.16 K  ·  611.73 Pa\", \"start\"),\n    (2640, 250, 2510, 320, 2495, 314, 354, \"Critical Point\", \"647.1 K  ·  22.064 MPa\", \"end\"),\n]\nfor x_d, y_d, x2, y2, x_t, y_t1, y_t2, label, sublabel, anchor in callouts:\n    g = ET.SubElement(root, f\"{{{ns}}}g\")\n    ln = ET.SubElement(g, f\"{{{ns}}}line\")\n    ln.set(\"x1\", str(x_d))\n    ln.set(\"y1\", str(y_d))\n    ln.set(\"x2\", str(x2))\n    ln.set(\"y2\", str(y2))\n    ln.set(\"style\", f\"stroke:{INK_MUTED};stroke-width:2;opacity:0.7;\")\n    t1 = ET.SubElement(g, f\"{{{ns}}}text\")\n    t1.set(\"x\", str(x_t))\n    t1.set(\"y\", str(y_t1))\n    t1.set(\n        \"style\",\n        f\"font-family:DejaVu Sans,Helvetica,Arial,sans-serif;\"\n        f\"font-size:36px;fill:{INK};font-weight:600;text-anchor:{anchor};\",\n    )\n    t1.text = label\n    t2 = ET.SubElement(g, f\"{{{ns}}}text\")\n    t2.set(\"x\", str(x_t))\n    t2.set(\"y\", str(y_t2))\n    t2.set(\n        \"style\",\n        f\"font-family:DejaVu Sans,Helvetica,Arial,sans-serif;\"\n        f\"font-size:30px;fill:{INK_MUTED};font-weight:400;text-anchor:{anchor};\",\n    )\n    t2.text = sublabel\n\nmodified_svg = ET.tostring(root, encoding=\"unicode\")\n\ncairosvg.svg2png(\n    bytestring=modified_svg.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\", output_width=3200, output_height=1800\n)\n\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}