Chapter 35: Canvas Element & 2D Graphics Basics

Circles, Arcs & Donut Gauges

Radian trigonometry, `ctx.arc()` & `ctx.arcTo()`, clockwise vs counter-clockwise sweeps, building data-driven pie charts, and animated circular progress meters.

LEARNING OBJECTIVES
  • Convert fluently between human degrees ($0^\circ\text{–}360^\circ$) and graphics radians ($0\text{–}2\pi$).
  • Master all parameters of ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise).
  • Construct rounded fillets and corners using the tangent-based ctx.arcTo() method.
  • Build high-performance circular progress meters and multi-segment donut storage visualizers.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

The Angle Wheel & The $3\text{ O'Clock}$ Origin

While human beings measure rotations in degrees ($360^\circ$ for a full circle), computer graphics engines speak exclusively in Radians.

A radian is the angle created when you take the radius of a circle and wrap it directly along the circle's curved perimeter:

  • A full circle ($360^\circ$) has a circumference of $2\pi r$, which equals $2\pi$ Radians ($\approx 6.28318\text{ rad}$).
  • A half circle ($180^\circ$) equals $\pi$ Radians ($\approx 3.14159\text{ rad}$).
  • A quarter turn ($90^\circ$) equals $\pi / 2$ Radians ($\approx 1.57079\text{ rad}$).
+-----------------------------------------------------------------------------+
|                           THE CANVAS RADIAN COMPASS                         |
+-----------------------------------------------------------------------------+
                                12 o'clock
                                1.5 * PI (270°)
                                    |
                                    |
                                    |
            9 o'clock               |               3 o'clock (ORIGIN)
       1.0 * PI (180°) -------------+------------- 0.0 / 2.0 * PI (0° / 360°)
                                    |
                                    |
                                    |
                                    v
                                6 o'clock
                                0.5 * PI (90°)

Crucial Rule: In Canvas, $0$ Radians is always at 3 o'clock (the positive $X$-axis). Angles increase clockwise toward 6 o'clock ($0.5\pi$), 9 o'clock ($1.0\pi$), and 12 o'clock ($1.5\pi$).


Technical Deep Dive & Specifications

The ctx.arc() Method Specification

ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);

Parameter Reference Matrix

Parameter Type Required Description & Constraints
x number Yes The $X$-coordinate of the circle's center point.
y number Yes The $Y$-coordinate of the circle's center point.
radius number Yes Distance from the center to the arc's outer edge (must be non-negative).
startAngle number Yes The angle in radians at which the arc begins, measured from the positive $X$-axis.
endAngle number Yes The angle in radians at which the arc terminates.
counterclockwise boolean Optional false (default) draws clockwise; true draws counter-clockwise.

The Conversion Formula

To convert human degrees to Canvas radians:

const radians = (degrees * Math.PI) / 180;
const degToRad = deg => (deg * Math.PI) / 180;

Starting Arcs at the Top ($12\text{ O'Clock}$)

Because $0$ radians is at 3 o'clock, progress meters and clocks that start at the top ($12\text{ o'clock}$) must begin at $-0.5\pi$ (or $1.5\pi$):

const startAngle = -Math.PI / 2; // 12 o'clock
const progress = 0.75;           // 75% complete
const endAngle = startAngle + (progress * 2 * Math.PI);

ctx.beginPath();
ctx.arc(100, 100, 50, startAngle, endAngle, false);
ctx.stroke();

Tangent Fillets with ctx.arcTo()

The ctx.arcTo(x1, y1, x2, y2, radius) method creates an arc that is tangent to two intersecting lines: the line from the current position to $(x_1, y_1)$, and the line from $(x_1, y_1)$ to $(x_2, y_2)$. It is ideal for constructing custom rounded rectangles:

Current Point (P0) ---------> Corner Target (P1: x1, y1)
                                   /
                              (Arc / Fillet of Radius R)
                                 /
                                v
                     Destination Target (P2: x2, y2)
function drawRoundedRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r); // Top-right corner
  ctx.arcTo(x + w, y + h, x, y + h, r); // Bottom-right corner
  ctx.arcTo(x, y + h, x, y, r);         // Bottom-left corner
  ctx.arcTo(x, y, x + w, y, r);         // Top-left corner
  ctx.closePath();
  ctx.stroke();
}

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Lines 49–54: Draws the full $360^\circ$ ($0$ to $2\pi$) inactive background track for the circular meter using a dark slate stroke (#1e293b).
  • Lines 57–65: Computes the dynamic sweep angle starting at $-0.5\pi$ ($12\text{ o'clock}$). Applying ctx.lineCap = 'round' creates modern, pill-shaped rounded tips on the animated neon progress bar.
  • Lines 82–97: Multi-segment donut chart loop. Instead of calculating complex dual-concentric path polygons, setting ctx.lineWidth = 26 on successive stroked arcs creates instant, perfectly aligned donut slices.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
+-------------------------------------------------------------+
|          ( CPU LOAD )                     ( MEMORY )        |
|        /     78%      \                 /   [Apps]   \      |
|       |  [===Arc===]   |               | [Sys]    [Free]    |
|        \              /                 \            /      |
|         Progress Meter                    Donut Chart       |
+-------------------------------------------------------------+

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Cloud Storage Multi-Segment Donut Meter with Slice Pop-Out

Instructions:

  1. Given four storage allocation categories:
    • Videos: $40%$ (#8b5cf6)
    • Images: $30%$ (#06b6d4)
    • Documents: $15%$ (#f59e0b)
    • Available: $15%$ (#334155)
  2. Render a 2-inch wide donut meter ($r=70\text{px}$, $\text{thickness}=24\text{px}$).
  3. Implement slice extrusion for the "Videos" category:
    • When drawing the largest category (Videos), translate its center point outwards by $8\text{px}$ along its mid-angle vector:
      $dx = \cos(\text{midAngle}) \times 8$, $dy = \sin(\text{midAngle}) \times 8$.
  4. Render category legend labels and center free capacity.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Passing Degree Values Directly to arc(): Calling ctx.arc(x, y, r, 0, 360) treats $360$ as radians ($360\text{ radians} \approx 57\text{ full rotations}$), resulting in broken rendering. Always convert degrees: (deg * Math.PI) / 180.
  2. Assuming $0\text{ Radians}$ is $12\text{ O'Clock}$: $0$ radians is at $3\text{ o'clock}$ (positive $X$-axis). Always subtract $\pi / 2$ (-Math.PI / 2) if you want your gauge to start at the top.
  3. Unwanted Connecting Segments: If a path was previously active and you call ctx.arc(), Canvas will automatically draw a straight line from the last pen position to the start of the arc. Always call ctx.beginPath() before ctx.arc().

💡 Pro Tips

  1. Pill-Shaped Progress Bars with lineCap = 'round': When drawing gauge arcs with thick line widths, setting ctx.lineCap = 'round' turns the sharp boxy ends of the arc into rounded caps.
  2. Full Circle Shortcut: To draw a full circle, use ctx.arc(x, y, radius, 0, Math.PI * 2).
  3. ctx.arcTo() for Smooth UI Corners: When drawing custom rounded rectangles, tabs, or dialogue speech bubbles, ctx.arcTo() produces mathematically smoother corners than manual Bézier approximations.

📌 Key Takeaways

  • Canvas angles are measured in Radians: $360^\circ = 2\pi\text{ rad}$, $180^\circ = \pi\text{ rad}$, $90^\circ = \pi/2\text{ rad}$.
  • $0$ Radians points to $3\text{ o'clock}$ ($+X$ direction); clockwise rotation moves toward $6\text{ o'clock}$.
  • ctx.arc(x, y, r, startAngle, endAngle, counterclockwise) defines circular trajectories.
  • Stroking an arc with a thick lineWidth is the most efficient way to render donut charts and circular meters.
  • Always invoke ctx.beginPath() before ctx.arc() to prevent stray connecting lines from previous sub-paths.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What is the radian equivalent of a $90^\circ$ angle in JavaScript Canvas math?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

If a developer wants a circular progress bar to begin at the very top ($12\text{ o'clock}$), what should startAngle be set to?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

How do you draw a complete, fully closed circle using ctx.arc()?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP