Psychrometric Chart
HVAC — humidity ratio vs. dry-bulb temperature
Example from the compendium of canonical charts
Python Code
"""HVAC — Psychrometric Chart (humidity ratio vs. dry-bulb temperature)."""
import numpy as np
import plotly.graph_objects as go
P = 101325.0 # atmospheric pressure (Pa)
# Relative humidity levels to plot
RH_LEVELS = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
# Cycle through palette
COLORS = [
"#52B3D0", # teal — 10%
"#55B685", # green — 20%
"#845EEE", # violet— 30%
"#52B3D0", # teal — 40%
"#DA5597", # pink — 50%
"#52B3D0",
"#55B685",
"#845EEE",
"#52B3D0",
"#845EEE", # 100% saturation — same as violet but bolder
]
def saturation_pressure(T_C):
"""Arden-Buck equation for saturation vapor pressure (Pa)."""
return 611.21 * np.exp((18.678 - T_C / 234.5) * (T_C / (257.14 + T_C)))
def humidity_ratio(phi_frac, T_C):
"""Humidity ratio W (kg_w/kg_da) at relative humidity phi (0-1)."""
pv = phi_frac * saturation_pressure(T_C)
return 0.621945 * pv / (P - pv)
def generate():
T = np.linspace(0, 50, 300)
fig = go.Figure()
# Constant-RH lines
for i, rh in enumerate(RH_LEVELS):
W = humidity_ratio(rh / 100.0, T)
is_sat = rh == 100
color = COLORS[i]
fig.add_trace(go.Scatter(
x=T,
y=W * 1000, # convert to g/kg for readability
mode="lines",
line=dict(
color=color,
width=2.5 if is_sat else 1.5,
dash="solid" if is_sat else "solid",
),
name=f"{rh}% RH",
hovertemplate=f"{rh}% RH<br>T=%{{x:.1f}} °C<br>W=%{{y:.2f}} g/kg<extra></extra>",
))
# Label at T=48°C
label_T = 47.0
label_W = humidity_ratio(rh / 100.0, label_T) * 1000
fig.add_annotation(
x=label_T,
y=label_W,
text=f"{rh}%",
showarrow=False,
font=dict(size=9, color=color),
xanchor="left",
yanchor="middle",
xshift=4,
)
# Shade comfort zone: 20-26°C, 40-60% RH
T_comfort = np.concatenate([
np.linspace(20, 26, 50), # lower RH=40% edge
np.linspace(26, 20, 50), # upper RH=60% edge
])
W_comfort = np.concatenate([
humidity_ratio(0.40, np.linspace(20, 26, 50)) * 1000,
humidity_ratio(0.60, np.linspace(26, 20, 50)) * 1000,
])
fig.add_trace(go.Scatter(
x=T_comfort,
y=W_comfort,
fill="toself",
fillcolor="rgba(85, 182, 133, 0.15)",
line=dict(color="rgba(85, 182, 133, 0.5)", width=1),
name="Comfort zone",
hoverinfo="skip",
))
fig.add_annotation(
x=23, y=humidity_ratio(0.50, 23) * 1000,
text="Comfort<br>zone",
showarrow=False,
font=dict(size=11, color=GREEN),
xanchor="center",
)
fig.update_layout(
xaxis=dict(title="Dry-Bulb Temperature (°C)", range=[0, 52]),
yaxis=dict(title="Humidity Ratio W (g/kg dry air)", rangemode="tozero"),
legend=dict(
x=0.01, y=0.99,
xanchor="left", yanchor="top",
font=dict(size=10),
tracegroupgap=0,
),
margin=dict(t=50, b=60, l=70, r=80),
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly