{"spec_id":"line-stress-strain","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nline-stress-strain: Engineering Stress-Strain Curve\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-06-21\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — canonical order, first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\nCOLOR_ELASTIC = IMPRINT_PALETTE[0]  # brand green — first series\nCOLOR_HARDENING = IMPRINT_PALETTE[2]  # blue\nCOLOR_NECKING = IMPRINT_PALETTE[4]  # matte red — semantic: fracture/failure\n\n# Data — Aluminum alloy 6061-T6 tensile test simulation (5 specimens)\n# Different from mild steel: lower E, no yield plateau, lower ductility\nnp.random.seed(42)\n\nyoungs_modulus = 69_000  # MPa (69 GPa — much lower than steel's 200 GPa)\nyield_stress = 276  # MPa (0.2% offset yield strength)\nuts = 310  # MPa (ultimate tensile strength)\nuts_strain = 0.08\nfracture_strain = 0.12\nfracture_stress = 252  # MPa\nyield_strain = yield_stress / youngs_modulus  # ~0.004\n\n# Shared strain grid across all specimens so seaborn can aggregate by (x, hue)\nstrain_elastic_ref = np.linspace(0, yield_strain, 40)\nstrain_hardening_ref = np.linspace(yield_strain, uts_strain, 150)\nstrain_necking_ref = np.linspace(uts_strain, fracture_strain, 80)\n\nt_hard = (strain_hardening_ref - yield_strain) / (uts_strain - yield_strain)\nt_neck = (strain_necking_ref - uts_strain) / (fracture_strain - uts_strain)\n\nstress_elastic_base = youngs_modulus * strain_elastic_ref\nstress_hardening_base = yield_stress + (uts - yield_stress) * (2 * t_hard - t_hard**2)\nstress_necking_base = uts - (uts - fracture_stress) * t_neck**1.5\n\nstrain_all = np.concatenate([strain_elastic_ref, strain_hardening_ref, strain_necking_ref])\nstress_all = np.concatenate([stress_elastic_base, stress_hardening_base, stress_necking_base])\nregions = [\"Elastic\"] * 40 + [\"Strain Hardening\"] * 150 + [\"Necking\"] * 80\n\n# Per-region noise scale: small in elastic (nearly deterministic linear response),\n# growing through hardening, largest in necking (localised plastic instability)\nnoise_scale = np.concatenate(\n    [\n        np.full(40, 0.5),  # elastic: nearly deterministic\n        np.full(150, 4.0),  # hardening: specimen-to-specimen variability\n        np.full(80, 8.0),  # necking: most variable (localised deformation)\n    ]\n)\n\n# Build long-format DataFrame for seaborn's statistical estimation\nn_specimens = 5\nspecimens = []\nfor i in range(n_specimens):\n    noise = np.random.normal(0, noise_scale)\n    specimens.append(\n        pd.DataFrame({\"strain\": strain_all, \"stress\": stress_all + noise, \"region\": regions, \"specimen\": i})\n    )\ndf = pd.concat(specimens, ignore_index=True)\n\n# 0.2% offset line (parallel to elastic slope, offset by 0.002 strain)\noffset = 0.002\nyield_offset_strain = offset + yield_stress / youngs_modulus  # ~0.006\nyield_offset_stress = yield_stress\noffset_strain = np.linspace(offset, yield_offset_strain + 0.004, 50)\noffset_stress_line = youngs_modulus * (offset_strain - offset)\n\n# Theme-adaptive chrome\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Plot — canvas 3200 × 1800 px (8 in × 4.5 in @ 400 dpi)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\nfig.set_facecolor(PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nregion_palette = {\"Elastic\": COLOR_ELASTIC, \"Strain Hardening\": COLOR_HARDENING, \"Necking\": COLOR_NECKING}\nregion_order = [\"Elastic\", \"Strain Hardening\", \"Necking\"]\n\n# Stress-strain curve coloured by region — seaborn aggregates 5 specimens,\n# drawing mean line + ±1 SD band to show material-to-specimen variability\nsns.lineplot(\n    data=df,\n    x=\"strain\",\n    y=\"stress\",\n    hue=\"region\",\n    hue_order=region_order,\n    palette=region_palette,\n    linewidth=3.0,\n    errorbar=(\"sd\", 1),\n    err_style=\"band\",\n    err_kws={\"alpha\": 0.18},\n    ax=ax,\n    legend=False,\n)\n\n# 0.2% offset dashed line\nax.plot(offset_strain, offset_stress_line, linestyle=\"--\", linewidth=1.8, color=INK_MUTED)\n\n# Subtle region shading\nfor x0, x1, col in [\n    (0, yield_strain, COLOR_ELASTIC),\n    (yield_strain, uts_strain, COLOR_HARDENING),\n    (uts_strain, fracture_strain, COLOR_NECKING),\n]:\n    ax.axvspan(x0, x1, alpha=0.05, color=col, zorder=0)\n\n# Critical point markers (mean curve positions)\ncritical = pd.DataFrame(\n    {\n        \"strain\": [yield_offset_strain, uts_strain, fracture_strain],\n        \"stress\": [yield_offset_stress, uts, fracture_stress],\n        \"point\": [\"Yield Point\", \"UTS\", \"Fracture\"],\n    }\n)\npoint_palette = {\"Yield Point\": COLOR_ELASTIC, \"UTS\": COLOR_NECKING, \"Fracture\": INK}\nsns.scatterplot(\n    data=critical,\n    x=\"strain\",\n    y=\"stress\",\n    hue=\"point\",\n    palette=point_palette,\n    s=200,\n    edgecolor=PAGE_BG,\n    linewidth=1.5,\n    ax=ax,\n    zorder=6,\n    legend=False,\n)\n\n# Annotations for key points and elastic modulus\nax.annotate(\n    \"Yield Point\\n(0.2% offset)\",\n    xy=(yield_offset_strain, yield_offset_stress),\n    xytext=(0.030, 200),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=COLOR_ELASTIC,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": COLOR_ELASTIC, \"lw\": 1.2},\n    ha=\"left\",\n    va=\"top\",\n)\nax.annotate(\n    f\"UTS = {uts} MPa\",\n    xy=(uts_strain, uts),\n    xytext=(0.090, uts + 22),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=COLOR_NECKING,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": COLOR_NECKING, \"lw\": 1.2},\n    ha=\"left\",\n    va=\"bottom\",\n)\nax.annotate(\n    \"Fracture\",\n    xy=(fracture_strain, fracture_stress),\n    xytext=(0.090, 195),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=INK,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": INK, \"lw\": 1.2},\n    ha=\"left\",\n    va=\"top\",\n)\nax.annotate(\n    f\"E = {youngs_modulus // 1000} GPa\",\n    xy=(yield_strain * 0.5, youngs_modulus * yield_strain * 0.5),\n    xytext=(0.038, 52),\n    fontsize=8,\n    fontstyle=\"italic\",\n    color=COLOR_ELASTIC,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": COLOR_ELASTIC, \"lw\": 1.0},\n    ha=\"left\",\n    va=\"center\",\n)\n\n# Inline region labels — \"Elastic\" placed high in the narrow elastic zone\n# (raised from 0.20 to 0.62 to reduce crowding with the E=69 GPa annotation)\nax.text(\n    yield_strain / 2,\n    uts * 0.62,\n    \"Elastic\",\n    fontsize=8,\n    color=COLOR_ELASTIC,\n    ha=\"center\",\n    va=\"center\",\n    fontstyle=\"italic\",\n)\nax.text(\n    (yield_strain + uts_strain) / 2,\n    uts * 0.40,\n    \"Strain\\nHardening\",\n    fontsize=8,\n    color=COLOR_HARDENING,\n    ha=\"center\",\n    va=\"center\",\n    fontstyle=\"italic\",\n)\nax.text(\n    (uts_strain + fracture_strain) / 2,\n    uts * 0.50,\n    \"Necking\",\n    fontsize=8,\n    color=COLOR_NECKING,\n    ha=\"center\",\n    va=\"center\",\n    fontstyle=\"italic\",\n)\n\n# Spine and grid styling\nsns.despine(ax=ax)\nax.xaxis.grid(False)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_xlabel(\"Engineering Strain\", fontsize=10, color=INK)\nax.set_ylabel(\"Engineering Stress (MPa)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_xlim(-0.005, fracture_strain + 0.030)\nax.set_ylim(-10, uts + 65)\n\n# Title with length-based font scaling (67-char baseline for seaborn = 12pt)\ntitle = \"6061-T6 Aluminum · line-stress-strain · python · seaborn · anyplot.ai\"\nn = len(title)\nratio = 67 / n if n > 67 else 1.0\ntitle_fontsize = max(8, round(12 * ratio))\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK)\n\n# Manual legend (three regions + offset line)\nlegend_handles = [\n    Line2D([0], [0], color=COLOR_ELASTIC, linewidth=3.0, label=\"Elastic\"),\n    Line2D([0], [0], color=COLOR_HARDENING, linewidth=3.0, label=\"Strain Hardening\"),\n    Line2D([0], [0], color=COLOR_NECKING, linewidth=3.0, label=\"Necking\"),\n    Line2D([0], [0], color=INK_MUTED, linewidth=1.8, linestyle=\"--\", label=\"0.2% Offset Line\"),\n]\nax.legend(\n    handles=legend_handles, fontsize=8, loc=\"lower right\", framealpha=0.9, facecolor=ELEVATED_BG, edgecolor=INK_SOFT\n)\n\n# Save — no bbox_inches to preserve exact 3200×1800 canvas\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}