javascriptd3.jsvisualizationchord-diagramchord

Equivalent of chord.groups in D3.js v7


I am trying to recreate Nadia Bremer's chord diagram for storytelling with data from https://gist.github.com/nbremer/94db779237655907b907

She accesses the chord.groups element when creating the g element:

var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", function(d) {return "group " + NameProvider[d.index];});

This works fine in v3 of D3. Here's a jsfiddle that works

However, when I try the same thing in D3 v7, chord.groups becomes undefined. Here is a jsfiddle with the v7 variant which gives an error when accessing chord.groups

The d3.chord() reference states that "The chords array also defines a secondary array of length n, chords.groups, where each group represents the combined outflow for node i..."

I've also found several examples on Observable which access chord.groups and they run fine. What am I doing wrong?


Solution

  • The error is very subtle, and here I'd blame the docs for lack of clarity, not you.

    The problem is that you passed the data to the chord generator like this:

    var chord = d3.chord(matrix)
    

    But d3.chord() doesn't accept arguments. Instead, it returns a function which accepts the arguments. Thus, it should be:

    var chord = d3.chord()(matrix)
    

    Alternatively, defining the generator and then passing the data:

    const chord = d3.chord();
    const chordLayout = chord(matrix);
    

    As you can see, it's a bit different. The confusion is maybe worsened by the fact that some D3 methods, like scales, accept data arguments like that, e. g. d3.scaleLinear(domain, range).

    Most of D3 generators are like this. For instance, using a common line generator...

    const lineGenerator = d3.line();
    

    You get the path d attribute using:

    lineGenerator(data);
    

    Which is the same of d3.line()(data), but it's not the same of d3.line(data).

    Here's your working v7 version:

    var NameProvider = ["Apple", "HTC", "Huawei", "LG", "Nokia", "Samsung", "Sony", "Other"];
    
    var matrix = [
      [9.6899, 0.8859, 0.0554, 0.443, 2.5471, 2.4363, 0.5537, 2.5471], /*Apple 19.1584*/
      [0.1107, 1.8272, 0, 0.4983, 1.1074, 1.052, 0.2215, 0.4983], /*HTC 5.3154*/
      [0.0554, 0.2769, 0.2215, 0.2215, 0.3876, 0.8306, 0.0554, 0.3322], /*Huawei 2.3811*/
      [0.0554, 0.1107, 0.0554, 1.2182, 1.1628, 0.6645, 0.4983, 1.052], /*LG 4.8173*/
      [0.2215, 0.443, 0, 0.2769, 10.4097, 1.2182, 0.4983, 2.8239], /*Nokia 15.8915*/
      [1.1628, 2.6024, 0, 1.3843, 8.7486, 16.8328, 1.7165, 5.5925], /*Samsung 38.0399*/
      [0.0554, 0.4983, 0, 0.3322, 0.443, 0.8859, 1.7719, 0.443], /*Sony 4.4297*/
      [0.2215, 0.7198, 0, 0.3322, 1.6611, 1.495, 0.1107, 5.4264] /*Other 9.9667*/
    ];
    /*Sums up to exactly 100*/
    
    var colors = ["#C4C4C4", "#69B40F", "#EC1D25", "#C8125C", "#008FC8", "#10218B", "#134B24", "#737373"];
    
    /*Initiate the color scale*/
    var fill = d3.scaleOrdinal()
      .domain(d3.range(NameProvider.length))
      .range(colors);
    
    /*//////////////////////////////////////////////////////////
    /////////////// Initiate Chord Diagram /////////////////////
    //////////////////////////////////////////////////////////*/
    var margin = {
        top: 30,
        right: 25,
        bottom: 20,
        left: 25
      },
      width = 650 - margin.left - margin.right,
      height = 600 - margin.top - margin.bottom,
      innerRadius = Math.min(width, height) * .39,
      outerRadius = innerRadius * 1.04;
    
    /*Initiate the SVG*/
    var svg = d3.select("#chart")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("svg:g")
      .attr("transform", "translate(" + (margin.left + width / 2) + "," + (margin.top + height / 2) + ")");
    
    
    var chord = d3.chord()
      .sortSubgroups(d3.descending) /*sort the chords inside an arc from high to low*/
      .sortChords(d3.descending)(matrix);
    
    
    /*//////////////////////////////////////////////////////////
    ////////////////// Draw outer Arcs /////////////////////////
    //////////////////////////////////////////////////////////*/
    
    var arc = d3.arc()
      .innerRadius(innerRadius)
      .outerRadius(outerRadius);
    
    
    var g = svg.selectAll("g.group")
      .data(chord.groups)
      .enter().append("svg:g")
      .attr("class", function(d) {
        return "group " + NameProvider[d.index];
      });
    
    g.append("svg:path")
      .attr("class", "arc")
      .style("stroke", function(d) {
        return fill(d.index);
      })
      .style("fill", function(d) {
        return fill(d.index);
      })
      .attr("d", arc)
      .style("opacity", 0)
      .transition().duration(1000)
      .style("opacity", 0.4);
    #chart rect {
      fill: steelblue;
    }
    
    #chart text {
      fill: white;
      font: 10px Helvetica;
      text-anchor: end;
    }
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <svg id="chart"></svg>