{"spec_id":"line-stress-strain","library":"makie","language":"julia","code":"# anyplot.ai\n# line-stress-strain: Engineering Stress-Strain Curve\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 94/100 | Created: 2026-08-24\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\") ---\nconst THEME     = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG   = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK       = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT  = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\n# Imprint categorical palette — first series always brand green\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\nconst BRAND = IMPRINT_PALETTE[1]\nconst ANYPLOT_AMBER = colorant\"#DDCC77\"\n\n# --- Data: mild steel tensile test -------------------------------------------\nelastic_modulus   = 200_000.0  # MPa (Young's modulus, mild steel)\nyield_stress      = 250.0      # MPa (upper yield point)\nyield_strain      = yield_stress / elastic_modulus\nluders_end_strain = 0.02       # end of the Luders (yield) plateau\nuts_strain        = 0.18\nuts_stress        = 420.0      # MPa\nfracture_strain   = 0.28\nfracture_stress   = 330.0      # MPa\n\nn_elastic = 20\nelastic_strain = collect(range(0.0, yield_strain; length=n_elastic))\nelastic_stress = elastic_modulus .* elastic_strain\n\nn_luders = 40\nluders_strain = collect(range(yield_strain, luders_end_strain; length=n_luders))\nluders_serration = (rand(n_luders) .- 0.5) .* 6.0\nluders_stress = fill(yield_stress, n_luders) .+ luders_serration\nluders_stress[1] = yield_stress\nluders_stress[end] = yield_stress\n\nn_hardening = 100\nhardening_strain = collect(range(luders_end_strain, uts_strain; length=n_hardening))\nhardening_progress = (hardening_strain .- luders_end_strain) ./ (uts_strain - luders_end_strain)\nhardening_stress = yield_stress .+ (uts_stress - yield_stress) .* hardening_progress .^ 0.55\n\nn_necking = 50\nnecking_strain = collect(range(uts_strain, fracture_strain; length=n_necking))\nnecking_progress = (necking_strain .- uts_strain) ./ (fracture_strain - uts_strain)\nnecking_stress = uts_stress .- (uts_stress - fracture_stress) .* necking_progress .^ 1.4\n\nstrain = vcat(elastic_strain, luders_strain[2:end], hardening_strain[2:end], necking_strain[2:end])\nstress = vcat(elastic_stress, luders_stress[2:end], hardening_stress[2:end], necking_stress[2:end])\n\n# 0.2% offset construction line — parallel to the elastic slope, shifted by 0.002 strain\noffset = 0.002\noffset_strain_end = offset + (yield_stress * 1.15) / elastic_modulus\noffset_line_strain = [offset, offset_strain_end]\noffset_line_stress = elastic_modulus .* (offset_line_strain .- offset)\nyield_point_strain = offset + yield_stress / elastic_modulus\nyield_point_stress = yield_stress\n\n# Bounds of the elastic-region inset (zoomed view of the near-vertical rise)\nzoom_x_max = offset_strain_end * 1.3\nzoom_y_max = yield_stress * 1.2\n\n# --- Title (scale fontsize to length; 67-char baseline) -----------------------\ntitle_str = \"Mild Steel Tensile Test · line-stress-strain · julia · makie · anyplot.ai\"\ntitle_ratio = length(title_str) > 67 ? 67 / length(title_str) : 1.0\ntitle_fontsize = round(Int, 20 * title_ratio)\n\n# --- Plot ----------------------------------------------------------------------\nfig = Figure(size=(1600, 900), fontsize=14, backgroundcolor=PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title = title_str,\n    titlesize = title_fontsize,\n    titlecolor = INK,\n    xlabel = \"Engineering Strain\",\n    ylabel = \"Engineering Stress (MPa)\",\n    xlabelsize = 16,\n    ylabelsize = 16,\n    xlabelcolor = INK,\n    ylabelcolor = INK,\n    xticklabelsize = 13,\n    yticklabelsize = 13,\n    xticklabelcolor = INK_SOFT,\n    yticklabelcolor = INK_SOFT,\n    xtickcolor = INK_SOFT,\n    ytickcolor = INK_SOFT,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    leftspinecolor = INK_SOFT,\n    bottomspinecolor = INK_SOFT,\n    xgridvisible = false,\n    ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    yminorgridvisible = false,\n)\n\nxlims!(ax, -0.01, fracture_strain * 1.1)\nylims!(ax, 0, uts_stress * 1.28)\n\n# Region bands — loading (elastic + strain hardening, brand tint) vs.\n# necking (semantic red tint, foreshadowing fracture). The elastic segment\n# is too narrow in strain to shade on its own (it is a near-vertical rise),\n# so it is called out with the \"Elastic\" text label below instead.\nvspan!(ax, 0.0, uts_strain; color=(IMPRINT_PALETTE[1], 0.05))\nvspan!(ax, uts_strain, fracture_strain; color=(IMPRINT_PALETTE[5], 0.06))\n\nlabel_y = uts_stress * 1.2\ntext!(ax, yield_strain + 0.008, label_y; text=\"Elastic\",\n      color=INK_MUTED, fontsize=14, align=(:left, :center))\ntext!(ax, (luders_end_strain + uts_strain) / 2, label_y; text=\"Plastic (strain hardening)\",\n      color=INK_MUTED, fontsize=14, align=(:center, :center))\ntext!(ax, (uts_strain + fracture_strain) / 2, label_y; text=\"Necking\",\n      color=INK_MUTED, fontsize=14, align=(:center, :center))\n\n# 0.2% offset construction line\nlines!(ax, offset_line_strain, offset_line_stress; color=INK_SOFT, linewidth=2, linestyle=:dash)\n\n# Stress-strain curve — first (and only) series, always Imprint position 1\nlines!(ax, strain, stress; color=BRAND, linewidth=3.5)\n\n# Dotted marker box around the elastic region, linking it to the zoomed inset below\nlines!(ax, [0.0, zoom_x_max, zoom_x_max, 0.0, 0.0], [0.0, 0.0, zoom_y_max, zoom_y_max, 0.0];\n       color=INK_SOFT, linewidth=1, linestyle=:dot)\n\n# Critical points — labels pulled well clear of the crowded origin cluster.\n# Each leader drops straight down from its marker (staying at constant strain,\n# so it never cuts diagonally across the near-flat Luders plateau) before\n# stepping right into open space below the curve for the label text.\nscatter!(ax, [yield_point_strain], [yield_point_stress];\n         color=ANYPLOT_AMBER, markersize=22, strokewidth=2, strokecolor=PAGE_BG)\nyield_leader_y = 130.0\nlines!(ax, [yield_point_strain, yield_point_strain], [yield_point_stress, yield_leader_y];\n       color=INK_SOFT, linewidth=1)\ntext!(ax, yield_point_strain + 0.006, yield_leader_y; text=\"Yield point\\n(0.2% offset)\",\n      color=INK, fontsize=13, align=(:left, :center))\n\noffset_leader_y = 205.0\nlines!(ax, [offset_strain_end, offset_strain_end], [offset_line_stress[end], offset_leader_y];\n       color=INK_SOFT, linewidth=1)\ntext!(ax, offset_strain_end + 0.006, offset_leader_y; text=\"0.2% offset\\nconstruction line\",\n      color=INK_SOFT, fontsize=13, align=(:left, :center))\n\nscatter!(ax, [uts_strain], [uts_stress];\n         color=IMPRINT_PALETTE[3], markersize=22, strokewidth=2, strokecolor=PAGE_BG)\ntext!(ax, uts_strain, uts_stress + 16; text=\"UTS\",\n      color=INK, fontsize=13, align=(:center, :bottom))\n\nscatter!(ax, [fracture_strain], [fracture_stress];\n         color=IMPRINT_PALETTE[5], markersize=24, marker=:xcross, strokewidth=3)\ntext!(ax, fracture_strain, fracture_stress - 26; text=\"Fracture\",\n      color=INK, fontsize=13, align=(:right, :top))\n\n# --- Elastic-region inset: makes the near-vertical modulus slope genuinely\n# visible instead of just annotated in text (addresses DE-03 review feedback).\n# Occupies the bottom-right of the main axis, where the curve never reaches.\ninset_ax = Axis(\n    fig[1, 1];\n    width = Relative(0.30),\n    height = Relative(0.36),\n    halign = 0.97,\n    valign = 0.07,\n    backgroundcolor = PAGE_BG,\n    title = \"Elastic region (zoomed)\",\n    titlesize = 12,\n    titlecolor = INK_SOFT,\n    xlabel = \"Strain\",\n    ylabel = \"Stress (MPa)\",\n    xlabelsize = 10,\n    ylabelsize = 10,\n    xlabelcolor = INK_SOFT,\n    ylabelcolor = INK_SOFT,\n    xticklabelsize = 9,\n    yticklabelsize = 9,\n    xticklabelcolor = INK_SOFT,\n    yticklabelcolor = INK_SOFT,\n    xtickcolor = INK_SOFT,\n    ytickcolor = INK_SOFT,\n    topspinevisible = true,\n    rightspinevisible = true,\n    topspinecolor = INK_SOFT,\n    rightspinecolor = INK_SOFT,\n    leftspinecolor = INK_SOFT,\n    bottomspinecolor = INK_SOFT,\n    xgridvisible = false,\n    ygridvisible = false,\n)\n\nzoom_mask = strain .<= zoom_x_max\nlines!(inset_ax, strain[zoom_mask], stress[zoom_mask]; color=BRAND, linewidth=2.5)\nlines!(inset_ax, offset_line_strain, offset_line_stress; color=INK_SOFT, linewidth=1.5, linestyle=:dash)\nscatter!(inset_ax, [yield_point_strain], [yield_point_stress];\n         color=ANYPLOT_AMBER, markersize=14, strokewidth=1.5, strokecolor=PAGE_BG)\ntext!(inset_ax, zoom_x_max * 0.08, zoom_y_max * 0.62; text=\"E ≈ 200 GPa\",\n      color=INK, fontsize=11, align=(:left, :center))\n\nxlims!(inset_ax, 0, zoom_x_max)\nylims!(inset_ax, 0, zoom_y_max)\n\n# --- Save ------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit=2)\n"}