Home » D3.js Graphs : A Tutorial

D3.js Graphs : A Tutorial

D3.js (Data-Driven Documents) is a powerful JavaScript library that enables you to create dynamic and interactive graphs directly in the browser.

With D3, you can create force-directed graphs, line graphs, and scatter plots that respond to user input and data changes.

In this tutorial, you’ll learn:

  • How to create different types of graphs with D3.js.
  • How to bind data to SVG elements.
  • Techniques for customizing and animating graphs.
  • How to handle interactivity (tooltips and hover effects).

1. Setting Up the Environment

Before diving into graph creation, set up a simple HTML file that loads D3.js from a CDN.

HTML Template:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>D3.js Graphs</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        svg {
            background-color: #f9f9f9;
            border: 1px solid #ddd;
        }
        .node {
            fill: steelblue;
            stroke: #fff;
            stroke-width: 2px;
        }
        .link {
            stroke: #aaa;
            stroke-width: 2px;
        }
        .tooltip {
            position: absolute;
            background: white;
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 4px;
            opacity: 0;
        }
    </style>
</head>
<body>

    <h1>D3.js Graph Examples</h1>
    <svg width="800" height="500"></svg>

</body>
</html>

2. Force-Directed Graph (Network Graph)

A force-directed graph displays nodes connected by links, commonly used to visualize relationships or networks.

Data for Graph:

const graphData = {
    nodes: [
        { id: "Node 1" },
        { id: "Node 2" },
        { id: "Node 3" },
        { id: "Node 4" }
    ],
    links: [
        { source: "Node 1", target: "Node 2" },
        { source: "Node 2", target: "Node 3" },
        { source: "Node 3", target: "Node 4" },
        { source: "Node 1", target: "Node 4" }
    ]
};

Drawing the Force-Directed Graph:

const svg = d3.select("svg");
const width = svg.attr("width");
const height = svg.attr("height");

const simulation = d3.forceSimulation(graphData.nodes)
    .force("link", d3.forceLink(graphData.links).id(d => d.id).distance(150))
    .force("charge", d3.forceManyBody().strength(-300))  // Repulsion
    .force("center", d3.forceCenter(width / 2, height / 2));

// Draw Links
const link = svg.selectAll("line")
    .data(graphData.links)
    .enter()
    .append("line")
    .attr("class", "link");

// Draw Nodes
const node = svg.selectAll("circle")
    .data(graphData.nodes)
    .enter()
    .append("circle")
    .attr("class", "node")
    .attr("r", 20)
    .call(drag(simulation));  // Enable drag behavior

// Tooltip
const tooltip = d3.select("body").append("div")
    .attr("class", "tooltip");

// Node Labels
node.append("title")
    .text(d => d.id);

// Simulate the Force
simulation.on("tick", () => {
    link.attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);

    node.attr("cx", d => d.x)
        .attr("cy", d => d.y);
});

// Drag Behavior
function drag(simulation) {
    return d3.drag()
        .on("start", (event, d) => {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        })
        .on("drag", (event, d) => {
            d.fx = event.x;
            d.fy = event.y;
        })
        .on("end", (event, d) => {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        });
}

Explanation:

  • Nodes and Links: Circles represent nodes, while lines represent links connecting them.
  • Force Simulation: forceSimulation applies forces to nodes and links, creating a dynamic, interactive graph.
  • Drag Behavior: Nodes can be dragged, and the graph automatically adjusts.
  • Labels and Tooltips: Node labels appear on hover.

3. Line Graph (Time-Series or Trends)

Line graphs are ideal for visualizing data trends over time.

Sample Data (Time Series):

const lineData = [
    { date: "2024-01-01", value: 30 },
    { date: "2024-01-02", value: 50 },
    { date: "2024-01-03", value: 80 },
    { date: "2024-01-04", value: 40 },
    { date: "2024-01-05", value: 60 }
];

Drawing the Line Graph:

const parseTime = d3.timeParse("%Y-%m-%d");
lineData.forEach(d => d.date = parseTime(d.date));

const x = d3.scaleTime()
    .domain(d3.extent(lineData, d => d.date))
    .range([50, 750]);

const y = d3.scaleLinear()
    .domain([0, d3.max(lineData, d => d.value)])
    .range([400, 50]);

const line = d3.line()
    .x(d => x(d.date))
    .y(d => y(d.value))
    .curve(d3.curveCatmullRom);

svg.append("path")
    .datum(lineData)
    .attr("class", "line")
    .attr("d", line);

// X Axis
svg.append("g")
    .attr("transform", "translate(0, 400)")
    .call(d3.axisBottom(x).ticks(5));

// Y Axis
svg.append("g")
    .attr("transform", "translate(50, 0)")
    .call(d3.axisLeft(y).ticks(5));

Explanation:

  • X and Y Axes: Represent time (x-axis) and data values (y-axis).
  • Path: The line generator creates a smooth path from the data points.

4. Scatter Plot (Visualizing Relationships)

Scatter plots visualize relationships between two variables.

Scatter Plot Data:

const scatterData = [
    { x: 5, y: 20 },
    { x: 15, y: 90 },
    { x: 25, y: 60 },
    { x: 35, y: 30 }
];

Drawing the Scatter Plot:

svg.selectAll("circle")
    .data(scatterData)
    .enter()
    .append("circle")
    .attr("cx", d => d.x * 20)
    .attr("cy", d => 400 - d.y * 3)
    .attr("r", 8)
    .attr("fill", "orange");

Conclusion:

With D3.js, creating force-directed graphs, line charts, and scatter plots is straightforward. By mastering force simulations, axis scaling, and interactive elements, you can build visually stunning and informative data visualizations.

You may also like