Thermodynamic Phase Diagram (Pressure-Temperature) — Plotly

A pressure-temperature (P-T) phase diagram showing the boundaries between solid, liquid, and gas phases of a substance. The diagram includes the triple point where all three phases coexist and the critical point beyond which the liquid-gas distinction vanishes. This is one of the most fundamental diagrams in chemistry and physics, essential for understanding phase transitions and states of matter.

Thermodynamic Phase Diagram (Pressure-Temperature) rendered with Plotly

Python source (Plotly)

""" anyplot.ai
phase-diagram-pt: Thermodynamic Phase Diagram (Pressure-Temperature)
Library: plotly 6.8.0 | Python 3.13.13
Quality: 91/100 | Updated: 2026-06-08
"""

import os

import numpy as np
import plotly.graph_objects as go


# Theme tokens (see prompts/default-style-guide.md)
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"

# Imprint palette — hybrid-v3 canonical order
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"]

# Data — Water phase diagram with real Clausius-Clapeyron constants
# Triple point: 273.16 K, 611.73 Pa | Critical point: 647.096 K, 22.064 MPa
triple_T = 273.16
triple_P = 611.73
critical_T = 647.096
critical_P = 22.064e6
R = 8.314  # J/(mol·K)

# Solid-gas boundary (sublimation curve) — triple point down to 200 K
T_solid_gas = np.linspace(200, triple_T, 80)
L_sub = 51059  # J/mol (sublimation enthalpy of water)
P_solid_gas = triple_P * np.exp((L_sub / R) * (1 / triple_T - 1 / T_solid_gas))

# Liquid-gas boundary (vaporization curve) — triple point to critical point
T_liquid_gas = np.linspace(triple_T, critical_T, 100)
L_vap = 40670  # J/mol (vaporization enthalpy of water)
P_liquid_gas = triple_P * np.exp((L_vap / R) * (1 / triple_T - 1 / T_liquid_gas))

# Solid-liquid boundary (melting curve) — negative slope unique to water
P_solid_liquid = np.logspace(np.log10(triple_P), np.log10(1e10), 80)
dT_dP = -7.4e-8  # K/Pa (anomalous negative slope for water)
T_solid_liquid = triple_T + dT_dP * (P_solid_liquid - triple_P)

# Axis bounds
x_min, x_max = 180, 800
y_log_min, y_log_max = 0, 10  # log10(Pa)

# Title font size — scale down for long title
title_str = "Water P-T Phase Diagram · phase-diagram-pt · python · plotly · anyplot.ai"
n = len(title_str)
title_fs = max(11, round(16 * (67 / n if n > 67 else 1.0)))

# Plot
fig = go.Figure()

# Phase region fills using Imprint palette at low opacity
# GAS region — below sublimation and vaporization curves
gas_T = np.concatenate([[x_min], T_solid_gas, T_liquid_gas, [x_max, x_max, x_min]])
gas_P = np.concatenate([[10**y_log_min], P_solid_gas, P_liquid_gas, [10**y_log_min, 10**y_log_min, 10**y_log_min]])
fig.add_trace(
    go.Scatter(
        x=gas_T,
        y=gas_P,
        fill="toself",
        mode="lines",
        line={"width": 0},
        fillcolor="rgba(189,130,51,0.10)",  # Imprint ochre #BD8233 — warm/gas
        showlegend=False,
        hoverinfo="skip",
    )
)

# SOLID region — left of melting curve, above sublimation curve
solid_T = np.concatenate([T_solid_gas[::-1], [x_min, x_min], T_solid_liquid[T_solid_liquid >= x_min][::-1]])
solid_P = np.concatenate(
    [P_solid_gas[::-1], [P_solid_gas[0], 10**y_log_max], P_solid_liquid[T_solid_liquid >= x_min][::-1]]
)
fig.add_trace(
    go.Scatter(
        x=solid_T,
        y=solid_P,
        fill="toself",
        mode="lines",
        line={"width": 0},
        fillcolor="rgba(68,103,163,0.12)",  # Imprint blue #4467A3 — cold/solid
        showlegend=False,
        hoverinfo="skip",
    )
)

# LIQUID region — between melting and vaporization curves
liquid_mask = (T_solid_liquid >= x_min) & (P_solid_liquid <= critical_P)
liquid_T = np.concatenate([T_liquid_gas, [critical_T], T_solid_liquid[liquid_mask][::-1]])
liquid_P = np.concatenate([P_liquid_gas, [critical_P], P_solid_liquid[liquid_mask][::-1]])
fig.add_trace(
    go.Scatter(
        x=liquid_T,
        y=liquid_P,
        fill="toself",
        mode="lines",
        line={"width": 0},
        fillcolor="rgba(196,117,253,0.10)",  # Imprint lavender #C475FD — liquid
        showlegend=False,
        hoverinfo="skip",
    )
)

# Supercritical region — above and right of critical point
fig.add_trace(
    go.Scatter(
        x=[critical_T, x_max, x_max, critical_T, critical_T],
        y=[critical_P, critical_P, 10**y_log_max, 10**y_log_max, critical_P],
        fill="toself",
        mode="lines",
        line={"width": 0},
        fillcolor="rgba(221,204,119,0.15)",  # Imprint amber #DDCC77 — supercritical
        showlegend=False,
        hoverinfo="skip",
    )
)

# Phase boundary curves — Imprint palette positions 1→3
fig.add_trace(
    go.Scatter(
        x=T_solid_gas,
        y=P_solid_gas,
        mode="lines",
        line={"color": IMPRINT[0], "width": 3.5},  # #009E73 green — sublimation
        name="Sublimation curve",
        hovertemplate="<b>Sublimation</b><br>T: %{x:.1f} K<br>P: %{y:.2e} Pa<extra></extra>",
    )
)

fig.add_trace(
    go.Scatter(
        x=T_liquid_gas,
        y=P_liquid_gas,
        mode="lines",
        line={"color": IMPRINT[1], "width": 3.5},  # #C475FD lavender — vaporization
        name="Vaporization curve",
        hovertemplate="<b>Vaporization</b><br>T: %{x:.1f} K<br>P: %{y:.2e} Pa<extra></extra>",
    )
)

fig.add_trace(
    go.Scatter(
        x=T_solid_liquid,
        y=P_solid_liquid,
        mode="lines",
        line={"color": IMPRINT[2], "width": 3.5},  # #4467A3 blue — melting
        name="Melting curve",
        hovertemplate="<b>Melting</b><br>T: %{x:.1f} K<br>P: %{y:.2e} Pa<extra></extra>",
    )
)

# Triple point marker — Imprint matte red (semantic: special critical state)
fig.add_trace(
    go.Scatter(
        x=[triple_T],
        y=[triple_P],
        mode="markers",
        marker={
            "size": 16,
            "color": IMPRINT[4],  # #AE3030 matte red
            "symbol": "diamond",
            "line": {"color": PAGE_BG, "width": 2},
        },
        name="Triple point",
        hovertemplate="Triple Point<br>T: 273.16 K<br>P: 611.73 Pa<extra></extra>",
    )
)

# Critical point marker — Imprint ochre (warm, distinct from red)
fig.add_trace(
    go.Scatter(
        x=[critical_T],
        y=[critical_P],
        mode="markers",
        marker={
            "size": 16,
            "color": IMPRINT[3],  # #BD8233 ochre
            "symbol": "star",
            "line": {"color": PAGE_BG, "width": 2},
        },
        name="Critical point",
        hovertemplate="Critical Point<br>T: 647.1 K<br>P: 2.206×10⁷ Pa<extra></extra>",
    )
)

# Phase region labels — INK_MUTED so they sit behind the data visually
label_font = {"size": 22, "color": INK_MUTED, "family": "Arial Black"}
# annotation y uses log10 values because yaxis.type='log' uses log10 coordinate space
fig.add_annotation(x=225, y=np.log10(3e4), text="SOLID", font=label_font, showarrow=False, yref="y")
fig.add_annotation(x=430, y=np.log10(5e6), text="LIQUID", font=label_font, showarrow=False, yref="y")
fig.add_annotation(x=450, y=np.log10(30), text="GAS", font=label_font, showarrow=False, yref="y")
fig.add_annotation(
    x=700,
    y=9.75,
    text="Supercritical<br>Fluid",
    font={"size": 16, "color": INK_MUTED, "family": "Arial Black"},
    showarrow=False,
    yref="y",
    xanchor="center",
)

# Triple point annotation — placed right and above to avoid crowding with converging curves
fig.add_annotation(
    x=triple_T,
    y=np.log10(triple_P),
    yref="y",
    text="Triple Point<br>(273.16 K, 611.73 Pa)",
    font={"size": 12, "color": INK},
    bgcolor=ELEVATED_BG,
    bordercolor=IMPRINT[4],
    borderwidth=1,
    ax=90,
    ay=-60,
    arrowhead=2,
    arrowsize=1.2,
    arrowwidth=1.5,
    arrowcolor=IMPRINT[4],
)

# Critical point annotation — points left into liquid region for space
fig.add_annotation(
    x=critical_T,
    y=np.log10(critical_P),
    yref="y",
    text="Critical Point<br>(647.1 K, 2.206×10⁷ Pa)",
    font={"size": 12, "color": INK},
    bgcolor=ELEVATED_BG,
    bordercolor=IMPRINT[3],
    borderwidth=1,
    ax=-120,
    ay=-30,
    arrowhead=2,
    arrowsize=1.2,
    arrowwidth=1.5,
    arrowcolor=IMPRINT[3],
)

# Dashed supercritical boundary lines — vertical and horizontal from critical point
fig.add_trace(
    go.Scatter(
        x=[critical_T, critical_T],
        y=[critical_P, 1e10],
        mode="lines",
        line={"color": IMPRINT[3], "width": 2, "dash": "dot"},
        showlegend=False,
        hoverinfo="skip",
    )
)
fig.add_trace(
    go.Scatter(
        x=[critical_T, 800],
        y=[critical_P, critical_P],
        mode="lines",
        line={"color": IMPRINT[3], "width": 2, "dash": "dot"},
        showlegend=False,
        hoverinfo="skip",
    )
)

# Layout — theme-adaptive chrome throughout
fig.update_layout(
    autosize=False,
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    font={"color": INK},
    title={"text": title_str, "font": {"size": title_fs, "color": INK}, "x": 0.5, "xanchor": "center"},
    xaxis={
        "title": {"text": "Temperature (K)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "range": [x_min, x_max],
        "showgrid": True,
        "gridwidth": 1,
        "gridcolor": GRID,
        "linecolor": INK_SOFT,
        "zerolinecolor": INK_SOFT,
    },
    yaxis={
        "title": {"text": "Pressure (Pa)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "type": "log",
        "range": [y_log_min, y_log_max],
        "showgrid": True,
        "gridwidth": 1,
        "gridcolor": GRID,
        "linecolor": INK_SOFT,
        "zerolinecolor": INK_SOFT,
    },
    legend={
        "font": {"size": 10, "color": INK_SOFT},
        "x": 0.02,
        "y": 0.98,
        "bgcolor": ELEVATED_BG,
        "bordercolor": INK_SOFT,
        "borderwidth": 1,
    },
    margin={"l": 100, "r": 80, "t": 80, "b": 80},
    showlegend=True,
    updatemenus=[
        {
            "type": "buttons",
            "direction": "left",
            "x": 0.98,
            "y": -0.15,
            "xanchor": "right",
            "buttons": [
                {"label": "Log Scale", "method": "relayout", "args": [{"yaxis.type": "log"}]},
                {"label": "Linear Scale", "method": "relayout", "args": [{"yaxis.type": "linear"}]},
            ],
            "font": {"size": 10, "color": INK},
            "bgcolor": ELEVATED_BG,
            "borderwidth": 1,
            "bordercolor": INK_SOFT,
        }
    ],
)

# Save — landscape 3200×1800 (width=800 height=450 scale=4)
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Part of Thermodynamic Phase Diagram (Pressure-Temperature) on anyplot.ai.

Other implementations