{"spec_id":"mohr-circle","library":"d3","language":"javascript","code":"// anyplot.ai\n// mohr-circle: Mohr's Circle for Stress Analysis\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-08-26\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 100, right: 90, bottom: 120, left: 130 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data: a steel plate element under combined loading (in-memory, fixed) --\nconst sigmaX = 90; // normal stress, x-face (MPa)\nconst sigmaY = 20; // normal stress, y-face (MPa)\nconst tauXY = 30; // shear stress, xy-plane (MPa)\n\nconst center = (sigmaX + sigmaY) / 2;\nconst radius = Math.sqrt(((sigmaX - sigmaY) / 2) ** 2 + tauXY ** 2);\nconst sigma1 = center + radius; // major principal stress\nconst sigma2 = center - radius; // minor principal stress\nconst tauMax = radius; // maximum shear stress\nconst thetaA = Math.atan2(tauXY, sigmaX - center); // 2*theta_p, radians\nconst thetaDeg = (thetaA * 180) / Math.PI;\n\n// --- Scales: equal span on both axes keeps the circle a true circle --------\nconst halfSpan = radius * 1.6;\nconst x = d3.scaleLinear().domain([center - halfSpan, center + halfSpan]).range([0, iw]);\nconst y = d3.scaleLinear().domain([-halfSpan, halfSpan]).range([ih, 0]);\nconst unitPx = iw / (2 * halfSpan); // pixels per MPa, identical on both axes\n\nconst cx = x(center);\nconst cy = y(0);\nconst rPx = radius * unitPx;\n\n// point helper: polar offset from the circle center, math convention (ccw, y up)\nconst polar = (rStress, thetaRad) => ({\n  px: cx + rStress * unitPx * Math.cos(thetaRad),\n  py: cy - rStress * unitPx * Math.sin(thetaRad),\n});\n\n// --- SVG mount ---------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Grid ---------------------------------------------------------------------\nconst xAxisGrid = d3.axisBottom(x).ticks(6).tickSize(-ih).tickFormat(\"\");\nconst yAxisGrid = d3.axisLeft(y).ticks(6).tickSize(-iw).tickFormat(\"\");\ng.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(xAxisGrid)\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\").attr(\"stroke\", t.grid);\ng.append(\"g\").call(yAxisGrid)\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\").attr(\"stroke\", t.grid);\n\n// --- Reference lines through the circle center --------------------------------\ng.append(\"line\")\n  .attr(\"x1\", 0).attr(\"x2\", iw).attr(\"y1\", cy).attr(\"y2\", cy)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"6,5\").attr(\"opacity\", 0.6);\ng.append(\"line\")\n  .attr(\"x1\", cx).attr(\"x2\", cx).attr(\"y1\", 0).attr(\"y2\", ih)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"6,5\").attr(\"opacity\", 0.6);\n\n// --- Axes -----------------------------------------------------------------\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(d3.axisBottom(x).ticks(6));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6));\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 68).attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"20px\")\n  .text(\"Normal Stress σ (MPa)\");\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2).attr(\"y\", -95).attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"20px\")\n  .text(\"Shear Stress τ (MPa)\");\n\n// --- Mohr's circle -------------------------------------------------------------\ng.append(\"circle\")\n  .attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", rPx)\n  .attr(\"fill\", t.palette[0]).attr(\"fill-opacity\", 0.08)\n  .attr(\"stroke\", t.palette[0]).attr(\"stroke-width\", 3);\n\n// --- Diameter through A and B (secondary construction geometry, de-emphasized) --\nconst A = { sigma: sigmaX, tau: tauXY };\nconst B = { sigma: sigmaY, tau: -tauXY };\ng.append(\"line\")\n  .attr(\"x1\", x(A.sigma)).attr(\"y1\", y(A.tau))\n  .attr(\"x2\", x(B.sigma)).attr(\"y2\", y(B.tau))\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1).attr(\"stroke-dasharray\", \"4,4\").attr(\"opacity\", 0.4);\n\n// --- Angle 2*theta_p arc, from the sigma1 axis to point A (also de-emphasized) --\nconst arcRStress = radius * 0.32;\nconst arcStart = polar(arcRStress, 0);\nconst arcEnd = polar(arcRStress, thetaA);\nconst sweep = thetaA > 0 ? 1 : 0;\nconst path = d3.path();\npath.moveTo(arcStart.px, arcStart.py);\npath.arc(cx, cy, arcRStress * unitPx, 0, -thetaA, sweep === 1);\ng.append(\"path\").attr(\"d\", path.toString())\n  .attr(\"fill\", \"none\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1).attr(\"opacity\", 0.6);\n\nconst arcMid = polar(arcRStress * 1.55, thetaA / 2);\ng.append(\"text\")\n  .attr(\"x\", arcMid.px).attr(\"y\", arcMid.py).attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\")\n  .text(`2θp ≈ ${thetaDeg.toFixed(1)}°`);\n\n// --- Center point C ---------------------------------------------------------------\ng.append(\"circle\").attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", 5).attr(\"fill\", t.inkSoft);\ng.append(\"text\")\n  .attr(\"x\", cx + 20).attr(\"y\", cy - 24)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\")\n  .text(\"C\");\n\n// --- Stress points A and B (construction points, data-bound) ----------------------\nconst stressPoints = [\n  { p: A, label: `A(σx, τxy) = (${A.sigma}, ${A.tau})`, color: t.palette[1], dx: 16, dy: -14 },\n  { p: B, label: `B(σy, −τxy) = (${B.sigma}, ${B.tau})`, color: t.palette[2], dx: 16, dy: 24 },\n];\nconst stressG = g.selectAll(null).data(stressPoints).join(\"g\").attr(\"class\", \"stress-point\");\nstressG.append(\"circle\")\n  .attr(\"cx\", (d) => x(d.p.sigma)).attr(\"cy\", (d) => y(d.p.tau)).attr(\"r\", 8)\n  .attr(\"fill\", (d) => d.color).attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 1.5);\nstressG.append(\"text\")\n  .attr(\"x\", (d) => x(d.p.sigma) + d.dx).attr(\"y\", (d) => y(d.p.tau) + d.dy)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"16px\").style(\"font-weight\", \"600\")\n  .text((d) => d.label);\n\n// --- Principal stresses (sigma1, sigma2) and maximum shear stress: the focal point,\n// bolder than the A/B construction points above, drawn last so they read on top ---\nconst extremes = [\n  { px: x(sigma1), py: y(0), label: `σ1 = ${sigma1.toFixed(1)} MPa`, dx: 0, dy: 36, anchor: \"middle\" },\n  { px: x(sigma2), py: y(0), label: `σ2 = ${sigma2.toFixed(1)} MPa`, dx: 0, dy: -24, anchor: \"middle\" },\n  { px: cx, py: y(tauMax), label: `τmax = ${tauMax.toFixed(1)} MPa`, dx: -18, dy: -14, anchor: \"end\" },\n  { px: cx, py: y(-tauMax), label: `τmax = ${tauMax.toFixed(1)} MPa`, dx: -18, dy: 26, anchor: \"end\" },\n];\nconst extremeG = g.selectAll(null).data(extremes).join(\"g\").attr(\"class\", \"extreme-point\");\nextremeG.append(\"circle\")\n  .attr(\"cx\", (d) => d.px).attr(\"cy\", (d) => d.py).attr(\"r\", 11)\n  .attr(\"fill\", t.palette[3]).attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 2.5);\nextremeG.append(\"text\")\n  .attr(\"x\", (d) => d.px + d.dx).attr(\"y\", (d) => d.py + d.dy).attr(\"text-anchor\", (d) => d.anchor)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"16px\").style(\"font-weight\", \"700\")\n  .text((d) => d.label);\n\n// --- Title ----------------------------------------------------------------------\nconst titleText = \"Steel Plate in Combined Loading · mohr-circle · javascript · d3 · anyplot.ai\";\nconst titleFontSize = Math.max(15, Math.round(24 * Math.min(1, 67 / titleText.length)));\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 52).attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", `${titleFontSize}px`).style(\"font-weight\", \"600\")\n  .text(titleText);\n"}