Mohr's Circle is a graphical method used to determine principal stresses, maximum shear stress, and stress transformations from a given 2D stress state. A circle is drawn on a normal stress (σ) vs. shear stress (τ) plane, with the center at ((σx + σy) / 2, 0) and a radius derived from the stress components. It is an essential tool in mechanical and civil engineering for visualizing how stress components change under coordinate rotation.

// anyplot.ai
// mohr-circle: Mohr's Circle for Stress Analysis
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-08-26
//# anyplot-orientation: square
const t = window.ANYPLOT_TOKENS;
// --- Data: 2D stress state on a steel shaft under combined bending + torsion (MPa) ---
const sigmaX = 120;
const sigmaY = 40;
const tauXY = 55;
const center = (sigmaX + sigmaY) / 2;
const radius = Math.sqrt(((sigmaX - sigmaY) / 2) ** 2 + tauXY ** 2);
const sigma1 = center + radius;
const sigma2 = center - radius;
const tauMax = radius;
const twoThetaPRad = Math.atan2(tauXY, (sigmaX - sigmaY) / 2);
const twoThetaPDeg = (twoThetaPRad * 180) / Math.PI;
// Circle outline, traced parametrically around the center
const STEPS = 200;
const circlePoints = [];
for (let i = 0; i <= STEPS; i++) {
const angle = (2 * Math.PI * i) / STEPS;
circlePoints.push([center + radius * Math.cos(angle), radius * Math.sin(angle)]);
}
const pointA = [sigmaX, tauXY];
const pointB = [sigmaY, -tauXY];
// --- Chart -------------------------------------------------------------------
Highcharts.chart("container", {
chart: {
type: "line",
backgroundColor: "transparent",
animation: false,
style: { fontFamily: "inherit" },
events: {
load: function () {
// Force equal pixel-per-unit scale on both axes so the circle renders
// as a true circle instead of stretching to the plot area's aspect ratio.
const chart = this;
const xAxis = chart.xAxis[0];
const yAxis = chart.yAxis[0];
const scale = Math.min(chart.plotWidth / (xAxis.max - xAxis.min), chart.plotHeight / (yAxis.max - yAxis.min));
const xMid = (xAxis.max + xAxis.min) / 2;
const yMid = (yAxis.max + yAxis.min) / 2;
xAxis.setExtremes(xMid - chart.plotWidth / (2 * scale), xMid + chart.plotWidth / (2 * scale), false);
yAxis.setExtremes(yMid - chart.plotHeight / (2 * scale), yMid + chart.plotHeight / (2 * scale), false);
chart.redraw();
// Angle arc for the principal-plane rotation 2θp, swept from the
// positive-σ direction (toward σ1) to the radius line center → A.
const cx = xAxis.toPixels(center);
const cy = yAxis.toPixels(0);
const ax = xAxis.toPixels(pointA[0]);
const ay = yAxis.toPixels(pointA[1]);
const arcR = Math.min(chart.plotWidth, chart.plotHeight) * 0.14;
// Highcharts renderer.arc angles: 0 rad = 3 o'clock (right), increasing clockwise.
const angleToA = Math.atan2(ay - cy, ax - cx);
const startAngle = Math.min(0, angleToA);
const endAngle = Math.max(0, angleToA);
chart.renderer
.arc(cx, cy, arcR, 0, startAngle, endAngle)
.attr({ stroke: t.inkSoft, "stroke-width": 1.5, fill: "none", dashstyle: "Dash" })
.add();
const midAngle = (startAngle + endAngle) / 2;
const labelR = arcR + 34;
// Use renderer.label (not .text) so the halo matching the page background
// sits behind the glyphs, keeping the dashed arc from cutting through them.
const angleLabel = chart.renderer
.label(`2θp = ${twoThetaPDeg.toFixed(1)}°`, 0, 0)
.attr({ fill: t.pageBg, padding: 3, zIndex: 6 })
.css({ color: t.inkSoft, fontSize: "14px" })
.add();
const labelBox = angleLabel.getBBox();
angleLabel.attr({
x: cx + labelR * Math.cos(midAngle) - labelBox.width / 2,
y: cy + labelR * Math.sin(midAngle) - labelBox.height / 2,
});
},
},
},
credits: { enabled: false },
colors: t.palette,
title: {
text: "mohr-circle · javascript · highcharts · anyplot.ai",
style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
},
subtitle: {
text: "Steel shaft under combined bending and torsion",
style: { color: t.inkSoft, fontSize: "14px" },
},
xAxis: {
title: { text: "Normal Stress σ (MPa)", style: { color: t.inkSoft, fontSize: "16px" } },
min: center - radius * 1.3,
max: center + radius * 1.3,
lineColor: t.inkSoft,
tickColor: t.inkSoft,
gridLineColor: t.grid,
gridLineWidth: 1,
labels: { style: { color: t.inkSoft, fontSize: "14px" } },
plotLines: [
{ value: 0, color: t.inkSoft, width: 1, zIndex: 2 },
{ value: center, color: t.inkSoft, width: 1, dashStyle: "ShortDash", zIndex: 2 },
],
},
yAxis: {
title: { text: "Shear Stress τ (MPa)", style: { color: t.inkSoft, fontSize: "16px" } },
min: -radius * 1.3,
max: radius * 1.3,
lineColor: t.inkSoft,
tickColor: t.inkSoft,
gridLineColor: t.grid,
gridLineWidth: 1,
labels: { style: { color: t.inkSoft, fontSize: "14px" } },
plotLines: [{ value: 0, color: t.inkSoft, width: 1, zIndex: 2 }],
},
legend: { enabled: false },
tooltip: {
pointFormat: "σ = {point.x:.1f} MPa<br/>τ = {point.y:.1f} MPa",
},
plotOptions: {
series: { animation: false },
},
series: [
{
name: "Mohr's Circle",
type: "line",
data: circlePoints,
color: t.palette[0],
lineWidth: 3,
marker: { enabled: false },
enableMouseTracking: false,
},
{
name: "Diameter (A-B)",
type: "line",
data: [pointA, pointB],
color: t.inkSoft,
dashStyle: "ShortDash",
lineWidth: 1.5,
marker: { enabled: false },
enableMouseTracking: false,
},
{
name: "Stress Points",
type: "scatter",
color: t.palette[1],
marker: { symbol: "circle", radius: 6, lineWidth: 1, lineColor: t.pageBg },
dataLabels: {
enabled: true,
style: { color: t.ink, fontSize: "14px", textOutline: "none" },
formatter: function () {
return this.point.label;
},
},
data: [
{ x: pointA[0], y: pointA[1], label: `A(${sigmaX}, ${tauXY})`, dataLabels: { align: "left", x: 10, y: -8 } },
{ x: pointB[0], y: pointB[1], label: `B(${sigmaY}, -${tauXY})`, dataLabels: { align: "right", x: -10, y: 18 } },
],
},
{
name: "Principal Stresses",
type: "scatter",
color: t.palette[2],
marker: { symbol: "diamond", radius: 7, lineWidth: 1, lineColor: t.pageBg },
dataLabels: {
enabled: true,
style: { color: t.ink, fontSize: "14px", textOutline: "none" },
backgroundColor: t.pageBg,
borderWidth: 0,
borderRadius: 3,
padding: 3,
formatter: function () {
return this.point.label;
},
},
// σ1/σ2 sit at the circle's extreme left/right points, where the tangent
// is near-vertical — push the labels outward (away from the circle body)
// instead of straight up, so the stroke doesn't cut through the text.
data: [
{ x: sigma1, y: 0, label: `σ1 = ${sigma1.toFixed(1)}`, dataLabels: { align: "left", x: 12, y: -10 } },
{ x: sigma2, y: 0, label: `σ2 = ${sigma2.toFixed(1)}`, dataLabels: { align: "right", x: -12, y: -10 } },
],
},
{
name: "Max Shear Stress",
type: "scatter",
color: t.palette[3],
marker: { symbol: "triangle", radius: 7, lineWidth: 1, lineColor: t.pageBg },
dataLabels: {
enabled: true,
style: { color: t.ink, fontSize: "14px", textOutline: "none" },
backgroundColor: t.pageBg,
borderWidth: 0,
borderRadius: 3,
padding: 3,
formatter: function () {
return this.point.label;
},
},
// τmax/-τmax sit on the dashed σ=center reference line — offset the
// labels sideways (not centered on x=center) so the dashed line doesn't
// run straight through the text.
data: [
{ x: center, y: tauMax, label: `τmax = ${tauMax.toFixed(1)}`, dataLabels: { align: "left", x: 10, y: -10 } },
{ x: center, y: -tauMax, label: `-τmax`, dataLabels: { align: "left", x: 10, y: 22 } },
],
},
{
name: "Center",
type: "scatter",
color: t.ink,
marker: { symbol: "circle", radius: 4 },
enableMouseTracking: false,
showInLegend: false,
dataLabels: { enabled: false },
data: [{ x: center, y: 0 }],
},
],
});
Part of Mohr's Circle for Stress Analysis on anyplot.ai.