{"spec_id":"line-stress-strain","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-stress-strain: Engineering Stress-Strain Curve\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-08-24\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsText } from \"@mui/x-charts/ChartsText\";\nimport { useXScale, useYScale, useDrawingArea } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Material model: mild-steel tensile test (deterministic) ---------------\nconst E = 200000; // Young's modulus, MPa\nconst OFFSET = 0.002; // 0.2% strain offset for yield determination\nconst YIELD_STRESS = 250; // MPa, yield-plateau stress\nconst ELASTIC_END = YIELD_STRESS / E; // proportional limit, ≈ 0.00125\nconst YIELD_STRAIN = OFFSET + YIELD_STRESS / E; // where the offset line meets the plateau, ≈ 0.00325\nconst PLATEAU_END = 0.02; // Lüders strain — plateau gives way to strain hardening\nconst UTS_STRAIN = 0.2;\nconst UTS_STRESS = 450;\nconst FRACTURE_STRAIN = 0.26;\nconst FRACTURE_STRESS = 300;\nconst OFFSET_EXIT_STRAIN = OFFSET + (UTS_STRESS + 50) / E; // where the offset line exits the plot area\nconst Y_MAX = UTS_STRESS + 60;\n\n// Small fixed-seed LCG — Lüders-band serration on the yield plateau\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nfunction linspace(a, b, n) {\n  const out = [];\n  for (let i = 0; i < n; i += 1) out.push(a + ((b - a) * i) / (n - 1));\n  return out;\n}\n\n// --- Strain samples (170 pts): dense near the elastic/yield transition,\n// plus the two 0.2%-offset construction points ------------------------------\nconst strains = [\n  ...linspace(0, ELASTIC_END, 6),\n  ...linspace(ELASTIC_END, YIELD_STRAIN, 6).slice(1),\n  ...linspace(YIELD_STRAIN, PLATEAU_END, 24).slice(1),\n  ...linspace(PLATEAU_END, UTS_STRAIN, 90).slice(1),\n  ...linspace(UTS_STRAIN, FRACTURE_STRAIN, 46).slice(1),\n  OFFSET,\n  OFFSET_EXIT_STRAIN,\n].sort((a, b) => a - b);\n\nfunction stressElasticYield(strain) {\n  if (strain <= ELASTIC_END) return (strain / ELASTIC_END) * YIELD_STRESS;\n  if (strain <= PLATEAU_END) return YIELD_STRESS + (rand() - 0.5) * 6;\n  return null;\n}\n\nfunction stressHardening(strain) {\n  if (strain < PLATEAU_END || strain > UTS_STRAIN) return null;\n  const frac = (strain - PLATEAU_END) / (UTS_STRAIN - PLATEAU_END);\n  return YIELD_STRESS + (UTS_STRESS - YIELD_STRESS) * (2 * frac - frac * frac);\n}\n\nfunction stressNecking(strain) {\n  if (strain < UTS_STRAIN || strain > FRACTURE_STRAIN) return null;\n  const frac = (strain - UTS_STRAIN) / (FRACTURE_STRAIN - UTS_STRAIN);\n  return UTS_STRESS - (UTS_STRESS - FRACTURE_STRESS) * (3 * frac * frac - 2 * frac * frac * frac);\n}\n\nfunction stressOffsetLine(strain) {\n  if (strain < OFFSET || strain > OFFSET_EXIT_STRAIN) return null;\n  return E * (strain - OFFSET);\n}\n\nconst dataset = strains.map((strain) => ({\n  strain,\n  stressElasticYield: stressElasticYield(strain),\n  stressHardening: stressHardening(strain),\n  stressNecking: stressNecking(strain),\n  stressOffset: stressOffsetLine(strain),\n}));\n\n// Region-label anchor points, in data coordinates\nconst HARDENING_LABEL_X = 0.11;\nconst HARDENING_LABEL_Y = stressHardening(HARDENING_LABEL_X) + 45;\nconst NECKING_LABEL_X = 0.23;\nconst NECKING_LABEL_Y = stressNecking(NECKING_LABEL_X) + 45;\n\n// --- Floating annotation text, placed in data coordinates -------------------\nfunction DataLabel({ x, y, text, fill, fontSize = 15, anchor = \"middle\" }) {\n  const xScale = useXScale(\"strain-axis\");\n  const yScale = useYScale(\"stress-axis\");\n  return (\n    <ChartsText\n      x={xScale(x)}\n      y={yScale(y)}\n      text={text}\n      fill={fill}\n      style={{ fontSize, textAnchor: anchor, dominantBaseline: \"middle\" }}\n    />\n  );\n}\n\n// Critical-point marker: solid dot with a page-bg edge, drawn above the\n// reference lines so it stays visible where a line crosses the curve.\nfunction PointMarker({ x, y, color }) {\n  const xScale = useXScale(\"strain-axis\");\n  const yScale = useYScale(\"stress-axis\");\n  return <circle cx={xScale(x)} cy={yScale(y)} r={10} fill={color} stroke={t.pageBg} strokeWidth={3} />;\n}\n\n// Hand-rolled y-axis title, anchored to a fixed distance from the canvas edge\n// rather than MUI X's built-in `yAxis.label` (whose offset is derived from\n// `tickFontSize`, not the actual rendered width of 3-digit tick values, so it\n// collides with the tick-label column for this data range).\nfunction YAxisTitle({ text }) {\n  const { top, height } = useDrawingArea();\n  return (\n    <ChartsText\n      x={26}\n      y={top + height / 2}\n      text={text}\n      fill={t.ink}\n      style={{ fontSize: 16, angle: -90, textAnchor: \"middle\", dominantBaseline: \"auto\" }}\n    />\n  );\n}\n\n// --- Chart -------------------------------------------------------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_HEIGHT = 60;\n\n  return (\n    <Box sx={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <Box sx={{ height: TITLE_HEIGHT, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <Typography sx={{ color: t.ink, fontSize: 22, fontWeight: 500, letterSpacing: 0.3 }}>\n          line-stress-strain · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n\n      <LineChart\n        width={width}\n        height={height - TITLE_HEIGHT}\n        dataset={dataset}\n        skipAnimation\n        xAxis={[\n          {\n            id: \"strain-axis\",\n            dataKey: \"strain\",\n            scaleType: \"linear\",\n            label: \"Engineering strain, ε (mm/mm)\",\n            min: 0,\n            max: FRACTURE_STRAIN * 1.05,\n            valueFormatter: (v) => v.toFixed(2),\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            labelStyle: { fontSize: 16, fill: t.ink },\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"stress-axis\",\n            min: 0,\n            max: Y_MAX,\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        ]}\n        series={[\n          {\n            id: \"elasticYield\",\n            dataKey: \"stressElasticYield\",\n            label: \"Elastic + yield\",\n            color: t.palette[0],\n            curve: \"linear\",\n            connectNulls: true,\n            showMark: false,\n          },\n          {\n            id: \"hardening\",\n            dataKey: \"stressHardening\",\n            label: \"Strain hardening\",\n            color: t.palette[1],\n            curve: \"monotoneX\",\n            connectNulls: true,\n            showMark: false,\n          },\n          {\n            id: \"necking\",\n            dataKey: \"stressNecking\",\n            label: \"Necking\",\n            color: t.palette[4],\n            curve: \"monotoneX\",\n            connectNulls: true,\n            showMark: false,\n          },\n          {\n            id: \"offsetLine\",\n            dataKey: \"stressOffset\",\n            label: \"0.2% offset\",\n            color: t.ink,\n            curve: \"linear\",\n            connectNulls: true,\n            showMark: false,\n          },\n        ]}\n        margin={{ top: 30, bottom: 90, left: 116, right: 40 }}\n        sx={{\n          \"& .MuiLineElement-root\": { strokeWidth: 3.5 },\n          \"& .MuiLineElement-series-offsetLine\": { strokeWidth: 1.75, strokeDasharray: \"10 6\" },\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft },\n          \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft },\n        }}\n        slotProps={{\n          legend: {\n            position: { vertical: \"top\", horizontal: \"right\" },\n            itemMarkWidth: 16,\n            itemMarkHeight: 3,\n            labelStyle: { fontSize: 14, fill: t.ink },\n          },\n        }}\n      >\n        <YAxisTitle text=\"Engineering stress, σ (MPa)\" />\n\n        <ChartsReferenceLine\n          x={YIELD_STRAIN}\n          label=\"Yield ≈ 250 MPa\"\n          labelAlign=\"start\"\n          spacing={{ x: 8, y: 16 }}\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"5 4\", strokeWidth: 1.25 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 13 }}\n        />\n        <ChartsReferenceLine\n          x={UTS_STRAIN}\n          label=\"UTS ≈ 450 MPa\"\n          labelAlign=\"start\"\n          spacing={{ x: 8, y: 16 }}\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"5 4\", strokeWidth: 1.25 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 13 }}\n        />\n        <ChartsReferenceLine\n          x={FRACTURE_STRAIN}\n          label=\"Fracture ≈ 300 MPa\"\n          labelAlign=\"start\"\n          spacing={{ x: 8, y: 16 }}\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"5 4\", strokeWidth: 1.25 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 13, textAnchor: \"end\" }}\n        />\n\n        <DataLabel x={0.012} y={130} text=\"Elastic + Yield\" fill={t.palette[0]} anchor=\"start\" />\n        <DataLabel x={HARDENING_LABEL_X} y={HARDENING_LABEL_Y} text=\"Plastic (Strain Hardening)\" fill={t.palette[1]} />\n        <DataLabel x={NECKING_LABEL_X} y={NECKING_LABEL_Y} text=\"Necking\" fill={t.palette[4]} />\n        <DataLabel x={0.05} y={Y_MAX - 25} text=\"E ≈ 200 GPa (elastic modulus)\" fill={t.inkSoft} anchor=\"start\" fontSize={14} />\n\n        <PointMarker x={YIELD_STRAIN} y={YIELD_STRESS} color={t.palette[0]} />\n        <PointMarker x={UTS_STRAIN} y={UTS_STRESS} color={t.palette[1]} />\n        <PointMarker x={FRACTURE_STRAIN} y={FRACTURE_STRESS} color={t.palette[4]} />\n      </LineChart>\n    </Box>\n  );\n}\n"}