Mathematical Vector Engineering

SVG Path Arc Commands Demystified

A complete mathematical breakdown of the SVG A / a elliptical arc command: trigonometry, large-arc-flag, sweep-flag, and zero-dependency radial UI gauges.

TL;DR — The 7 Parameters of SVG Arc Syntax

The arc command is defined as A rx ry x-axis-rotation large-arc-flag sweep-flag x y. rx and ry define ellipse radii; x-axis-rotation angles the ellipse; large-arc-flag (0 or 1) chooses whether to travel the short (≤180°) or long (>180°) path; sweep-flag (0 or 1) chooses counter-clockwise or clockwise direction; and x, y is the final endpoint.

Why Arc Commands Intimidate Developers

Among all SVG path commands (M, L, H, V, C, S, Q, T, Z), the Elliptical Arc (A / a) is uniquely notorious. While lines and Bézier curves use direct control points, the W3C specification uses endpoint parameterization for arcs.

Instead of defining the center coordinates of an ellipse and sweeping an angle, you define two physical points on a canvas and let the browser solve a system of quadratic equations to determine which ellipse connects them. Given two points and two radii, exactly four distinct arcs can connect them:

/* Syntax Breakdown */
d="M startX startY A rx ry x-axis-rotation large-arc-flag sweep-flag endX endY"

The 4 Boolean Combinations Visualized

The two boolean flags (large-arc-flag and sweep-flag) dictate which of the four possible geometric solutions the browser draws:

1. large-arc-flag=0, sweep-flag=1 (Most Common)

Draws the shorter arc (≤ 180°) sweeping clockwise. This is the default setting for standard pie wedges under 50% and rounded UI corners.

2. large-arc-flag=1, sweep-flag=1

Draws the longer arc (> 180°) sweeping clockwise. Used for radial progress bars and donut meters filled past the 50% threshold.

3. large-arc-flag=0, sweep-flag=0

Draws the shorter arc (≤ 180°) sweeping counter-clockwise.

4. large-arc-flag=1, sweep-flag=0

Draws the longer arc (> 180°) sweeping counter-clockwise.

Polar-to-Cartesian Trigonometry in JavaScript

To dynamically render interactive radial progress meters, donut charts, or speedometers without pulling in 150 KB charting libraries, convert angles to Cartesian coordinates using standard trigonometry:

// Converts polar coordinates (radius, angle in degrees) to SVG Cartesian coordinates (x, y)
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  // Subtract 90 degrees to orient 0 degrees at the 12 o'clock position
  const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;

  return {
    x: centerX + (radius * Math.cos(angleInRadians)),
    y: centerY + (radius * Math.sin(angleInRadians))
  };
}

// Generates dynamic SVG path data for any arc from startAngle to endAngle
function describeArc(x, y, radius, startAngle, endAngle) {
  const start = polarToCartesian(x, y, radius, endAngle);
  const end = polarToCartesian(x, y, radius, startAngle);

  // If the angular span exceeds 180 degrees, set large-arc-flag to 1
  const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

  return [
    "M", start.x, start.y, 
    "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
  ].join(" ");
}

Building a Zero-Dependency Circular Progress Gauge

Here is a complete, production-ready React component rendering an interactive radial gauge using our pure arc math:

import React from 'react';

interface GaugeProps {
  percentage: number; // 0 to 100
  size?: number;
  strokeWidth?: number;
}

export const RadialGauge: React.FC<GaugeProps> = ({ 
  percentage, 
  size = 120, 
  strokeWidth = 10 
}) => {
  const center = size / 2;
  const radius = center - strokeWidth;
  const sweepAngle = (percentage / 100) * 359.99; // Cap at 359.99 to prevent point collision

  const arcPath = describeArc(center, center, radius, 0, sweepAngle);

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      {/* Background Track Circle */}
      <circle 
        cx={center} 
        cy={center} 
        r={radius} 
        fill="none" 
        stroke="#1F1F1F" 
        strokeWidth={strokeWidth} 
      />
      {/* Dynamic Foreground Arc */}
      <path 
        d={arcPath} 
        fill="none" 
        stroke="#C1DD2D" 
        strokeWidth={strokeWidth} 
        strokeLinecap="round" 
      />
    </svg>
  );
};
The 360-Degree Point Collision Trap

If you pass startAngle = 0 and endAngle = 360, the starting point and ending point coincide at the exact same pixel coordinates. The browser treats this as a zero-length path and renders nothing! Always cap your maximum angle at 359.99 degrees, or split a 360-degree circle into two separate 180-degree semicircular arcs.

Frequently Asked Questions

When should I use stroke-dashoffset vs SVG arc paths for progress meters?

For simple concentric circles, CSS stroke-dashoffset on a <circle> element is simpler. However, for custom non-circular ellipses, speedometers with gap angles, or complex multi-segment pie charts, procedural SVG arc path calculation is required.

What is the difference between uppercase 'A' and lowercase 'a'?

Uppercase A specifies the final destination coordinates (x, y) in absolute canvas coordinates. Lowercase a specifies the endpoint relative to the previous subpath's current pen position (dx, dy).

Ready to Build Custom Vector Gauges?

Search 134,000+ open-source SVG icons across 28 curated libraries on IconStash. Fast, clean, and 100% open source.

Search All 134K+ Icons →