Thermodynamic Phase Diagram (Pressure-Temperature) — D3.js

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 D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// phase-diagram-pt: Thermodynamic Phase Diagram (Pressure-Temperature)
// Library: d3 7.9.0 | JavaScript 22.22.3
// Quality: 90/100 | Created: 2026-06-08

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;

const margin = { top: 90, right: 140, bottom: 90, left: 140 };
const iw = width - margin.left - margin.right;
const ih = height - margin.top - margin.bottom;

// --- Water phase diagram constants ---
const T_TRIPLE = 273.16, P_TRIPLE = 611.73;
const T_CRIT = 647.1, P_CRIT = 2.2064e7;
// Melting slope: negative for water (it expands on freezing)
const MELT_SLOPE = -1.346e7;   // Pa/K
const L_SUB_R = 6141;          // sublimation Clausius-Clapeyron L/R (K)
// Vaporization L/R calibrated so the curve terminates exactly at the critical point
const L_VAP_R = Math.log(P_CRIT / P_TRIPLE) / (1 / T_TRIPLE - 1 / T_CRIT);

// Unicode superscript digits for axis tick labels
const SUP_DIGITS = "⁰¹²³⁴⁵⁶⁷⁸⁹";
const toSup = n => String(n).split("").map(c => SUP_DIGITS[+c]).join("");

// Axis domain
const T_MIN = 170, T_MAX = 720;
const P_MIN = 1, P_MAX = 1e9;

// --- Generate phase boundary curves ---
const sublimData = [];
for (let Tv = 209; Tv <= T_TRIPLE; Tv += 0.3) {
  sublimData.push({ T: Tv, P: P_TRIPLE * Math.exp(L_SUB_R * (1 / T_TRIPLE - 1 / Tv)) });
}

const vapData = [];
for (let Tv = T_TRIPLE; Tv <= T_CRIT; Tv += 0.3) {
  vapData.push({ T: Tv, P: P_TRIPLE * Math.exp(L_VAP_R * (1 / T_TRIPLE - 1 / Tv)) });
}

const meltData = [];
for (let lP = Math.log10(P_TRIPLE); lP <= 9.15; lP += 0.04) {
  const P = Math.pow(10, lP);
  const T = T_TRIPLE + (P - P_TRIPLE) / MELT_SLOPE;
  if (T >= T_MIN && T <= T_MAX) meltData.push({ T, P });
}

// --- SVG ---
const svg = d3.select("#container").append("svg")
  .attr("width", width).attr("height", height);
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);

// --- Scales ---
const x = d3.scaleLinear().domain([T_MIN, T_MAX]).range([0, iw]);
const y = d3.scaleLog().base(10).domain([P_MIN, P_MAX]).range([ih, 0]);

// --- Gridlines ---
[1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9].forEach(p => {
  g.append("line")
    .attr("x1", 0).attr("x2", iw).attr("y1", y(p)).attr("y2", y(p))
    .attr("stroke", t.grid).attr("stroke-width", 1);
});
[200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700].forEach(temp => {
  g.append("line")
    .attr("x1", x(temp)).attr("x2", x(temp)).attr("y1", 0).attr("y2", ih)
    .attr("stroke", t.grid).attr("stroke-width", 1);
});

// --- Axes ---
const xAx = g.append("g").attr("transform", `translate(0,${ih})`)
  .call(d3.axisBottom(x)
    .tickValues([200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700])
    .tickSize(6));
xAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "14px");
xAx.selectAll("line").attr("stroke", t.inkSoft);
xAx.select(".domain").attr("stroke", t.inkSoft);

const yAx = g.append("g")
  .call(d3.axisLeft(y)
    .tickValues([1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9])
    .tickFormat(d => "10" + toSup(Math.round(Math.log10(d))))
    .tickSize(6));
yAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "14px");
yAx.selectAll("line").attr("stroke", t.inkSoft);
yAx.select(".domain").attr("stroke", t.inkSoft);

// --- Clip path for boundary curves ---
g.append("clipPath").attr("id", "plot-clip")
  .append("rect").attr("width", iw).attr("height", ih);

// --- Line generator ---
const mkLine = d3.line()
  .x(d => x(d.T))
  .y(d => y(d.P))
  .defined(d => d.T >= T_MIN && d.T <= T_MAX && d.P >= P_MIN && d.P <= P_MAX);

const curves = g.append("g").attr("clip-path", "url(#plot-clip)");

// Imprint palette order: solid-liquid first (#009E73), then liquid-gas, then solid-gas
const boundaries = [
  { data: meltData,   color: t.palette[0], dash: null,  strokeW: 3.5 },  // #009E73 solid-liquid
  { data: vapData,    color: t.palette[1], dash: null,  strokeW: 3.5 },  // #C475FD liquid-gas
  { data: sublimData, color: t.palette[2], dash: "8,4", strokeW: 3   },  // #4467A3 solid-gas
];
boundaries.forEach(b => {
  const path = curves.append("path").datum(b.data)
    .attr("fill", "none")
    .attr("stroke", b.color)
    .attr("stroke-width", b.strokeW)
    .attr("d", mkLine);
  if (b.dash) path.attr("stroke-dasharray", b.dash);
});

// --- Triple point and critical point markers ---
const addMarker = (cx, cy, fill) =>
  g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", 9)
    .attr("fill", fill).attr("stroke", t.pageBg).attr("stroke-width", 3);

addMarker(x(T_TRIPLE), y(P_TRIPLE), t.ink);
addMarker(x(T_CRIT),   y(P_CRIT),   t.palette[3]);  // ochre #BD8233

// --- Point annotations ---
const tpX = x(T_TRIPLE), tpY = y(P_TRIPLE);
g.append("text").attr("x", tpX + 14).attr("y", tpY - 12)
  .attr("fill", t.ink).style("font-size", "14px").style("font-weight", "600")
  .text("Triple point");
g.append("text").attr("x", tpX + 14).attr("y", tpY + 6)
  .attr("fill", t.inkSoft).style("font-size", "14px")
  .text("273.16 K · 612 Pa");

const cpX = x(T_CRIT), cpY = y(P_CRIT);
g.append("text").attr("x", cpX - 14).attr("y", cpY - 12)
  .attr("text-anchor", "end").attr("fill", t.ink)
  .style("font-size", "14px").style("font-weight", "600")
  .text("Critical point");
g.append("text").attr("x", cpX - 14).attr("y", cpY + 6)
  .attr("text-anchor", "end").attr("fill", t.inkSoft)
  .style("font-size", "14px")
  .text("647.1 K · 22.1 MPa");

// --- Phase region labels ---
// Positions verified to lie within each phase region
[
  { T: 225,  P: 5e5,  anchor: "middle", text: "SOLID"          },
  { T: 390,  P: 3e5,  anchor: "middle", text: "LIQUID"         },
  { T: 490,  P: 200,  anchor: "middle", text: "GAS"            },
].forEach(r => {
  g.append("text")
    .attr("x", x(r.T)).attr("y", y(r.P))
    .attr("text-anchor", r.anchor)
    .attr("fill", t.inkSoft)
    .style("font-size", "16px").style("font-weight", "700")
    .style("font-style", "italic").style("opacity", "0.85")
    .text(r.text);
});

// Supercritical fluid label as two tspan lines (combined to avoid disconnect)
const scfX = x(676), scfY = y(1.3e8);
const scfEl = g.append("text")
  .attr("text-anchor", "middle").attr("fill", t.inkSoft)
  .style("font-size", "16px").style("font-weight", "700")
  .style("font-style", "italic").style("opacity", "0.85");
scfEl.append("tspan").attr("x", scfX).attr("y", scfY).text("SUPERCRITICAL");
scfEl.append("tspan").attr("x", scfX).attr("dy", "1.3em").text("FLUID");

// --- Legend (placed in open gas region, lower right) ---
const lx = iw - 20;
const ly0 = ih - 5 * 28 - 20;

const legCurves = [
  { color: t.palette[0], label: "Solid–Liquid (Melting)",     dash: null  },
  { color: t.palette[1], label: "Liquid–Gas (Vaporization)",  dash: null  },
  { color: t.palette[2], label: "Solid–Gas (Sublimation)",    dash: "6,3" },
];
legCurves.forEach((d, i) => {
  const ly = ly0 + i * 28;
  const seg = g.append("line")
    .attr("x1", lx - 28).attr("x2", lx).attr("y1", ly).attr("y2", ly)
    .attr("stroke", d.color).attr("stroke-width", 3.5);
  if (d.dash) seg.attr("stroke-dasharray", d.dash);
  g.append("text").attr("x", lx - 34).attr("y", ly + 5)
    .attr("text-anchor", "end").attr("fill", t.inkSoft)
    .style("font-size", "14px").text(d.label);
});

const legPoints = [
  { fill: t.ink,        label: "Triple point"   },
  { fill: t.palette[3], label: "Critical point" },
];
legPoints.forEach((d, i) => {
  const ly = ly0 + (legCurves.length + i) * 28;
  g.append("circle").attr("cx", lx - 14).attr("cy", ly).attr("r", 6)
    .attr("fill", d.fill).attr("stroke", t.pageBg).attr("stroke-width", 2);
  g.append("text").attr("x", lx - 24).attr("y", ly + 5)
    .attr("text-anchor", "end").attr("fill", t.inkSoft)
    .style("font-size", "14px").text(d.label);
});

// --- Axis labels ---
svg.append("text")
  .attr("x", margin.left + iw / 2).attr("y", height - 22)
  .attr("text-anchor", "middle").attr("fill", t.ink).style("font-size", "16px")
  .text("Temperature (K)");

svg.append("text")
  .attr("transform", `translate(30,${margin.top + ih / 2})rotate(-90)`)
  .attr("text-anchor", "middle").attr("fill", t.ink).style("font-size", "16px")
  .text("Pressure (Pa)");

// --- Title ---
const TITLE = "Water Phase Diagram · phase-diagram-pt · javascript · d3 · anyplot.ai";
const titleFs = Math.max(14, Math.round(22 * Math.min(1.0, 67 / TITLE.length)));
svg.append("text")
  .attr("x", width / 2).attr("y", 50)
  .attr("text-anchor", "middle").attr("fill", t.ink)
  .style("font-size", `${titleFs}px`).style("font-weight", "600")
  .text(TITLE);

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/phase-diagram-pt/d3/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "phase-diagram-pt",
  "language": "javascript",
  "library": "d3",
  "page": "https://anyplot.ai/phase-diagram-pt/javascript/d3",
  "hub": "https://anyplot.ai/phase-diagram-pt",
  "code_json": "https://api.anyplot.ai/specs/phase-diagram-pt/d3/code",
  "spec_json": "https://api.anyplot.ai/specs/phase-diagram-pt",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/phase-diagram-pt/javascript/d3/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/phase-diagram-pt/javascript/d3/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/phase-diagram-pt/javascript/d3/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/phase-diagram-pt/javascript/d3/plot-dark.html",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

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

Other implementations