This is an interactive browser-based tool for 3D stress analysis. Given a full stress tensor, it calculates the principal stresses and plots the three Mohr’s circles along with Von Mises and Tresca stress values.

The source code is self-contained within a single HTML file.


Live Tool


Libraries Used


Theory

The stress tensor

The state of stress at a point in a 3D body is fully described by the symmetric Cauchy stress tensor $\boldsymbol{\sigma}$:

\[\boldsymbol{\sigma} = \begin{pmatrix} \sigma_{11} & \sigma_{12} & \sigma_{13} \\ \sigma_{12} & \sigma_{22} & \sigma_{23} \\ \sigma_{13} & \sigma_{23} & \sigma_{33} \end{pmatrix} = \begin{pmatrix} \sigma_x & \tau_{xy} & \tau_{xz} \\ \tau_{xy} & \sigma_y & \tau_{yz} \\ \tau_{xz} & \tau_{yz} & \sigma_z \end{pmatrix}\]

The diagonal entries are normal stresses; the off-diagonal entries are shear stresses. Symmetry ($\sigma_{ij} = \sigma_{ji}$) follows from moment equilibrium, leaving 6 independent components.

Principal stresses

Principal stresses $\sigma_p$ are the eigenvalues of $\boldsymbol{\sigma}$ — the normal stresses acting on planes where shear stress vanishes. They satisfy the characteristic equation:

\[\det(\boldsymbol{\sigma} - \sigma_p \mathbf{I}) = 0\]

Expanding the determinant gives the cubic:

\[\sigma_p^3 - I_1\,\sigma_p^2 + I_2\,\sigma_p - I_3 = 0\]

where $I_1$, $I_2$, $I_3$ are the stress invariants — scalar quantities that do not change with coordinate rotation (reference):

\[\begin{aligned} I_{1} &= \sigma_{11}+\sigma_{22}+\sigma_{33} \\ I_{2} &= \sigma_{11}\sigma_{22}+\sigma_{22}\sigma_{33}+\sigma_{33}\sigma_{11}-\sigma_{12}^{2}-\sigma_{23}^{2}-\sigma_{31}^{2} \\ I_{3} &= \det(\boldsymbol{\sigma}) = \sigma_{11}\sigma_{22}\sigma_{33}-\sigma_{11}\sigma_{23}^{2}-\sigma_{22}\sigma_{31}^{2}-\sigma_{33}\sigma_{12}^{2}+2\sigma_{12}\sigma_{23}\sigma_{31} \end{aligned}\]

The cubic has three real roots (guaranteed for a symmetric real matrix). Using the trigonometric method, they are found via the auxiliary angle:

\[\phi = \frac{1}{3}\cos^{-1}\!\left(\frac{2I_{1}^{3}-9I_{1}I_{2}+27I_{3}}{2\left(I_{1}^{2}-3I_{2}\right)^{3/2}}\right)\]

Principal stresses (ordered $\sigma_1 \geq \sigma_2 \geq \sigma_3$):

\[\begin{aligned} \sigma_{1} &= \frac{I_{1}}{3}+\frac{2}{3}\sqrt{I_{1}^{2}-3I_{2}}\,\cos\phi \\ \sigma_{2} &= \frac{I_{1}}{3}+\frac{2}{3}\sqrt{I_{1}^{2}-3I_{2}}\,\cos\!\left(\phi-\frac{2\pi}{3}\right) \\ \sigma_{3} &= \frac{I_{1}}{3}+\frac{2}{3}\sqrt{I_{1}^{2}-3I_{2}}\,\cos\!\left(\phi-\frac{4\pi}{3}\right) \end{aligned}\]

Mohr’s circles

Each pair of principal stresses defines one circle in the $(\sigma, \tau)$ plane. The three circles have centers and radii:

\[C_{ij} = \frac{\sigma_i + \sigma_j}{2}, \qquad R_{ij} = \frac{\sigma_i - \sigma_j}{2}\]

The outermost circle (between $\sigma_1$ and $\sigma_3$) gives the maximum shear stress $\tau_{\max} = R_{13}$ (Tresca criterion).

Failure criteria

\[\tau_{\max} = \frac{\sigma_1 - \sigma_3}{2} \qquad \text{(Tresca)}\] \[\sigma_{VM} = \sqrt{\frac{(\sigma_1-\sigma_2)^2+(\sigma_2-\sigma_3)^2+(\sigma_1-\sigma_3)^2}{2}} \qquad \text{(Von Mises)}\]

Source Code

// Helper: generate circle coordinates for plotting
function get_circle_coordinates(x, y, r) {
  let tmp = [];
  for (let theta = 0; theta < 360; theta++) {
    tmp[theta] = [
      x + r * Math.cos(theta * (Math.PI / 180)),
      y + r * Math.sin(theta * (Math.PI / 180))
    ];
  }
  return tmp;
}

// Input/output objects
const input = {
  s_x: -100, s_y: -200, s_z: -150,
  t_xy: 50,  t_xz: 100, t_yz: 150
};

const output = {
  s_1: 0, s_2: 0, s_3: 0,
  R_1: 0, R_2: 0, R_3: 0,
  C_1: 0, C_2: 0, C_3: 0,
  s_vm: 0,
};

// Core calculation
function calc_mohr_3d(input) {
  const pi = Math.PI;

  const s_x  = input.s_x,  s_y  = input.s_y,  s_z  = input.s_z;
  const t_xy = input.t_xy, t_xz = input.t_xz, t_yz = input.t_yz;

  // Stress Invariants
  const I_1 = s_x + s_y + s_z;
  const I_2 = s_x*s_y + s_y*s_z + s_z*s_x - t_xy**2 - t_xz**2 - t_yz**2;
  const I_3 = (s_x*s_y*s_z) - s_x*t_yz**2 - s_y*t_xz**2 - s_z*t_xy**2 + (2*t_xy*t_xz*t_yz);

  const phi = (1/3) * Math.acos(
    (2*I_1**3 - 9*I_1*I_2 + 27*I_3) / (2*(I_1**2 - 3*I_2)**(3/2))
  );

  // Principal Stresses
  const s_1 = (I_1/3) + (2/3)*Math.sqrt(I_1**2 - 3*I_2)*Math.cos(phi);
  const s_2 = (I_1/3) + (2/3)*Math.sqrt(I_1**2 - 3*I_2)*Math.cos(phi - 2*pi/3);
  const s_3 = (I_1/3) + (2/3)*Math.sqrt(I_1**2 - 3*I_2)*Math.cos(phi - 4*pi/3);

  // Von Mises stress
  const s_vm = Math.sqrt(0.5*((s_1-s_3)**2 + (s_2-s_3)**2 + (s_1-s_2)**2));

  // Mohr's circle parameters
  const C_1 = 0.5*(s_1 + s_2),  R_1 = 0.5*(s_1 - s_2);
  const C_2 = 0.5*(s_1 + s_3),  R_2 = 0.5*(s_1 - s_3);
  const C_3 = 0.5*(s_2 + s_3),  R_3 = 0.5*(s_2 - s_3);

  Object.assign(output, { s_1, s_2, s_3, R_1, R_2, R_3, C_1, C_2, C_3, s_vm });

  // Round to 2 decimal places
  for (const key in output) {
    if (typeof output[key] === 'number') {
      output[key] = +output[key].toFixed(2);
    }
  }

  return output;
}

// ECharts drawing
function draw() {
  const c1_coor = get_circle_coordinates(output.C_1, 0, output.R_1);
  const c2_coor = get_circle_coordinates(output.C_2, 0, output.R_2);
  const c3_coor = get_circle_coordinates(output.C_3, 0, output.R_3);

  const myChart = echarts.init(document.getElementById('myChart'));
  window.onresize = () => myChart.resize();

  myChart.setOption({
    maintainAspectRatio: 1,
    legend: {},
    animation: false,
    xAxis: { type: 'value' },
    yAxis: { type: 'value' },
    series: [
      { name: 'Max Stress',            symbolSize: 5, data: c2_coor, type: 'scatter' },
      { name: 'Max Stress (plane 2,3)', symbolSize: 5, data: c1_coor, type: 'scatter' },
      { name: 'Max Stress (plane 1,2)', symbolSize: 5, data: c3_coor, type: 'scatter' },
      {
        name: 'Principal stress', symbolSize: 10, type: 'scatter',
        data: [[output.s_1, 0, 'σ1'], [output.s_2, 0, 'σ2'], [output.s_3, 0, 'σ3']],
        label: { show: true, position: 'top', formatter: '{@[2]} = {@[0]}' },
        labelLine: { show: 1, type: 'dashed' }, labelLayout: { dx: -30 },
      },
      {
        name: 'Max shear stress', symbolSize: 10, type: 'scatter',
        data: [[output.C_1, output.R_1, 'τ1'], [output.C_2, output.R_2, 'τ2'], [output.C_3, output.R_3, 'τ3']],
        label: { show: true, position: 'top', formatter: '{@[2]} = {@[1]}' },
        labelLine: { show: 1, type: 'dashed' }, labelLayout: { dx: -30 },
      },
    ]
  });
}

// Tweakpane panels for input/output
const panel_input  = new Tweakpane.Pane({ title: 'Input' });
const panel_output = new Tweakpane.Pane({ title: 'Output' });

const folder_normal = panel_input.addFolder({ title: 'Normal Stress [MPa]' });
folder_normal.addInput(input, 's_x', { label: 'σ11:' });
folder_normal.addInput(input, 's_y', { label: 'σ22:' });
folder_normal.addInput(input, 's_z', { label: 'σ33:' });

const folder_shear = panel_input.addFolder({ title: 'Shear Stress [MPa]' });
folder_shear.addInput(input, 't_xy', { label: 'τ12:' });
folder_shear.addInput(input, 't_yz', { label: 'τ23:' });
folder_shear.addInput(input, 't_xz', { label: 'τ13:' });

const folder_ps = panel_output.addFolder({ title: 'Principal Stress [MPa]' });
folder_ps.addMonitor(output, 's_1', { label: 'σ1:' });
folder_ps.addMonitor(output, 's_2', { label: 'σ2:' });
folder_ps.addMonitor(output, 's_3', { label: 'σ3:' });

const folder_shear_out = panel_output.addFolder({ title: 'Max Shear Stress [MPa]' });
folder_shear_out.addMonitor(output, 'R_1', { label: 'τ1:' });
folder_shear_out.addMonitor(output, 'R_2', { label: 'τ2:' });
folder_shear_out.addMonitor(output, 'R_3', { label: 'τ3:' });

const folder_vm = panel_output.addFolder({ title: 'Von Mises Stress [MPa]' });
folder_vm.addMonitor(output, 's_vm', { label: 'σvm:' });

calc_mohr_3d(input);
draw();

panel_input.on('change', () => {
  calc_mohr_3d(input);
  panel_output.refresh();
  draw();
});