Engineering Stress-Strain Curve — Bokeh

An engineering stress-strain curve visualizes the relationship between applied stress (MPa) and resulting strain (dimensionless) in a material under uniaxial tensile loading. The curve reveals distinct mechanical behavior regions — elastic deformation, yielding, strain hardening, and necking — culminating in fracture. It is the foundational plot for characterizing material mechanical properties and comparing material performance.

Engineering Stress-Strain Curve rendered with Bokeh

Python source (Bokeh)

""" pyplots.ai
line-stress-strain: Engineering Stress-Strain Curve
Library: bokeh 3.9.0 | Python 3.14.3
Quality: 91/100 | Created: 2026-03-20
"""

import numpy as np
from bokeh.io import export_png
from bokeh.models import ColumnDataSource, HoverTool, Label, Legend, LegendItem, Span
from bokeh.plotting import figure, save


# Data — Mild steel tensile test simulation
np.random.seed(42)

youngs_modulus = 210000  # MPa
yield_strength = 250  # MPa
uts = 400  # MPa
fracture_strain = 0.35
uts_strain = 0.22
yield_strain = yield_strength / youngs_modulus  # ~0.00119

# Elastic region
strain_elastic = np.linspace(0, yield_strain, 60)
stress_elastic = youngs_modulus * strain_elastic

# Yield plateau and strain hardening (Ludwik-type power law)
strain_plastic = np.linspace(yield_strain, uts_strain, 200)
plastic_strain = strain_plastic - yield_strain
stress_plastic = yield_strength + (uts - yield_strength) * (plastic_strain / (uts_strain - yield_strain)) ** 0.45

# Necking region (stress decreases after UTS)
strain_necking = np.linspace(uts_strain, fracture_strain, 80)
necking_progress = (strain_necking - uts_strain) / (fracture_strain - uts_strain)
stress_necking = uts - (uts - 320) * necking_progress**0.8

# Combine all regions
strain = np.concatenate([strain_elastic, strain_plastic, strain_necking])
stress = np.concatenate([stress_elastic, stress_plastic, stress_necking])

# Region labels for hover tooltip
region_labels = np.concatenate(
    [
        np.full(len(strain_elastic), "Elastic"),
        np.full(len(strain_plastic), "Strain Hardening"),
        np.full(len(strain_necking), "Necking"),
    ]
)

# 0.2% offset line — extended to be clearly visible
offset_strain_start = 0.002
offset_strain_end = 0.004 + yield_strength / youngs_modulus
offset_strain_line = np.linspace(offset_strain_start, offset_strain_end, 80)
offset_stress_line = youngs_modulus * (offset_strain_line - 0.002)
mask = offset_stress_line <= yield_strength + 30
offset_strain_line = offset_strain_line[mask]
offset_stress_line = offset_stress_line[mask]

# Key points
yield_point_strain = yield_strain + 0.002
yield_point_stress = yield_strength
uts_point_strain = uts_strain
uts_point_stress = uts
fracture_point_strain = fracture_strain
fracture_point_stress = stress_necking[-1]

# Colorblind-safe palette
color_main = "#306998"  # Python blue
color_yield = "#D4A84B"  # Gold
color_uts = "#8B5CF6"  # Purple (replaces red)
color_fracture = "#0EA5E9"  # Sky blue (replaces green)
color_region = "#6B7280"  # Neutral gray

# Plot
p = figure(
    width=4800,
    height=2700,
    title="line-stress-strain · bokeh · pyplots.ai",
    x_axis_label="Engineering Strain (mm/mm)",
    y_axis_label="Engineering Stress (MPa)",
)

# Subtle horizontal reference lines at yield and UTS
p.add_layout(
    Span(
        location=yield_strength,
        dimension="width",
        line_color=color_yield,
        line_alpha=0.2,
        line_dash="dotted",
        line_width=2,
    )
)
p.add_layout(
    Span(location=uts, dimension="width", line_color=color_uts, line_alpha=0.2, line_dash="dotted", line_width=2)
)

# Main curve with region data for HoverTool
source = ColumnDataSource(data={"strain": strain, "stress": stress, "region": region_labels})
main_line = p.line(x="strain", y="stress", source=source, line_width=5, color=color_main)

# HoverTool — Bokeh-distinctive interactive feature
hover = HoverTool(
    renderers=[main_line],
    tooltips=[("Strain", "@strain{0.0000}"), ("Stress", "@stress{0.1} MPa"), ("Region", "@region")],
    mode="vline",
    line_policy="nearest",
)
p.add_tools(hover)

# 0.2% offset line — thicker and more visible
offset_source = ColumnDataSource(data={"strain": offset_strain_line, "stress": offset_stress_line})
offset_line = p.line(x="strain", y="stress", source=offset_source, line_width=4, line_dash="dashed", color=color_yield)

# Key points — larger markers for clarity
yield_glyph = p.scatter(
    x=[yield_point_strain],
    y=[yield_point_stress],
    size=32,
    color=color_yield,
    marker="circle",
    line_color="white",
    line_width=3,
)

uts_glyph = p.scatter(
    x=[uts_point_strain],
    y=[uts_point_stress],
    size=32,
    color=color_uts,
    marker="triangle",
    line_color="white",
    line_width=3,
)

fracture_glyph = p.scatter(
    x=[fracture_point_strain],
    y=[fracture_point_stress],
    size=32,
    color=color_fracture,
    marker="square",
    line_color="white",
    line_width=3,
)

# Region labels — repositioned to reduce left-side congestion
p.add_layout(
    Label(x=0.008, y=130, text="Elastic", text_font_size="22pt", text_color=color_region, text_font_style="italic")
)

p.add_layout(
    Label(
        x=0.08, y=280, text="Strain Hardening", text_font_size="22pt", text_color=color_region, text_font_style="italic"
    )
)

p.add_layout(
    Label(x=0.27, y=380, text="Necking", text_font_size="22pt", text_color=color_region, text_font_style="italic")
)

# Key point annotations — repositioned to spread out and avoid left-side crowding
p.add_layout(
    Label(
        x=yield_point_strain + 0.018,
        y=yield_point_stress - 25,
        text=f"Yield Point ({yield_point_stress} MPa)",
        text_font_size="18pt",
        text_color=color_yield,
        text_font_style="bold",
    )
)

p.add_layout(
    Label(
        x=uts_point_strain - 0.075,
        y=uts_point_stress + 18,
        text=f"UTS ({uts_point_stress} MPa)",
        text_font_size="18pt",
        text_color=color_uts,
        text_font_style="bold",
    )
)

p.add_layout(
    Label(
        x=fracture_point_strain - 0.04,
        y=fracture_point_stress - 40,
        text="Fracture",
        text_font_size="18pt",
        text_color=color_fracture,
        text_font_style="bold",
    )
)

# Young's modulus annotation — moved right to reduce elastic region crowding
p.add_layout(
    Label(
        x=0.025,
        y=50,
        text=f"E = {youngs_modulus // 1000} GPa",
        text_font_size="18pt",
        text_color=color_main,
        text_font_style="bold",
    )
)

# Legend — positioned inside the plot near the data
legend = Legend(
    items=[
        LegendItem(label="Stress-Strain Curve", renderers=[main_line]),
        LegendItem(label="0.2% Offset Line", renderers=[offset_line]),
        LegendItem(label="Yield Point", renderers=[yield_glyph]),
        LegendItem(label="Ultimate Tensile Strength", renderers=[uts_glyph]),
        LegendItem(label="Fracture Point", renderers=[fracture_glyph]),
    ],
    location=(2800, 200),
)
legend.label_text_font_size = "18pt"
legend.glyph_height = 30
legend.glyph_width = 30
legend.spacing = 10
legend.padding = 20
legend.margin = 20
legend.background_fill_color = "#FAFAFA"
legend.background_fill_alpha = 0.9
legend.border_line_color = "#E5E7EB"
legend.border_line_alpha = 0.6
legend.border_line_width = 2
p.add_layout(legend, "center")

# Style
p.title.text_font_size = "28pt"
p.title.text_font_style = "normal"
p.title.text_color = "#374151"
p.xaxis.axis_label_text_font_size = "22pt"
p.yaxis.axis_label_text_font_size = "22pt"
p.xaxis.major_label_text_font_size = "18pt"
p.yaxis.major_label_text_font_size = "18pt"
p.xaxis.axis_label_text_color = "#4B5563"
p.yaxis.axis_label_text_color = "#4B5563"
p.xaxis.major_label_text_color = "#6B7280"
p.yaxis.major_label_text_color = "#6B7280"
p.xaxis.axis_line_color = "#D1D5DB"
p.yaxis.axis_line_color = "#D1D5DB"
p.xaxis.major_tick_line_color = "#D1D5DB"
p.yaxis.major_tick_line_color = "#D1D5DB"
p.xaxis.minor_tick_line_color = None
p.yaxis.minor_tick_line_color = None

p.xgrid.grid_line_alpha = 0.15
p.ygrid.grid_line_alpha = 0.15
p.xgrid.grid_line_color = "#9CA3AF"
p.ygrid.grid_line_color = "#9CA3AF"

p.outline_line_color = None
p.toolbar_location = None
p.background_fill_color = "#FAFAFA"
p.border_fill_color = "white"

p.y_range.start = -10
p.y_range.end = 450
p.x_range.start = -0.005
p.x_range.end = 0.38

# Save
export_png(p, filename="plot.png")
save(p, filename="plot.html", title="line-stress-strain · bokeh · pyplots.ai")

Part of Engineering Stress-Strain Curve on anyplot.ai.

Other implementations