Resources Numerical accuracy Calculation policy

Multileader geometry

Works out what a multileader draws: its line with the landing, the arrowhead and where the text starts.

How the precision is kept

The landing and text position are the last leader point plus the stored sizes, and the arrowhead is built from the unit direction of the first segment. All in 64-bit numbers with nothing rounded.

Calculations under this policy

2 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

leader.js CadCraft 0.8.2 64-bit 2 calculations Reviewed

Tests bin/test-cadcraft.mjs

Landing and text start

64-bit

Where the landing ends and where the text starts.

end = last + dir·(landing length, 0); text = end + dir·(gap, 0)

Two additions of stored sizes to the last leader point; dir is +1 or −1.

  • leader.js
  • lines 20 to 25
  • CadCraft 0.8.2
/** Where the landing ends and the text starts. */
export function landing(e) {
  const st = leaderStyle(e), last = e.pts[e.pts.length - 1], dir = e.dir === -1 ? -1 : 1;
  const end = { x: last.x + dir * st.dogleg, y: last.y };
  return { start: last, end, text: { x: end.x + dir * st.gap, y: end.y } };
}

Leader line, arrowhead and text

64-bit

The polyline, the solid arrowhead and the text a multileader is drawn and exploded as.

u = (tip − next) ÷ |tip − next|; base = tip − u·a; corners = base ± (−u_y, u_x)·a ÷ 6; text top = landing y + h ÷ 2

The arrow direction is the first segment divided by its Math.hypot length. The leader's own points are copied unchanged into the polyline.

  • leader.js
  • lines 27 to 46
  • CadCraft 0.8.2
export function leaderGeometry(e) {
  if (!e.pts || e.pts.length < 2) return [];
  const st = leaderStyle(e), dir = e.dir === -1 ? -1 : 1;
  const L = landing(e);
  const base = { layer: '0', color: 0 };
  const out = [{ ...base, type: 'poly', closed: false, pts: [...e.pts, ...(st.dogleg > 0 ? [L.end] : [])].map((p) => ({ x: p.x, y: p.y, b: 0 })) }];
  // arrowhead: a solid triangle at the tip, pointing back along the first segment
  const tip = e.pts[0], nxt = e.pts[1], dx = tip.x - nxt.x, dy = tip.y - nxt.y, len = Math.hypot(dx, dy);
  if (len > 1e-12 && st.asz > 0) {
    const ux = dx / len, uy = dy / len, a = st.asz, w = a / 6;   // AutoCAD's closed filled arrow: length a, half width a/6
    const bx = tip.x - ux * a, by = tip.y - uy * a;
    out.push({ ...base, type: 'hatch', solid: true, loops: [[tip.x, tip.y, bx - uy * w, by + ux * w, bx + uy * w, by - ux * w]] });
  }
  if (String(e.s ?? '').trim()) {
    const h = e.h > 0 ? e.h : st.asz;
    // the first line's middle on the landing: hang the text from its top, half a capital height above
    out.push({ ...base, type: 'text', x: L.text.x, y: L.text.y + h / 2, h, rot: 0, s: String(e.s), ha: dir === 1 ? 'left' : 'right', va: 'top', wf: 1, ...(e.ww > 0 ? { ww: e.ww } : {}), ...(e.ls && e.ls !== 1 ? { ls: e.ls } : {}) });
  }
  return out;
}

In detail

Method
Landing end = last point ± landing length along x; arrowhead a triangle of length a and half width a ÷ 6.
Number types
64-bit numbers.
Tolerances
A first segment of 1e-12 or shorter gets no arrowhead.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach