Skip to content

Commit df2ce12

Browse files
feat(seaborn): implement line-stress-strain (#5130)
## Implementation: `line-stress-strain` - seaborn Implements the **seaborn** version of `line-stress-strain`. **File:** `plots/line-stress-strain/implementations/seaborn.py` **Parent Issue:** #4413 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/23363004930)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent e2ccf91 commit df2ce12

2 files changed

Lines changed: 408 additions & 0 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
""" pyplots.ai
2+
line-stress-strain: Engineering Stress-Strain Curve
3+
Library: seaborn 0.13.2 | Python 3.14.3
4+
Quality: 90/100 | Created: 2026-03-20
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import pandas as pd
10+
import seaborn as sns
11+
12+
13+
# Data — Mild steel tensile test simulation
14+
np.random.seed(42)
15+
16+
youngs_modulus = 200000 # MPa
17+
yield_stress = 250 # MPa
18+
uts = 400 # MPa
19+
fracture_strain = 0.35
20+
uts_strain = 0.22
21+
yield_strain = yield_stress / youngs_modulus
22+
23+
# Elastic region
24+
strain_elastic = np.linspace(0, yield_strain, 60)
25+
stress_elastic = youngs_modulus * strain_elastic
26+
27+
# Strain hardening (quadratic rise to UTS)
28+
strain_hardening = np.linspace(yield_strain, uts_strain, 200)
29+
t = (strain_hardening - yield_strain) / (uts_strain - yield_strain)
30+
stress_hardening = yield_stress + (uts - yield_stress) * (2 * t - t**2)
31+
32+
# Necking region (UTS to fracture — stress drops)
33+
strain_necking = np.linspace(uts_strain, fracture_strain, 100)
34+
t_neck = (strain_necking - uts_strain) / (fracture_strain - uts_strain)
35+
fracture_stress = 320
36+
stress_necking = uts - (uts - fracture_stress) * t_neck**1.5
37+
38+
# Build DataFrame with region labels for seaborn hue-based plotting
39+
df_elastic = pd.DataFrame({"strain": strain_elastic, "stress": stress_elastic, "region": "Elastic"})
40+
df_hardening = pd.DataFrame({"strain": strain_hardening, "stress": stress_hardening, "region": "Strain Hardening"})
41+
df_necking = pd.DataFrame({"strain": strain_necking, "stress": stress_necking, "region": "Necking"})
42+
df = pd.concat([df_elastic, df_hardening, df_necking], ignore_index=True)
43+
44+
# 0.2% offset yield point
45+
offset = 0.002
46+
offset_line_strain = np.linspace(offset, offset + yield_stress / youngs_modulus + 0.003, 50)
47+
offset_line_stress = youngs_modulus * (offset_line_strain - offset)
48+
offset_line_stress = np.clip(offset_line_stress, 0, yield_stress + 20)
49+
50+
yield_offset_strain = offset + yield_stress / youngs_modulus
51+
yield_offset_stress = yield_stress
52+
53+
# Define seaborn palette and style
54+
region_palette = {"Elastic": "#306998", "Strain Hardening": "#E07B39", "Necking": "#C0392B"}
55+
sns.set_style("whitegrid", {"grid.linestyle": "-", "grid.alpha": 0.15, "grid.linewidth": 0.8})
56+
sns.set_context(
57+
"talk",
58+
rc={
59+
"axes.titlesize": 24,
60+
"axes.labelsize": 20,
61+
"xtick.labelsize": 16,
62+
"ytick.labelsize": 16,
63+
"legend.fontsize": 13,
64+
"font.weight": "medium",
65+
},
66+
)
67+
68+
fig, ax = plt.subplots(figsize=(16, 9))
69+
70+
# Plot stress-strain curve using seaborn lineplot with hue for regions
71+
sns.lineplot(
72+
data=df, x="strain", y="stress", hue="region", palette=region_palette, linewidth=3.5, ax=ax, legend=True, sort=False
73+
)
74+
75+
# 0.2% offset line
76+
ax.plot(offset_line_strain, offset_line_stress, linestyle="--", linewidth=2, color="#808080", zorder=3)
77+
78+
# Mark critical points using seaborn scatterplot for consistent styling
79+
critical_points = pd.DataFrame(
80+
{
81+
"strain": [yield_offset_strain, uts_strain, fracture_strain],
82+
"stress": [yield_offset_stress, uts, fracture_stress],
83+
"point": ["Yield Point", "UTS", "Fracture"],
84+
}
85+
)
86+
point_palette = {"Yield Point": "#E07B39", "UTS": "#C0392B", "Fracture": "#2C3E50"}
87+
point_markers = {"Yield Point": "o", "UTS": "o", "Fracture": "X"}
88+
sns.scatterplot(
89+
data=critical_points,
90+
x="strain",
91+
y="stress",
92+
hue="point",
93+
style="point",
94+
palette=point_palette,
95+
markers=point_markers,
96+
s=200,
97+
edgecolor="white",
98+
linewidth=1.5,
99+
ax=ax,
100+
zorder=5,
101+
legend=True,
102+
)
103+
104+
# Annotations for critical points — yield moved right to reduce left-side clutter
105+
ax.annotate(
106+
"Yield Point\n(0.2% offset)",
107+
xy=(yield_offset_strain, yield_offset_stress),
108+
xytext=(0.06, yield_offset_stress - 30),
109+
fontsize=14,
110+
fontweight="bold",
111+
color="#E07B39",
112+
arrowprops={"arrowstyle": "-|>", "color": "#E07B39", "lw": 1.5},
113+
ha="left",
114+
va="top",
115+
)
116+
117+
ax.annotate(
118+
f"UTS = {uts} MPa",
119+
xy=(uts_strain, uts),
120+
xytext=(uts_strain + 0.03, uts + 30),
121+
fontsize=14,
122+
fontweight="bold",
123+
color="#C0392B",
124+
arrowprops={"arrowstyle": "-|>", "color": "#C0392B", "lw": 1.5},
125+
ha="left",
126+
va="bottom",
127+
)
128+
129+
ax.annotate(
130+
"Fracture",
131+
xy=(fracture_strain, fracture_stress),
132+
xytext=(fracture_strain - 0.01, fracture_stress - 60),
133+
fontsize=14,
134+
fontweight="bold",
135+
color="#2C3E50",
136+
arrowprops={"arrowstyle": "-|>", "color": "#2C3E50", "lw": 1.5},
137+
ha="center",
138+
va="top",
139+
)
140+
141+
# Elastic modulus annotation — positioned to the right to avoid left-side clutter
142+
ax.annotate(
143+
f"E = {youngs_modulus // 1000} GPa",
144+
xy=(yield_strain * 0.5, youngs_modulus * yield_strain * 0.5),
145+
xytext=(0.05, 60),
146+
fontsize=13,
147+
fontstyle="italic",
148+
color="#306998",
149+
arrowprops={"arrowstyle": "-|>", "color": "#306998", "lw": 1.2},
150+
ha="left",
151+
va="center",
152+
)
153+
154+
# Region shading using axvspan
155+
region_shading = [
156+
(0, yield_strain, "#306998"),
157+
(yield_strain, uts_strain, "#E07B39"),
158+
(uts_strain, fracture_strain, "#C0392B"),
159+
]
160+
for x0, x1, color in region_shading:
161+
ax.axvspan(x0, x1, alpha=0.06, color=color, zorder=0)
162+
163+
# Style using seaborn's despine
164+
sns.despine(ax=ax)
165+
ax.xaxis.grid(False)
166+
ax.set_xlabel("Engineering Strain")
167+
ax.set_ylabel("Engineering Stress (MPa)")
168+
ax.set_title("line-stress-strain · seaborn · pyplots.ai", fontweight="medium")
169+
ax.set_xlim(-0.01, fracture_strain + 0.03)
170+
ax.set_ylim(-10, uts + 80)
171+
172+
# Consolidate legend: combine region lines and critical point markers
173+
handles, labels = ax.get_legend_handles_labels()
174+
# Reorder: regions first, then critical points
175+
ax.legend(handles=handles, labels=labels, fontsize=13, loc="center right", framealpha=0.9, edgecolor="#cccccc")
176+
177+
# Save
178+
plt.tight_layout()
179+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")

0 commit comments

Comments
 (0)