Skip to content
D3.js SVG Explained — Shapes, Paths & Transformations Step-by-Step

D3.js SVG Explained — Shapes, Paths & Transformations Step-by-Step

DodaTech Updated Jun 6, 2026 14 min read

D3.js visualizations are built on SVG (Scalable Vector Graphics). Understanding SVG elements and how D3 generates them is essential for creating custom charts.

What You’ll Learn

By the end of this tutorial, you will be able to:

  • Understand the SVG coordinate system and how it differs from math graphs
  • Draw basic SVG shapes: circles, rectangles, lines, and ellipses
  • Use SVG path commands (M, L, C, Q, Z) to draw custom shapes
  • Leverage D3 path generators for lines, areas, arcs, and symbols
  • Group elements with <g> and apply transformations
  • Build data-driven SVG visualizations

Why SVG Matters for D3.js

SVG is the canvas D3.js paints on. When Doda Browser renders its memory usage timeline or Durga Antivirus Pro displays real-time threat detection charts, they’re using SVG elements created by D3.js. Unlike raster graphics (PNG, JPEG), SVG is resolution-independent — charts look crisp on any screen — and each element is a DOM node that can be styled, animated, and made interactive. Understanding SVG is understanding the foundation of every D3.js visualization.

Learning Path

    flowchart LR
    A["D3.js Basics"] --> B["D3.js SVG"]
    B --> C["D3.js Scales & Axes"]
    C --> D["D3.js Charts"]
    D --> E["D3.js Interactions"]
    D --> F["D3.js Data Handling"]
    E --> G["D3.js Advanced"]
    F --> G
    G --> H["D3.js Reference"]
    B:::current
    
    classDef current fill:#4CAF50,color:#fff,stroke:#333,stroke-width:2px
  
Prerequisites: You should be comfortable with D3.js selections and the data join pattern from the D3.js Basics tutorial. Basic HTML knowledge is required.

The SVG Coordinate System — The Most Important Thing to Understand

Before drawing anything, you need to understand how SVG measures positions. This trips up almost every beginner.

(0,0) ──────────→ x (increases right)
  │
  │   ┌──────────────┐
  │   │  (x, y)      │
  │   │     width    │
  │   │   height     │
  │   └──────────────┘
  ▼
  y (increases DOWN)

In math class: y increases upward. A value of 100 is “high up” on a graph.

In SVG: y increases downward. A value of 0 is at the top, 100 is further down.

This inversion is the #1 cause of confusion for beginners. When your bar chart appears upside down, this is why. You’ll need to invert the y-axis when drawing charts: y = chartHeight - value.

Creating the SVG Canvas

// Create an SVG canvas on the page
var svg = d3.select("body")
    .append("svg")
    .attr("width", 400)
    .attr("height", 300)
    .style("border", "1px solid #ccc");

What this does: It selects <body>, appends an <svg> element 400×300 pixels, and gives it a light gray border so you can see the canvas boundaries. The width and height attributes define the viewable area.

Basic SVG Shapes

Think of SVG shapes as stamps — each shape is a DOM element with position and size attributes.

Circle

svg.append("circle")
    .attr("cx", 100)     // center x position
    .attr("cy", 100)     // center y position
    .attr("r", 50)       // radius in pixels
    .attr("fill", "steelblue")
    .attr("stroke", "black")
    .attr("stroke-width", 2);

Why each attribute?:

  • cx, cy — where the circle’s center sits on the canvas
  • r — how big the circle is (distance from center to edge)
  • fill — inside color; without this, the circle is invisible
  • stroke — outline color; optional but useful for definition

Rectangle

svg.append("rect")
    .attr("x", 50)       // top-left corner x
    .attr("y", 50)       // top-left corner y
    .attr("width", 100)
    .attr("height", 80)
    .attr("rx", 5)       // rounded corner radius x
    .attr("ry", 5)       // rounded corner radius y
    .attr("fill", "tomato");

Why position matters: x and y define the top-left corner, not the center. If you want a rectangle centered at (100, 100), subtract half the width from x and half the height from y.

Line

svg.append("line")
    .attr("x1", 0)     // start point x
    .attr("y1", 0)     // start point y
    .attr("x2", 200)   // end point x
    .attr("y2", 100)   // end point y
    .attr("stroke", "green")
    .attr("stroke-width", 3);

Key insight: Lines don’t have a fill attribute — they only use stroke. Without setting stroke, the line won’t be visible.

Ellipse

svg.append("ellipse")
    .attr("cx", 200)    // center x
    .attr("cy", 100)    // center y
    .attr("rx", 80)     // x-radius (horizontal stretch)
    .attr("ry", 50)     // y-radius (vertical stretch)
    .attr("fill", "purple");

Think of an ellipse as a stretched circle. A circle with different rx and ry becomes an ellipse. When rx === ry, it’s a circle.

SVG Paths — The Power Tool

Paths are the most flexible SVG element. A single path can draw lines, curves, arcs, and complex shapes. The d attribute contains the drawing instructions.

Path Commands

CommandNameWhat It DoesExample
MMove toLifts the pen, moves to (x, y)M 100 50
LLine toDraws a line to (x, y)L 50 150
HHorizontal lineDraws horizontal line to xH 200
VVertical lineDraws vertical line to yV 100
CCubic BezierCurved line with 2 control pointsC cx1 cy1, cx2 cy2, x y
QQuadratic BezierCurved line with 1 control pointQ cx cy, x y
AArcCircular arc segmentA rx ry rot large sweep x y
ZClose pathDraws straight line back to startZ

Drawing a Triangle with Paths

// A triangle: start at top, go to bottom-left, then bottom-right, close
svg.append("path")
    .attr("d", "M 100 50 L 50 150 L 150 150 Z")
    .attr("fill", "gold")
    .attr("stroke", "orange");

Reading the path command:

  • M 100 50 — Move the pen to (100, 50) without drawing (this is the top point)
  • L 50 150 — Draw a line from (100, 50) to (50, 150) (bottom-left)
  • L 150 150 — Draw a line from (50, 150) to (150, 150) (bottom-right)
  • Z — Close the shape: draw a line from (150, 150) back to (100, 50)

Quadratic Curve

svg.append("path")
    .attr("d", "M 50 200 Q 100 50 200 150")
    .attr("fill", "none")
    .attr("stroke", "teal")
    .attr("stroke-width", 2);

Reading the curve: Start at (50, 200), draw a quadratic curve controlled by point (100, 50) ending at (200, 150). The control point acts like a magnet pulling the curve toward it.

D3 Path Generators — Writing Paths for You

Writing path strings by hand is error-prone. D3 provides generator functions that create path strings from data.

Line Generator

var data = [
    { x: 0, y: 50 },
    { x: 100, y: 80 },
    { x: 200, y: 30 },
    { x: 300, y: 120 },
    { x: 400, y: 60 }
];

// Create a line generator — it's a function that converts data to a path string
var line = d3.line()
    .x(function(d) { return d.x; })  // how to get x from each data point
    .y(function(d) { return d.y; }); // how to get y from each data point

// Bind the data and draw the path
svg.append("path")
    .datum(data)                      // bind one array of data
    .attr("d", line)                  // the generator creates the d string
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 2);

Why datum() instead of data()? A line chart uses one array of points to create one path element. .data() is for binding an array to multiple elements. .datum() binds a single value (the whole array) to one element.

Area Generator

var area = d3.area()
    .x(function(d) { return d.x; })
    .y0(200)                          // baseline — where the area starts
    .y1(function(d) { return d.y; }); // top line

svg.append("path")
    .datum(data)
    .attr("d", area)
    .attr("fill", "steelblue")
    .attr("opacity", 0.3);

An area chart is like a line chart with the space below filled in. y0 is the bottom boundary, y1 is the top boundary (the line itself).

Arc Generator (for Pie/Donut Charts)

var arc = d3.arc()
    .innerRadius(50)        // 0 = pie, >0 = donut
    .outerRadius(100)       // outer edge
    .startAngle(0)          // where to start (in radians)
    .endAngle(Math.PI / 2); // where to end (90 degrees)

svg.append("path")
    .attr("d", arc)
    .attr("transform", "translate(200, 150)")
    .attr("fill", "coral");

Radians vs degrees: SVG uses radians, not degrees. Full circle = 2π radians. π/2 = 90°, π = 180°, 2π = 360°.

Symbol Generator (for Markers)

var symbols = [
    d3.symbolCircle, d3.symbolCross, d3.symbolDiamond,
    d3.symbolSquare, d3.symbolStar, d3.symbolTriangle, d3.symbolWye
];

svg.selectAll("path")
    .data(symbols)
    .enter()
    .append("path")
    .attr("d", d3.symbol().type(function(d) { return d; }).size(200))
    .attr("transform", function(d, i) { return "translate(" + (50 + i * 50) + ", 50)"; })
    .attr("fill", "steelblue");

Useful for scatter plot markers — each symbol gives a different visual representation for data points.

SVG Groups (<g>)

Think of a <g> like a container or folder for SVG elements. Attributes applied to the group affect all children.

var g = svg.append("g")
    .attr("transform", "translate(100, 50)");

g.append("circle").attr("r", 30).attr("fill", "red");
g.append("circle").attr("r", 20).attr("fill", "white");
g.append("circle").attr("r", 10).attr("fill", "red");

Why groups matter: Without the group, you’d need to add cx and cy to every circle. With the group translated by (100, 50), all three circles are offset by that amount. This is how you build complex charts — each component (bars, axes, legend) lives in its own group.

Transformations

Transformations change how elements are positioned, rotated, or scaled. They act like instructions on how to interpret the coordinates, not changes to the coordinates themselves.

Translate (Move)

.attr("transform", "translate(x, y)")

// Dynamic with data
.attr("transform", function(d) {
    return "translate(" + d.x + ", " + d.y + ")";
});

Rotate

// Rotate around (0,0)
.attr("transform", "rotate(45)")

// Rotate around a specific center point
.attr("transform", "rotate(45, 100, 100)")

Scale

// Uniform scale
.attr("transform", "scale(1.5)")

// Non-uniform (stretch)
.attr("transform", "scale(2, 0.5)")

// Combined — apply in order!
.attr("transform", "translate(50, 50) rotate(45) scale(1.5)")

Order matters: Transformations are applied right-to-left. translate(50, 50) rotate(45) means: first rotate around (0,0), then move the result. This is different from rotate(45) translate(50, 50) which means: first move, then rotate.

Data-Driven SVG — Putting It Together

<!DOCTYPE html>
<html>
<head>
    <title>Data-Driven SVG</title>
</head>
<body>
    <svg width="400" height="200" id="chart"></svg>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script>
        var data = [
            { shape: "circle", x: 60, y: 100, size: 40, color: "steelblue" },
            { shape: "rect", x: 160, y: 60, size: 40, color: "tomato" },
            { shape: "circle", x: 260, y: 100, size: 30, color: "green" },
            { shape: "rect", x: 340, y: 80, size: 25, color: "purple" }
        ];

        var svg = d3.select("#chart");

        // Draw circles from the data
        svg.selectAll("circle")
            .data(data.filter(function(d) { return d.shape === "circle"; }))
            .enter()
            .append("circle")
            .attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; })
            .attr("r", function(d) { return d.size; })
            .attr("fill", function(d) { return d.color; });

        // Draw rectangles from the data
        svg.selectAll("rect.custom")
            .data(data.filter(function(d) { return d.shape === "rect"; }))
            .enter()
            .append("rect")
            .attr("class", "custom")
            .attr("x", function(d) { return d.x - d.size / 2; })
            .attr("y", function(d) { return d.y - d.size / 2; })
            .attr("width", function(d) { return d.size; })
            .attr("height", function(d) { return d.size; })
            .attr("fill", function(d) { return d.color; });
    </script>
</body>
</html>

What’s happening: We filter the data by shape type, draw circles for circle entries and rectangles for rect entries. This pattern — filtering data and drawing different shape types — is how you build scatter plots with different markers.

Common Mistakes

1. Thinking SVG y-Coordinates Increase Upward

SVG y increases downward. A bar chart drawn with data values directly as y positions will appear upside down. Always invert: y = chartHeight - value.

2. Forgetting to Set fill or stroke

// WRONG — invisible shape
svg.append("circle").attr("r", 20);

// RIGHT — visible
svg.append("circle").attr("r", 20).attr("fill", "steelblue");

3. Using .data() Instead of .datum() for Path Generators

// WRONG — creates multiple paths, each with one point
svg.selectAll("path").data([points]).enter().append("path").attr("d", line);

// RIGHT — creates one path with all points
svg.datum(points).append("path").attr("d", line);

4. Not Accounting for Group Transforms

When a <g> is translated by (100, 50), child coordinates are relative to the group. A circle at cx=0, cy=0 inside the group appears at (100, 50) on the SVG canvas. This is intentional but confusing if you’re calculating absolute positions.

5. Using CSS Pixels for SVG Attributes

SVG attributes like cx, cy, r, width, height are unitless (default to pixels). CSS properties like font-size need units:

// WRONG — SVG attribute with px suffix
.attr("font-size", "14px")  // doesn't work

// RIGHT
.style("font-size", "14px")
.attr("font-size", 14)      // also works (SVG treats as pixels)

6. Path Commands Without Space Separators

Some path commands work without spaces (M100 50L50 150), but always use spaces for readability and to avoid parsing bugs: M 100 50 L 50 150.

Practice Questions

Question 1

Why does y increase downward in SVG instead of upward like in math?

Answer: SVG was designed for document layout, not data visualization. The coordinate system mimics how you read a page — top to bottom. When building charts, you must invert the y-axis: y = chartHeight - value.

Question 2

What is the difference between a <g> element and a <div> in HTML?

Answer: <g> is a group container in SVG — it doesn’t have a visual appearance itself, but attributes and transforms applied to it affect all children. It’s the SVG equivalent of a <div>, but for vector graphics.

Question 3

When would you use d3.line() vs d3.area()?

Answer: Use d3.line() when you want to show a trend (like a stock price over time). Use d3.area() when you want to emphasize magnitude (like the volume of sales, where the filled area shows quantity).

Question 4

What does the path command Z do?

Answer: Z (close path) draws a straight line from the current position back to the starting point of the path. It’s how you close shapes like triangles and polygons.

Question 5

How do you create a donut chart instead of a pie chart?

Answer: Set innerRadius to a value greater than 0 in d3.arc(). For example, innerRadius(60) creates a donut with a 60px hole, while innerRadius(0) creates a solid pie.

Challenge

Create an SVG visualization that draws 10 circles in a row, each with a progressively larger radius and a different color. Use a data array and D3’s data join. The circles should be evenly spaced and the colors should transition from light to dark blue.

FAQ

What is the difference between SVG and Canvas?
SVG is a vector-based graphics format where each element is a DOM node — good for interactivity, accessibility, and small-to-medium data. Canvas is pixel-based and faster for very large datasets but harder to make interactive.
Can I use CSS to style SVG elements?
Yes. SVG elements can be styled with CSS properties like fill, stroke, opacity, and transition. Use .style() in D3 or CSS classes via .attr("class", ...).
What is the <g> element for?
<g> is a group container. Attributes and transforms applied to a <g> affect all its children. It’s the SVG equivalent of a <div>.
What does d3.symbol() generate?
It generates SVG path data for predefined shapes: circle, cross, diamond, square, star, triangle, and wye. Useful for scatter plot markers.
How do I draw curved lines in D3?
Use d3.line().curve(d3.curveMonotoneX) or other curve factories like curveBasis, curveCardinal, curveStep.

Try It Yourself

Here’s a complete interactive SVG shape explorer. Add shapes to the canvas with random positions and colors:

<!DOCTYPE html>
<html>
<head>
    <title>SVG Shape Explorer — Try It Yourself</title>
    <style>
        body { font-family: sans-serif; padding: 20px; }
        #canvas { border: 1px solid #ccc; margin: 10px 0; }
        .controls { margin: 10px 0; }
        select, button, input { padding: 5px 10px; margin: 0 5px; cursor: pointer; }
    </style>
</head>
<body>
    <h2>SVG Shape Explorer</h2>
    <div class="controls">
        <select id="shapeSelect">
            <option value="circle">Circle</option>
            <option value="rect">Rectangle</option>
            <option value="ellipse">Ellipse</option>
            <option value="line">Line</option>
            <option value="triangle">Triangle (Path)</option>
            <option value="star">Star (Symbol)</option>
        </select>
        <input type="color" id="colorPicker" value="#4CAF50">
        <button onclick="addShape()">Add Shape</button>
        <button onclick="clearShapes()">Clear All</button>
    </div>
    <svg width="400" height="300" id="canvas"></svg>

    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script>
        var svg = d3.select("#canvas");

        function addShape() {
            var type = document.getElementById("shapeSelect").value;
            var color = document.getElementById("colorPicker").value;
            var x = Math.random() * 350 + 25;
            var y = Math.random() * 250 + 25;

            switch (type) {
                case "circle":
                    svg.append("circle")
                        .attr("cx", x).attr("cy", y)
                        .attr("r", 20).attr("fill", color);
                    break;
                case "rect":
                    svg.append("rect")
                        .attr("x", x - 20).attr("y", y - 15)
                        .attr("width", 40).attr("height", 30)
                        .attr("rx", 3).attr("fill", color);
                    break;
                case "ellipse":
                    svg.append("ellipse")
                        .attr("cx", x).attr("cy", y)
                        .attr("rx", 30).attr("ry", 20).attr("fill", color);
                    break;
                case "line":
                    svg.append("line")
                        .attr("x1", x - 20).attr("y1", y)
                        .attr("x2", x + 20).attr("y2", y)
                        .attr("stroke", color).attr("stroke-width", 4);
                    break;
                case "triangle":
                    svg.append("polygon")
                        .attr("points", (x)+","+(y-20)+" "+(x-20)+","+(y+15)+" "+(x+20)+","+(y+15))
                        .attr("fill", color);
                    break;
                case "star":
                    var starPath = d3.symbol().type(d3.symbolStar).size(800)();
                    svg.append("path")
                        .attr("d", starPath)
                        .attr("transform", "translate("+x+","+y+")")
                        .attr("fill", color);
                    break;
            }
        }

        function clearShapes() {
            svg.selectAll("*").remove();
        }
    </script>
</body>
</html>

Try this: Select different shape types, pick a color, and click “Add Shape” to place shapes randomly. Notice how each shape uses different SVG attributes.

What’s Next

Now that you understand SVG, you’re ready to map data to pixels:

TutorialWhat You’ll Learn
D3.js Scales & AxesMap data values to pixel coordinates
D3.js ChartsBuild complete bar, pie, and line charts
D3.js InteractionsAdd tooltips, drag, and zoom

Related topics: SVG, CSS, JavaScript, HTML, Data Visualization


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — bringing secure, high-performance software to your digital life.

What’s Next

Congratulations on completing this D3Js Svg tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro