Ragone plot
Energy Storage — specific energy vs. specific power, log-log
Example from the compendium of canonical charts
Python Code
"""Energy Storage — Ragone plot (specific energy vs. specific power, log-log)."""
import numpy as np
import plotly.graph_objects as go
TECHNOLOGIES = {
"Supercapacitor": {
"energy_range": [2, 10],
"power_range": [1000, 10000],
"color": TEAL,
},
"Li-ion": {
"energy_range": [100, 250],
"power_range": [200, 2000],
"color": VIOLET,
},
"NiMH": {
"energy_range": [60, 120],
"power_range": [150, 1000],
"color": GREEN,
},
"Lead-acid": {
"energy_range": [30, 50],
"power_range": [100, 400],
"color": ORANGE,
},
"Fuel cell": {
"energy_range": [300, 3000],
"power_range": [10, 100],
"color": PINK,
},
}
def hex_to_rgba(hex_str, alpha=0.25):
"""Convert #RRGGBB to rgba(r,g,b,a) string."""
h = hex_str.lstrip("#")
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
return f"rgba({r},{g},{b},{alpha})"
def make_ellipse_path(xmin, xmax, ymin, ymax, n=60):
"""Return (x, y) arrays for an ellipse inscribed in the log-space bounding box."""
lxc = (np.log10(xmin) + np.log10(xmax)) / 2
lyc = (np.log10(ymin) + np.log10(ymax)) / 2
lxa = (np.log10(xmax) - np.log10(xmin)) / 2
lya = (np.log10(ymax) - np.log10(ymin)) / 2
theta = np.linspace(0, 2 * np.pi, n + 1)
lx = lxc + lxa * np.cos(theta)
ly = lyc + lya * np.sin(theta)
return 10 ** lx, 10 ** ly
def generate():
fig = go.Figure()
# ── Constant discharge-time lines (diagonal guides) ──────────────────────
E_line = np.logspace(0, 4, 200)
for t_h, label in [(1.0, "1 h"), (0.1, "6 min"), (0.01, "36 s")]:
P_line = E_line / t_h
fig.add_trace(go.Scatter(
x=E_line,
y=P_line,
mode="lines",
line=dict(color="#b0b0b0", width=1, dash="dot"),
showlegend=False,
hoverinfo="skip",
))
# ── Technology ellipses ───────────────────────────────────────────────────
for tech, props in TECHNOLOGIES.items():
e_min, e_max = props["energy_range"]
p_min, p_max = props["power_range"]
color = props["color"]
xe, ye = make_ellipse_path(e_min, e_max, p_min, p_max)
fig.add_trace(go.Scatter(
x=xe,
y=ye,
mode="lines",
fill="toself",
fillcolor=hex_to_rgba(color, 0.25),
line=dict(color=color, width=2),
name=tech,
hovertemplate=(
f"<b>{tech}</b><br>"
f"Energy: {e_min}–{e_max} Wh/kg<br>"
f"Power: {p_min}–{p_max} W/kg<extra></extra>"
),
))
# Label at centroid of ellipse (in log space)
xc = np.sqrt(e_min * e_max)
yc = np.sqrt(p_min * p_max)
fig.add_annotation(
x=np.log10(xc),
y=np.log10(yc),
text=f"<b>{tech}</b>",
showarrow=False,
font=dict(size=10, color=color),
xref="x",
yref="y",
)
# ── Discharge-time axis labels ────────────────────────────────────────────
for t_h, label in [(1.0, "1 h"), (0.1, "6 min"), (0.01, "36 s")]:
x_label = 2e3
y_label = x_label / t_h
if 5 < y_label < 5e4:
fig.add_annotation(
x=np.log10(x_label),
y=np.log10(y_label),
text=label,
showarrow=False,
font=dict(size=12, color="#555555"),
xref="x",
yref="y",
textangle=-35,
)
fig.update_layout(
xaxis=dict(
title="Specific Energy (Wh/kg)",
type="log",
range=[0, 4],
showgrid=True,
),
yaxis=dict(
title="Specific Power (W/kg)",
type="log",
range=[0.5, 4.5],
showgrid=True,
),
legend=dict(
x=1.02, y=1.0,
xanchor="left",
font=dict(size=10),
),
margin=dict(t=20, b=60, l=70, r=140),
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly