{"spec_id":"mohr-circle","library":"muix","language":"javascript","code":"// anyplot.ai\n// mohr-circle: Mohr's Circle for Stress Analysis\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-26\n//# anyplot-orientation: square\n// anyplot.ai\n// mohr-circle: Mohr's Circle for Stress Analysis\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-08-26\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsText } from \"@mui/x-charts/ChartsText\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Stress state (in-memory, deterministic) --------------------------------\nconst sigmaX = 80; // MPa — normal stress on the x-face\nconst sigmaY = 20; // MPa — normal stress on the y-face\nconst tauXY = 30; // MPa — shear stress on the xy-plane\n\nconst center = (sigmaX + sigmaY) / 2;\nconst radius = Math.sqrt(((sigmaX - sigmaY) / 2) ** 2 + tauXY ** 2);\nconst sigma1 = center + radius;\nconst sigma2 = center - radius;\nconst tauMax = radius;\nconst twoThetaPDeg = (Math.atan2(tauXY, (sigmaX - sigmaY) / 2) * 180) / Math.PI;\n\n// --- Circle geometry, sampled parametrically (x is non-monotonic, so the\n// line series is positioned via an explicit numeric xAxis.data array rather\n// than relying on a sorted/categorical axis) --------------------------------\nconst CIRCLE_STEPS = 180;\nconst circleX = [];\nconst circleY = [];\nfor (let i = 0; i <= CIRCLE_STEPS; i++) {\n  const angle = (i / CIRCLE_STEPS) * 2 * Math.PI;\n  circleX.push(center + radius * Math.cos(angle));\n  circleY.push(radius * Math.sin(angle));\n}\n\n// --- Axis domain — equal span on both axes; combined with the equal-size\n// drawing area (see MARGIN below) this keeps the circle a true circle. ------\nconst PAD = radius * 0.75;\nconst X_MIN = center - radius - PAD;\nconst X_MAX = center + radius + PAD;\nconst Y_MIN = -radius - PAD;\nconst Y_MAX = radius + PAD;\n\nconst TITLE = \"mohr-circle · javascript · muix · anyplot.ai\";\nconst TITLE_HEIGHT = 70;\n\n// left+right and top+bottom leave an equal-size square drawing area — the\n// hard requirement for the circle to render with a true 1:1 aspect ratio.\nconst MARGIN = { top: 55, bottom: 65, left: 130, right: 60 };\n\n// --- Custom overlay: stress points, reference diameter, angle callout -------\n// Community `@mui/x-charts/hooks` (useXScale/useYScale) map data coordinates\n// to pixels so the annotations stay aligned with the circle at any size.\nfunction MohrAnnotations() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const toPx = (x, y) => ({ x: xScale(x), y: yScale(y) });\n\n  const c = toPx(center, 0);\n  const pointA = toPx(sigmaX, tauXY);\n  const pointB = toPx(sigmaY, -tauXY);\n  const pointS1 = toPx(sigma1, 0);\n  const pointS2 = toPx(sigma2, 0);\n  const pointTauMax = toPx(center, tauMax);\n  const pointTauMin = toPx(center, -tauMax);\n\n  const arcRadius = radius * 0.32;\n  const arcStepCount = 24;\n  const twoThetaPRad = (twoThetaPDeg * Math.PI) / 180;\n  const arcPath = Array.from({ length: arcStepCount + 1 }, (_, i) => {\n    const angle = (twoThetaPRad * i) / arcStepCount;\n    return toPx(center + arcRadius * Math.cos(angle), arcRadius * Math.sin(angle));\n  })\n    .map((p, i) => `${i === 0 ? \"M\" : \"L\"} ${p.x} ${p.y}`)\n    .join(\" \");\n  const angleLabelPos = toPx(\n    center + arcRadius * 1.7 * Math.cos(twoThetaPRad / 2),\n    arcRadius * 1.7 * Math.sin(twoThetaPRad / 2),\n  );\n\n  const stateColor = t.palette[1]; // the given stress-state points A and B\n  const landmarkColor = t.ink; // derived circle geometry (neutral / reference)\n\n  return (\n    <g>\n      {/* Principal-stress axis — a bold neutral segment between sigma2/sigma1\n          makes the headline result the diagram's clear focal point. */}\n      <line x1={pointS2.x} y1={pointS2.y} x2={pointS1.x} y2={pointS1.y} stroke={landmarkColor} strokeWidth={5} strokeLinecap=\"round\" />\n      <line\n        x1={pointA.x}\n        y1={pointA.y}\n        x2={pointB.x}\n        y2={pointB.y}\n        stroke={t.ink}\n        strokeWidth={2}\n        strokeDasharray=\"7 6\"\n      />\n      <path d={arcPath} fill=\"none\" stroke={t.inkSoft} strokeWidth={1.5} />\n\n      <circle cx={c.x} cy={c.y} r={6} fill={landmarkColor} />\n      {[pointS1, pointS2, pointTauMax, pointTauMin].map((p, i) => (\n        <circle key={i} cx={p.x} cy={p.y} r={11} fill={landmarkColor} stroke={t.pageBg} strokeWidth={2.5} />\n      ))}\n      {[pointA, pointB].map((p, i) => (\n        <circle key={i} cx={p.x} cy={p.y} r={11} fill={stateColor} stroke={t.pageBg} strokeWidth={2.5} />\n      ))}\n\n      <ChartsText\n        x={pointA.x + 22}\n        y={pointA.y - 18}\n        text={`A(${sigmaX}, ${tauXY})`}\n        style={{ fontSize: 17, fill: t.ink, dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={pointB.x - 22}\n        y={pointB.y + 20}\n        text={`B(${sigmaY}, ${-tauXY})`}\n        style={{ fontSize: 17, fill: t.ink, textAnchor: \"end\", dominantBaseline: \"central\" }}\n      />\n      {/* Principal-stress / shear results — bolder and larger than the\n          supporting geometry labels so they read as the headline numbers. */}\n      <ChartsText\n        x={pointS1.x + 14}\n        y={pointS1.y + 30}\n        text={`σ₁ = ${sigma1.toFixed(1)} MPa`}\n        style={{ fontSize: 19, fontWeight: 700, fill: t.ink, dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={pointS2.x - 14}\n        y={pointS2.y + 30}\n        text={`σ₂ = ${sigma2.toFixed(1)} MPa`}\n        style={{ fontSize: 19, fontWeight: 700, fill: t.ink, textAnchor: \"end\", dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={pointTauMax.x + 16}\n        y={pointTauMax.y - 12}\n        text={`τ_max = ${tauMax.toFixed(1)} MPa`}\n        style={{ fontSize: 19, fontWeight: 700, fill: t.ink, dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={pointTauMin.x + 16}\n        y={pointTauMin.y + 12}\n        text={`τ_min = ${(-tauMax).toFixed(1)} MPa`}\n        style={{ fontSize: 19, fontWeight: 700, fill: t.ink, dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={c.x + 14}\n        y={c.y - 20}\n        text=\"C\"\n        style={{ fontSize: 16, fill: t.inkSoft, dominantBaseline: \"central\" }}\n      />\n      <ChartsText\n        x={angleLabelPos.x + 10}\n        y={angleLabelPos.y - 8}\n        text={`2θp = ${twoThetaPDeg.toFixed(0)}°`}\n        style={{ fontSize: 16, fill: t.inkSoft, dominantBaseline: \"central\" }}\n      />\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <div\n      style={{\n        width: window.ANYPLOT_SIZE.width,\n        height: window.ANYPLOT_SIZE.height,\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <div\n        style={{\n          height: TITLE_HEIGHT,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          fontSize: 22,\n          fontWeight: 500,\n          color: t.ink,\n        }}\n      >\n        {TITLE}\n      </div>\n      <ChartContainer\n        width={window.ANYPLOT_SIZE.width}\n        height={window.ANYPLOT_SIZE.height - TITLE_HEIGHT}\n        margin={MARGIN}\n        skipAnimation\n        xAxis={[{ scaleType: \"linear\", data: circleX, min: X_MIN, max: X_MAX }]}\n        yAxis={[{ scaleType: \"linear\", min: Y_MIN, max: Y_MAX }]}\n        series={[{ type: \"line\", data: circleY, color: t.palette[0], curve: \"linear\", showMark: false }]}\n      >\n        <ChartsGrid horizontal />\n        <ChartsReferenceLine y={0} lineStyle={{ stroke: t.grid, strokeWidth: 2 }} />\n        <ChartsReferenceLine x={center} lineStyle={{ stroke: t.grid, strokeWidth: 2 }} />\n        <LinePlot />\n        <MohrAnnotations />\n        <ChartsXAxis\n          label=\"Normal Stress σ (MPa)\"\n          labelStyle={{ fontSize: 16, fill: t.ink, fontWeight: 500 }}\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n          stroke={t.inkSoft}\n        />\n        <ChartsYAxis\n          label=\"Shear Stress τ (MPa)\"\n          labelStyle={{ fontSize: 16, fill: t.ink, fontWeight: 500 }}\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n          stroke={t.inkSoft}\n        />\n      </ChartContainer>\n    </div>\n  );\n}\n"}