V-Plotter

HTML

<h3>Click and drag on the canvas to update position of the print head</h3>
<p>
  <label for="d">Distance between mounting points (mm)</label><br />
  <input
    id="d"
    type="range"
    min="100"
    max="10000"
    value="2000"
    oninput="update()"
  />
</p>
<canvas id="canvas" width="1000" height="1000">V-Plotter graphic</canvas>

CSS

body {
  margin: 40px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 14px;
  line-height: 20px;
}

canvas {
  border: 1px solid;
}

JavaScript

// printhead mass (kg)
let m = 1;

const ctx = canvas.getContext("2d");
ctx.font = "14px sans-serif";
ctx.textAlign = "center";

let alpha, beta, Fg, Fa, Fb, scaleForce, a, b, scale;
let dragging = false;

// printhead coordinates (mm)
let x = 450, y = 200;

// offset for drawing everything (px)
const offset = 50;

update();

function update() {
  // plotter width (mm)
  let width = d.value;
  // convert to pixel dimensions
  scale = (canvas.width - 2 * offset) / width;

  // gravity pull on printhead (N)
  Fg = m * 9.81;
  // scale all force vectors so that Fg always is 100 px
  scaleForce = 100 / Fg;

  // length of ropes (mm)
  a = Math.round(Math.sqrt(x ** 2 + y ** 2) / scale);
  b = Math.round(
    Math.sqrt((canvas.width - 2 * offset - x) ** 2 + y ** 2) / scale,
  );

  // how much rope a deviates from vertical (radians)
  alpha = Math.atan(x / y);
  // how much rope b deviates from vertical (radians)
  beta = Math.atan((width * scale - x) / y);
  // the angle between both ropes (radians)
  let theta = alpha + beta;

  // https://technikdoku.com/zentrales-kraeftesystem/#Zerlegen_einer_Kraft_in_zwei_nicht_parallele_Kraefte
  // force pulling on rope b (N)
  Fb = (Fg * Math.sin(alpha)) / Math.sin(Math.PI - theta);
  // force pulling on rope a (N)
  Fa = Fg * Math.cos(alpha) - Fb * Math.cos(theta);

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawPlotter(scale * width, x, y, a, b);
}

// expects dimensions in px
function drawPlotter(w, x, y, a, b) {
  // ropes
  ctx.beginPath();
  ctx.moveTo(offset, offset);
  ctx.lineTo(x + offset, y + offset);
  ctx.lineTo(w + offset, offset);
  ctx.strokeStyle = "black";
  ctx.lineWidth = 2;
  ctx.stroke();

  // mounting points and carriage
  ctx.beginPath();
  ctx.moveTo(offset, offset);
  ctx.arc(offset, offset, 5, 0, 2 * Math.PI);
  ctx.moveTo(w + offset, offset);
  ctx.arc(w + offset, offset, 5, 0, 2 * Math.PI);
  ctx.moveTo(x + offset, y +...