Engineering Journal
Schema Editor
Schema Editor

Building a Live Measure Tool on an SVG Canvas

2026-06-04

TLDR

The GINEXYS Schema Editor implements a live tape measure tool using an ephemeral SVG <g> overlay group (#measure-overlay) positioned above all canvas elements. The overlay clears and redraws on mousemove frames during active measurements. Point placement uses geometry-aware snapping, clicking wires invokes native SVGGeometryElement.getTotalLength() for exact path distance, and 3-point angle measurements use dot and cross products to classify angles as acute, right, obtuse, or straight.
Feature CapabilityUnderlying API / MathImplementation Pattern
Ephemeral OverlaySVG <g> element + innerHTML = ''Full redraw per mousemove frame
Snap-Aware PointssmartSnap(worldPt)Anchors to exact component pin coordinates
Wire Auto-MeasurewireEl.getTotalLength()Native SVG path length calculation
Angle Classificationatan2(cross

Problem statement: high-precision measurements in vector editors

Engineers using schematic editors need to verify distances between pins, validate wire run lengths, and inspect geometric angles between components.

Building an interactive measurement tool requires:

  1. Drawing measurement lines and text labels in real-time as the cursor moves.
  2. Snapping measurement endpoints to exact component pins rather than approximate mouse pixels.
  3. Measuring multi-segment bent wire paths automatically without manual vertex clicking.

Technical failure mode: granular element property mutating vs overlay redraws

Updating individual DOM attributes across dozens of child elements (lines, text labels, ticks, backdrop rects) during a fast mousemove drag requires complex DOM diffing logic.

For ephemeral overlays with under 10 elements, clearing the group (overlay.innerHTML = '') and appending fresh SVG elements per frame is significantly faster and less bug-prone than mutating individual attributes.


The fix & architecture: ephemeral overlay & native path inspection

1. Ephemeral overlay & snap placement

Create a dedicated <g id="measure-overlay"> group at the root of the SVG canvas and clear/redraw it on mousemove:
function _updateMeasureOverlay(fromPt, toPt) {
  overlay.innerHTML = ''; // Fast reset for small DOM node counts (<10 nodes)
  
  const dx = toPt.x - fromPt.x;
  const dy = toPt.y - fromPt.y;
  const dist = Math.hypot(dx, dy);

// Offset distance label perpendicular to measurement line const mid = { x: (fromPt.x + toPt.x) / 2, y: (fromPt.y + toPt.y) / 2 }; const norm = { x: -dy / dist, y: dx / dist }; const labelPt = { x: mid.x + norm.x 14, y: mid.y + norm.y 14 };

_appendLine(overlay, fromPt.x, fromPt.y, toPt.x, toPt.y, 'measure-line'); _appendText(overlay, ${dist.toFixed(1)} u, labelPt, 'measure-label'); }

2. Auto-Measuring wire paths

Clicking a wire invokes native getTotalLength() and getPointAtLength() on the SVGPathElement:
function _handleWireClick(wireEl) {
  const length = wireEl.getTotalLength(); // Native browser SVG path calculation
  const labelPt = wireEl.getPointAtLength(length / 2); // Midpoint along curve
  _renderLengthLabel(labelPt, length, 'path');
}

3. Vector angle classification

When three points are selected (vertex between two vectors), calculate and classify the angle using dot and cross products:
function _classifyAngle(a, vertex, b) {
  const va = { x: a.x - vertex.x, y: a.y - vertex.y };
  const vb = { x: b.x - vertex.x, y: b.y - vertex.y };
  const dot = va.x  vb.x + va.y  vb.y;
  const cross = va.x  vb.y - va.y  vb.x;
  const angleDeg = Math.atan2(Math.abs(cross), dot) * (180 / Math.PI);

if (Math.abs(angleDeg - 90) < 1) return 'right'; if (angleDeg < 90) return 'acute'; if (angleDeg < 180) return 'obtuse'; return 'straight'; }

Rule of thumb: Use native browser APIs like SVGGeometryElement.getTotalLength() for path length measurements, and clear ephemeral overlay groups with innerHTML = '' on 60fps interaction frames when node counts are small (<10 elements).
Read this post in the full Engineering Journal →