Vertical arc diagram in d3.js





This post describes how to build a vertical arc diagram with d3.js. It represent a very basic network composed of 10 nodes. You can see many other examples in the arc diagram section of the gallery. Learn more about the theory of arc diagrams in data-to-viz.com.

This post describes how to build a vertical arc diagram with d3.js. It represent a very basic network composed of 10 nodes. You can see many other examples in the arc diagram section of the gallery. Learn more about the theory of arc diagrams in data-to-viz.com. This example works with d3.js v4 and v6


Arc diagram section

Steps:

  • Data input format is Json. This document explains how to get this format from a classic table.

  • Plotting the nodes is pretty straightforward: a scalePoint() is set to distribute them uniformly along the Y axis. They are then added using a classing append("circle") approach.

  • Drawing links is a bit tricky. Code is highly commented and should allow to understand the basic process.

  • See also the horizontal version.
|
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v6.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>

// set the dimensions and margins of the graph
var margin = {top: 20, right: 30, bottom: 20, left: 30},
  width = 450 - margin.left - margin.right,
  height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");

// Read dummy data
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_network.json", function( data) {

  // List of node names
  var allNodes = data.nodes.map(function(d){return d.name})

  // A linear scale to position the nodes on the X axis
  var y = d3.scalePoint()
    .range([0, height])
    .domain(allNodes)

  // Add the circle for the nodes
  svg
    .selectAll("mynodes")
    .data(data.nodes)
    .enter()
    .append("circle")
      .attr("cx", 50)
      .attr("cy", function(d){ return(y(d.name))})
      .attr("r", 8)
      .style("fill", "#69b3a2")

  // And give them a label
  svg
    .selectAll("mylabels")
    .data(data.nodes)
    .enter()
    .append("text")
      .attr("x", 20)
      .attr("y", function(d){ return(y(d.name))})
      .text(function(d){ return(d.name)})
      .style("text-anchor", "middle")
      .style("alignment-baseline", "middle")

  // Add links between nodes. Here is the tricky part.
  // In my input data, links are provided between nodes -id-, NOT between node names.
  // So I have to do a link between this id and the name
  var idToNode = {};
  data.nodes.forEach(function (n) {
    idToNode[n.id] = n;
  });
  // Cool, now if I do idToNode["2"].name I've got the name of the node with id 2

  // Add the links
  svg
    .selectAll('mylinks')
    .data(data.links)
    .enter()
    .append('path')
    .attr('d', function (d) {
      start = y(idToNode[d.source].name)    // X position of start node on the X axis
      end = y(idToNode[d.target].name)      // X position of end node
      return ['M', 50, start,    // the arc starts at the coordinate x=start, y=height-30 (where the starting node is)
        'A',                            // This means we're gonna build an elliptical arc
        (start - end)/2*4, ',',    // Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance
        (start - end)/2, 0, 0, ',',
        start < end ? 1 : 0, 50, ',', end] // We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down.
        .join(' ');
    })
    .style("fill", "none")
    .attr("stroke", "black")

})

</script>
<script>

// set the dimensions and margins of the graph
const margin = {top: 20, right: 30, bottom: 20, left: 30},
      width = 450 - margin.left - margin.right,
      height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
const svg = d3.select("#my_dataviz")
              .append("svg")
              .attr("width", width + margin.left + margin.right)
              .attr("height", height + margin.top + margin.bottom)
              .append("g")
              .attr("transform",`translate(${margin.left},${margin.top})`);

// Read dummy data
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_network.json").then(function(data) {

  // List of node names
  const allNodes = data.nodes.map(d=>d.name)

  // A linear scale to position the nodes on the X axis
  const y = d3.scalePoint()
            .range([0, height])
            .domain(allNodes)

  // Add the circle for the nodes
  svg
    .selectAll("mynodes")
    .data(data.nodes)
    .join("circle")
      .attr("cx", 50)
      .attr("cy", d=>y(d.name))
      .attr("r", 8)
      .style("fill", "#69b3a2")

  // And give them a label
  svg
    .selectAll("mylabels")
    .data(data.nodes)
    .join("text")
    .attr("x", 20)
    .attr("y", d=>y(d.name))
    .text(d=>d.name)
    .style("text-anchor", "middle")
    .style("alignment-baseline", "middle")

  // Add links between nodes. Here is the tricky part.
  // In my input data, links are provided between nodes -id-, NOT between node names.
  // So I have to do a link between this id and the name
  const idToNode = {};
  data.nodes.forEach(function (n) {
    idToNode[n.id] = n;
  });
  // Cool, now if I do idToNode["2"].name I've got the name of the node with id 2
  // Add the links
  svg
    .selectAll('mylinks')
    .data(data.links)
    .join('path')
    .attr('d', d=> {
      start = y(idToNode[d.source].name)    // X position of start node on the X axis
      end = y(idToNode[d.target].name)      // X position of end node
      return ['M', 50, start,    // the arc starts at the coordinate x=start, y=height-30 (where the starting node is)
        'A',                            // This means we're gonna build an elliptical arc
        (start - end)/2*4, ',',    // Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance
        (start - end)/2, 0, 0, ',',
        start < end ? 1 : 0, 50, ',', end] // We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down.
        .join(' ');
    })
    .style("fill", "none")
    .attr("stroke", "black")

})

</script>

Related blocks →