{"spec_id":"line-stress-strain","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nline-stress-strain: Engineering Stress-Strain Curve\nLibrary: letsplot 4.11.0 | Python 3.13.15\nQuality: 91/100 | Updated: 2026-08-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave as export_ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme-adaptive chrome (Imprint)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\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 categorical palette (theme-independent)\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data - Mild steel tensile test simulation\nnp.random.seed(42)\n\n# Material properties for mild steel\nyoungs_modulus = 210000  # MPa\nyield_strength = 250  # MPa\nuts = 400  # MPa (ultimate tensile strength)\nfracture_strain = 0.35\nuts_strain = 0.22\nyield_strain = yield_strength / youngs_modulus  # ~0.00119\n\n# Elastic region (0 to yield)\nn_elastic = 60\nstrain_elastic = np.linspace(0, yield_strain, n_elastic)\nstress_elastic = youngs_modulus * strain_elastic\n\n# Yield plateau (mild steel has a distinct yield point)\nn_plateau = 20\nstrain_plateau = np.linspace(yield_strain, 0.015, n_plateau)\nstress_plateau = yield_strength + np.random.normal(0, 1.5, n_plateau)\n\n# Strain hardening region (from end of plateau to UTS)\nn_hardening = 120\nstrain_hardening = np.linspace(0.015, uts_strain, n_hardening)\nstress_hardening = yield_strength + (uts - yield_strength) * (\n    1 - np.exp(-8 * (strain_hardening - 0.015) / (uts_strain - 0.015))\n)\nstress_hardening += np.random.normal(0, 1.0, n_hardening)\n\n# Necking region (UTS to fracture)\nn_necking = 60\nstrain_necking = np.linspace(uts_strain, fracture_strain, n_necking)\nstress_necking = uts - (uts - 280) * ((strain_necking - uts_strain) / (fracture_strain - uts_strain)) ** 1.5\nstress_necking += np.random.normal(0, 1.5, n_necking)\n\n# Combine all regions\nstrain = np.concatenate([strain_elastic, strain_plateau, strain_hardening, strain_necking])\nstress = np.concatenate([stress_elastic, stress_plateau, stress_hardening, stress_necking])\n\ndf = pd.DataFrame({\"strain\": strain, \"stress\": stress})\n\n# 0.2% offset line for yield point determination\noffset_val = 0.002\noffset_line_strain = np.linspace(offset_val, offset_val + yield_strength / youngs_modulus + 0.003, 50)\noffset_line_stress = youngs_modulus * (offset_line_strain - offset_val)\noffset_line_stress = np.clip(offset_line_stress, 0, yield_strength + 30)\ndf_offset = pd.DataFrame({\"strain\": offset_line_strain, \"stress\": offset_line_stress})\n\n# Key points\nyield_point_strain = offset_val + yield_strength / youngs_modulus\nyield_point_stress = yield_strength\nfracture_stress = stress_necking[-1]\n\ndf_points = pd.DataFrame(\n    {\n        \"strain\": [yield_point_strain, uts_strain, fracture_strain],\n        \"stress\": [yield_point_stress, uts, fracture_stress],\n        \"type\": [\"Yield\", \"UTS\", \"Fracture\"],\n    }\n)\n\n# Consolidated annotations DataFrame (positions tuned to avoid crowding near the origin)\ndf_annotations = pd.DataFrame(\n    {\n        \"x\": [yield_point_strain + 0.03, uts_strain + 0.015, fracture_strain - 0.048, 0.006, 0.058, 0.005, 0.11, 0.29],\n        \"y\": [yield_point_stress + 60, uts + 10, fracture_stress - 30, 80, 190, 350, 350, 350],\n        \"label\": [\n            f\"Yield Point\\n({yield_strength} MPa)\",\n            f\"UTS ({uts} MPa)\",\n            \"Fracture\",\n            f\"E = {youngs_modulus // 1000} GPa\",\n            \"0.2% offset\",\n            \"Elastic\",\n            \"Strain Hardening\",\n            \"Necking\",\n        ],\n        \"group\": [\"yield\", \"uts\", \"fracture\", \"modulus\", \"offset\", \"region\", \"region\", \"region\"],\n    }\n)\n\n# Imprint palette roles: main curve = blue, yield = lavender, UTS = matte red (critical/peak),\n# fracture = neutral ink (structural reference), 0.2% offset = neutral dashed construction line\ncolor_main = IMPRINT_PALETTE[2]  # blue\ncolor_yield = IMPRINT_PALETTE[1]  # lavender\ncolor_uts = IMPRINT_PALETTE[4]  # matte red\ncolor_fracture = INK_SOFT\ncolor_offset = INK_SOFT\n\n# Segment connector lines from key points to annotations (distinctive lets-plot feature)\ndf_segments = pd.DataFrame(\n    {\n        \"x\": [yield_point_strain, uts_strain, fracture_strain],\n        \"y\": [yield_point_stress, uts, fracture_stress],\n        \"xend\": [yield_point_strain + 0.028, uts_strain + 0.014, fracture_strain - 0.038],\n        \"yend\": [yield_point_stress + 52, uts + 8, fracture_stress - 22],\n    }\n)\n\n# Plot\nplot = (\n    ggplot()\n    # Region background bands using geom_rect (distinctive lets-plot feature)\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"region\"),\n        data=pd.DataFrame(\n            {\n                \"xmin\": [0, 0.015, uts_strain],\n                \"xmax\": [0.015, uts_strain, fracture_strain],\n                \"ymin\": [0, 0, 0],\n                \"ymax\": [460, 460, 460],\n                \"region\": [\"Elastic\", \"Strain Hardening\", \"Necking\"],\n            }\n        ),\n        alpha=0.16,\n        color=\"transparent\",\n    )\n    + scale_fill_manual(\n        values={\n            \"Elastic\": IMPRINT_PALETTE[2],\n            \"Strain Hardening\": IMPRINT_PALETTE[3],\n            \"Necking\": IMPRINT_PALETTE[6],\n            \"Yield\": color_yield,\n            \"UTS\": color_uts,\n            \"Fracture\": color_fracture,\n        }\n    )\n    # Main stress-strain curve with tooltips (distinctive lets-plot feature)\n    + geom_line(\n        aes(x=\"strain\", y=\"stress\"),\n        data=df,\n        color=color_main,\n        size=1.6,\n        tooltips=layer_tooltips()\n        .format(\"strain\", \".4f\")\n        .format(\"stress\", \".1f\")\n        .line(\"Strain: @strain\")\n        .line(\"Stress: @stress MPa\"),\n    )\n    # 0.2% offset line (construction reference line)\n    + geom_line(aes(x=\"strain\", y=\"stress\"), data=df_offset, color=color_offset, size=0.9, linetype=\"dashed\")\n    # Segment connectors from points to labels (geom_segment - distinctive feature)\n    + geom_segment(\n        aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_segments, color=INK_MUTED, size=0.5, linetype=\"dotted\"\n    )\n    # Key points with tooltips (distinctive lets-plot feature)\n    + geom_point(\n        aes(x=\"strain\", y=\"stress\", fill=\"type\"),\n        data=df_points,\n        color=PAGE_BG,\n        size=5.5,\n        shape=21,\n        stroke=1.4,\n        tooltips=layer_tooltips().line(\"@type\").line(\"Strain: @strain\").line(\"Stress: @stress MPa\"),\n    )\n    + guides(fill=\"none\")\n    # Annotations - key points\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'yield'\"),\n        size=4.2,\n        color=color_yield,\n        hjust=0,\n    )\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'uts'\"),\n        size=4.2,\n        color=color_uts,\n        hjust=0,\n    )\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'fracture'\"),\n        size=4.2,\n        color=color_fracture,\n        hjust=0.5,\n    )\n    # Elastic modulus annotation\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'modulus'\"),\n        size=3.8,\n        color=color_main,\n        hjust=0,\n        fontface=\"italic\",\n    )\n    # Offset label\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'offset'\"),\n        size=3.4,\n        color=color_offset,\n        hjust=0,\n        fontface=\"italic\",\n    )\n    # Region labels\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=df_annotations.query(\"group == 'region'\"),\n        size=4.6,\n        color=INK_MUTED,\n        fontface=\"italic\",\n    )\n    # Styling\n    + labs(\n        x=\"Engineering Strain\",\n        y=\"Engineering Stress (MPa)\",\n        title=\"line-stress-strain · python · letsplot · anyplot.ai\",\n    )\n    + scale_x_continuous(breaks=[0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35])\n    + scale_y_continuous(breaks=[0, 50, 100, 150, 200, 250, 300, 350, 400, 450])\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        plot_title=element_text(size=16, color=INK, face=\"bold\"),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3),\n        panel_grid_minor=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_border=element_blank(),\n        axis_ticks=element_blank(),\n        axis_ticks_length=0,\n        plot_margin=[15, 20, 10, 10],\n    )\n)\n\n# Save\nexport_ggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=4)\nexport_ggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}