{"spec_id":"phase-diagram-pt","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nphase-diagram-pt: Thermodynamic Phase Diagram (Pressure-Temperature)\nLibrary: altair 6.2.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-08\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Drop script directory from sys.path so `altair` resolves the package, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\n\n# Theme tokens — Imprint palette\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\"\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data — Water phase diagram (representative thermodynamic values)\nT_triple = 273.16  # K\nP_triple = 611.73  # Pa\nT_critical = 647.1  # K\nP_critical = 2.2064e7  # Pa\nL_sub = 51059  # J/mol sublimation enthalpy (ice)\nL_vap = 40700  # J/mol vaporization enthalpy (water)\nR = 8.314\n\n# Solid-gas boundary (sublimation) — Clausius-Clapeyron approximation\nT_sg = np.linspace(210, T_triple, 80)\nP_sg = P_triple * np.exp((L_sub / R) * (1 / T_triple - 1 / T_sg))\nmask_sg = P_sg >= 1\nT_sg, P_sg = T_sg[mask_sg], P_sg[mask_sg]\n\n# Liquid-gas boundary (vaporization) — triple point to critical point\nT_lg = np.linspace(T_triple, T_critical, 100)\nP_lg = P_triple * np.exp((L_vap / R) * (1 / T_triple - 1 / T_lg))\n\n# Solid-liquid boundary (melting) — negative slope is water's anomaly\nP_sl = np.linspace(P_triple, 1e9, 80)\nT_sl = T_triple - (P_sl - P_triple) * 7.5e-9\n\ndf_sg = pd.DataFrame({\"temperature\": T_sg, \"pressure\": P_sg, \"boundary\": \"Solid–Gas\"})\ndf_lg = pd.DataFrame({\"temperature\": T_lg, \"pressure\": P_lg, \"boundary\": \"Liquid–Gas\"})\ndf_sl = pd.DataFrame({\"temperature\": T_sl, \"pressure\": P_sl, \"boundary\": \"Solid–Liquid\"})\ndf_boundaries = pd.concat([df_sg, df_lg, df_sl], ignore_index=True)\n\n# Phase region shading (approximate rectangular extents)\ndf_regions = pd.DataFrame(\n    {\n        \"t1\": [200, T_triple, T_triple],\n        \"t2\": [T_triple, 700, 700],\n        \"p1\": [1, 1, P_triple],\n        \"p2\": [2e9, P_critical, 2e9],\n        \"phase\": [\"Solid\", \"Gas\", \"Liquid\"],\n    }\n)\n\n# Triple and critical points\ndf_points = pd.DataFrame(\n    {\n        \"temperature\": [T_triple, T_critical],\n        \"pressure\": [P_triple, P_critical],\n        \"label\": [\"Triple Point\\n273.16 K, 611.7 Pa\", \"Critical Point\\n647.1 K, 22.06 MPa\"],\n        \"point_type\": [\"Triple Point\", \"Critical Point\"],\n    }\n)\n\n# Phase region text labels\ndf_labels = pd.DataFrame(\n    {\"temperature\": [232, 480, 480], \"pressure\": [1e6, 1e8, 25], \"phase\": [\"SOLID\", \"LIQUID\", \"GAS\"]}\n)\n\n# Shared axis scales and encodings\nx_scale = alt.Scale(domain=[200, 700])\ny_scale = alt.Scale(type=\"log\", domain=[1, 2e9])\nx_enc = alt.X(\"temperature:Q\", title=\"Temperature (K)\", scale=x_scale)\ny_enc = alt.Y(\"pressure:Q\", title=\"Pressure (Pa)\", scale=y_scale)\n\n# Imprint palette positions 1–3 for the three phase boundaries\nboundary_scale = alt.Scale(\n    domain=[\"Solid–Gas\", \"Liquid–Gas\", \"Solid–Liquid\"], range=[IMPRINT[0], IMPRINT[1], IMPRINT[2]]\n)\n\nhover = alt.selection_point(fields=[\"boundary\"], on=\"pointerover\", empty=\"all\")\n\n# Phase region fills (same Imprint colors at low opacity for coherence)\nregions = (\n    alt.Chart(df_regions)\n    .mark_rect(opacity=0.10)\n    .encode(\n        x=alt.X(\"t1:Q\", scale=x_scale),\n        x2=\"t2:Q\",\n        y=alt.Y(\"p1:Q\", scale=y_scale),\n        y2=\"p2:Q\",\n        color=alt.Color(\n            \"phase:N\",\n            scale=alt.Scale(domain=[\"Solid\", \"Gas\", \"Liquid\"], range=[IMPRINT[0], IMPRINT[1], IMPRINT[2]]),\n            legend=None,\n        ),\n    )\n)\n\n# Phase region text (large bold labels at low opacity)\nphase_text = (\n    alt.Chart(df_labels)\n    .mark_text(fontSize=18, fontWeight=\"bold\", opacity=0.35)\n    .encode(\n        x=alt.X(\"temperature:Q\", scale=x_scale),\n        y=alt.Y(\"pressure:Q\", scale=y_scale),\n        text=\"phase:N\",\n        color=alt.Color(\n            \"phase:N\",\n            scale=alt.Scale(domain=[\"SOLID\", \"LIQUID\", \"GAS\"], range=[IMPRINT[0], IMPRINT[1], IMPRINT[2]]),\n            legend=None,\n        ),\n    )\n)\n\n# Phase boundary lines with hover highlight\nlines = (\n    alt.Chart(df_boundaries)\n    .mark_line()\n    .encode(\n        x=x_enc,\n        y=y_enc,\n        color=alt.Color(\"boundary:N\", scale=boundary_scale, legend=alt.Legend(title=\"Phase Boundary\")),\n        opacity=alt.condition(hover, alt.value(1.0), alt.value(0.5)),\n        strokeWidth=alt.condition(hover, alt.value(5.0), alt.value(3.5)),\n        tooltip=[\n            alt.Tooltip(\"temperature:Q\", title=\"Temperature (K)\", format=\".1f\"),\n            alt.Tooltip(\"pressure:Q\", title=\"Pressure (Pa)\", format=\".2e\"),\n            alt.Tooltip(\"boundary:N\", title=\"Boundary\"),\n        ],\n    )\n    .add_params(hover)\n)\n\n# Triple and critical points — distinct shapes, Imprint positions 4–5\npoints = (\n    alt.Chart(df_points)\n    .mark_point(size=300, filled=True, stroke=PAGE_BG, strokeWidth=2)\n    .encode(\n        x=x_enc,\n        y=y_enc,\n        shape=alt.Shape(\n            \"point_type:N\",\n            scale=alt.Scale(domain=[\"Triple Point\", \"Critical Point\"], range=[\"diamond\", \"square\"]),\n            legend=None,\n        ),\n        color=alt.Color(\n            \"point_type:N\",\n            scale=alt.Scale(domain=[\"Triple Point\", \"Critical Point\"], range=[IMPRINT[3], IMPRINT[4]]),\n            legend=None,\n        ),\n        tooltip=[alt.Tooltip(\"label:N\", title=\"Point\")],\n    )\n)\n\ndf_tp = df_points[df_points[\"point_type\"] == \"Triple Point\"]\ndf_cp = df_points[df_points[\"point_type\"] == \"Critical Point\"]\n\ntp_label = (\n    alt.Chart(df_tp)\n    .mark_text(fontSize=11, fontWeight=\"bold\", align=\"left\", dx=16, dy=-8, lineBreak=\"\\n\")\n    .encode(x=x_enc, y=y_enc, text=\"label:N\", color=alt.value(INK))\n)\n\ncp_label = (\n    alt.Chart(df_cp)\n    .mark_text(fontSize=11, fontWeight=\"bold\", align=\"right\", dx=-16, dy=-8, lineBreak=\"\\n\")\n    .encode(x=x_enc, y=y_enc, text=\"label:N\", color=alt.value(INK))\n)\n\n# Title — len(\"phase-diagram-pt · python · altair · anyplot.ai\") = 47 < 67, use default 16px\ntitle_str = \"phase-diagram-pt · python · altair · anyplot.ai\"\n\nchart = (\n    alt.layer(regions, phase_text, lines, points, tp_label, cp_label)\n    .resolve_scale(color=\"independent\", shape=\"independent\")\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            title_str,\n            fontSize=16,\n            fontWeight=\"bold\",\n            color=INK,\n            subtitle=\"Water Pressure–Temperature Phase Diagram\",\n            subtitleFontSize=13,\n            subtitleColor=INK_SOFT,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        gridOpacity=0.12,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        gridColor=INK,\n    )\n    .configure_legend(\n        titleFontSize=10,\n        labelFontSize=10,\n        symbolSize=150,\n        titleLimit=300,\n        labelLimit=300,\n        orient=\"bottom-right\",\n        padding=8,\n        cornerRadius=4,\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n)\n\n# Save PNG and pad to exact 3200×1800 canvas with PAGE_BG fill\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save interactive HTML\nchart.interactive().save(f\"plot-{THEME}.html\")\n"}