Search code examples
d3.jschord-diagram

Broken D3js Chord Diagram


I am having trouble with a D3 Chord diagram.

https://jsfiddle.net/Nyquist212/3vc0f2e5/6/

It uses the Steve Halls excellent Chord Mapper functions.

http://www.delimited.io/blog/2013/12/8/chord-diagrams-in-d3

While I've success with this in the past, I'm getting this error suggesting there's something wrong with my data (?).

Error: attribute d: Expected number, "…666666669 0 1,1 NaN,NaNLNaN,NaNA…".

Can someone help me understand what I might be doing wrong?

var data = [
  {
    "count": 1,
    "source": "6540",
    "target": "1030"
  },
  {
    "count": 1,
    "source": "6263",
    "target": "727"
  },
  {
    "count": 1,
    "source": "16760",
    "target": "3085"
  },
  {
    "count": 1,
    "source": "7518",
    "target": "2035"
  },
  {
    "count": 1,
    "source": "5512",
    "target": "1560"
  },
  {
    "count": 1,
    "source": "16239",
    "target": "3135"
  },
  {
    "count": 1,
    "source": "116528",
    "target": "4130"
  },
  {
    "count": 1,
    "source": "14060M",
    "target": "3130"
  },
  {
    "count": 1,
    "source": "6264",
    "target": "727"
  },
  {
    "count": 1,
    "source": "6265",
    "target": "727"
  },
  {
    "count": 1,
    "source": "6542",
    "target": "1036"
  },
  {
    "count": 1,
    "source": "16018",
    "target": "3035"
  },
  {
    "count": 1,
    "source": "116619LB",
    "target": "3135"
  },
  {
    "count": 1,
    "source": "116400",
    "target": "3130"
  },
  {
    "count": 1,
    "source": "5501",
    "target": "1530"
  },
  {
    "count": 1,
    "source": "6542",
    "target": "1065"
  },
  {
    "count": 1,
    "source": "1016",
    "target": "1560"
  },
  {
    "count": 1,
    "source": "6262",
    "target": "727"
  },
  {
    "count": 1,
    "source": "16520",
    "target": "4030"
  },
  {
    "count": 1,
    "source": "6517",
    "target": "1166"
  },
  {
    "count": 1,
    "source": "16528",
    "target": "4030"
  },
  {
    "count": 1,
    "source": "116518",
    "target": "4130"
  },
  {
    "count": 1,
    "source": "5514",
    "target": "1520"
  },
  {
    "count": 1,
    "source": "6827",
    "target": "2035"
  },
  {
    "count": 1,
    "source": "5508",
    "target": "1530"
  },
  {
    "count": 1,
    "source": "16515",
    "target": "4030"
  },
  {
    "count": 1,
    "source": "5510",
    "target": "1530"
  },
  {
    "count": 1,
    "source": "114234",
    "target": "2235"
  },
  {
    "count": 1,
    "source": "16238",
    "target": "3135"
  },
  {
    "count": 1,
    "source": "5517",
    "target": "1520"
  },
  {
    "count": 1,
    "source": "1675",
    "target": "1565"
  },
  {
    "count": 1,
    "source": "14000",
    "target": "3000"
  },
  {
    "count": 1,
    "source": "1804",
    "target": "1556"
  },
  {
    "count": 1,
    "source": "114200",
    "target": "2235"
  },
  {
    "count": 1,
    "source": "16713",
    "target": "3185"
  },
  {
    "count": 1,
    "source": "1675",
    "target": "1575"
  },
  {
    "count": 1,
    "source": "116589",
    "target": "4130"
  },
  {
    "count": 1,
    "source": "6917",
    "target": "2035"
  },
  {
    "count": 1,
    "source": "1678",
    "target": "1565"
  },
  {
    "count": 1,
    "source": "16250",
    "target": "3135"
  },
  {
    "count": 1,
    "source": "6916",
    "target": "2035"
  },
  {
    "count": 1,
    "source": "6538A",
    "target": "1030"
  },
  {
    "count": 1,
    "source": "6604",
    "target": "1065"
  },
  {
    "count": 1,
    "source": "16750",
    "target": "3075"
  },
  {
    "count": 1,
    "source": "16000",
    "target": "3035"
  },
  {
    "count": 1,
    "source": "16014",
    "target": "3035"
  },
  {
    "count": 1,
    "source": "16700",
    "target": "3175"
  },
  {
    "count": 1,
    "source": "16550",
    "target": "3085"
  },
  {
    "count": 1,
    "source": "1655",
    "target": "1575"
  },
  {
    "count": 1,
    "source": "116523",
    "target": "4130"
  },
  {
    "count": 1,
    "source": "116598",
    "target": "4130"
  },
  {
    "count": 1,
    "source": "6824",
    "target": "2035"
  },
  {
    "count": 1,
    "source": "6610",
    "target": "1030"
  },
  {
    "count": 1,
    "source": "6241",
    "target": "645"
  },
  {
    "count": 1,
    "source": "16518",
    "target": "4030"
  },
  {
    "count": 1,
    "source": "16523",
    "target": "4030"
  },
  {
    "count": 1,
    "source": "1803",
    "target": "1556"
  },
  {
    "count": 1,
    "source": "1673",
    "target": "1575"
  }
];

var mpr = chordMpr(data);

    mpr
    .addValuesToMap('source')
    .setFilter(function (row, a, b) {
        return(row.source == a.name && row.target == b.name);
        })
    .setAccessor(function (recs, a, b) {
        if(!recs[0]) return 0;
        return +recs[0].count ;
        });

    drawChords(mpr.getMatrix(), mpr.getMap());

function drawChords (matrix, mmap) {

        var w = 950, h = 950, r1 = h / 3, r0 = r1 - 150;

        var fill = d3.scale.category20();

        var chord = d3.layout.chord()
            .padding(.02)   // Padding between arc categories
            .sortSubgroups(d3.descending)
            .sortChords(d3.descending);

        var arc = d3.svg.arc()
            .innerRadius(r0)
            .outerRadius(r0 + 40);  // Thickness of arc category

        var svg = d3.select("#chord") // Attach to the div element
            .append("svg:svg")
            .attr("width", w)
            .attr("height", h)
            .append("svg:g")
            .attr("id", "circle")
            .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");

        svg.append("circle").attr("r", r0 + 50);

        var rdr = chordRdr(matrix, mmap);

        chord.matrix(matrix);

        var g = svg.selectAll("g.group")
            .data(chord.groups())
            .enter().append("svg:g")
            .attr("class", "group")


            g.append("svg:path")
                .style("stroke", "black")
                .style("fill", function(d) { return fill(d.index); })
                .attr("d", arc);

    var chordPaths = svg.selectAll("path.chord")
        .data(chord.chords())
      .enter().append("svg:path")
        .attr("class", "chord")
        .style("stroke", function(d) { return d3.rgb(fill(d.target.index)).darker(); })
        .style("fill", function(d) { return fill(d.target.index); })
        .attr("d", d3.svg.chord().radius(r0))
};

// blog-post - http://www.delimited.io/blog/2013/12/8/chord-diagrams-in-d3
//*******************************************************************
//  CHORD MAPPER 
//*******************************************************************
function chordMpr (data) {
  var mpr = {}, mmap = {}, n = 0,
      matrix = [], filter, accessor;

  mpr.setFilter = function (fun) {
    filter = fun;
    return this;
  },
  mpr.setAccessor = function (fun) {
    accessor = fun;
    return this;
  },
  mpr.getMatrix = function () {
    matrix = [];
    _.each(mmap, function (a) {
      if (!matrix[a.id]) matrix[a.id] = [];
      _.each(mmap, function (b) {
       var recs = _.filter(data, function (row) {
          return filter(row, a, b);
        })
        matrix[a.id][b.id] = accessor(recs, a, b);
      });
    });
    return matrix;
  },
  mpr.getMap = function () {
    return mmap;
  },
  mpr.printMatrix = function () {
    _.each(matrix, function (elem) {
      console.log(elem);
    })
  },
  mpr.addToMap = function (value, info) {
    if (!mmap[value]) {
      mmap[value] = { name: value, id: n++, data: info }
    }
  },
  mpr.addValuesToMap = function (varName, info) {
    var values = _.uniq(_.pluck(data, varName));
    _.map(values, function (v) {
      if (!mmap[v]) {
        mmap[v] = { name: v, id: n++, data: info }
      }
    });
    return this;
  }
  return mpr;
}
//*******************************************************************
//  CHORD READER
//*******************************************************************
function chordRdr (matrix, mmap) {
  return function (d) {
    var i,j,s,t,g,m = {};
    if (d.source) {
      i = d.source.index; j = d.target.index;
      s = _.where(mmap, {id: i });
      t = _.where(mmap, {id: j });
      m.sname = s[0].name;
      m.sdata = d.source.value;
      m.svalue = +d.source.value;
      m.stotal = _.reduce(matrix[i], function (k, n) { return k + n }, 0);
      m.tname = t[0].name;
      m.tdata = d.target.value;
      m.tvalue = +d.target.value;
      m.ttotal = _.reduce(matrix[j], function (k, n) { return k + n }, 0);
    } else {
      g = _.where(mmap, {id: d.index });
      m.gname = g[0].name;
      m.gdata = g[0].data;
      m.gvalue = d.value;
    }
    m.mtotal = _.reduce(matrix, function (m1, n1) { 
      return m1 + _.reduce(n1, function (m2, n2) { return m2 + n2}, 0);
    }, 0);
    return m;
  }
}

Solution

  • The source and target ids are mutually exclusive (i.e. no sources are targets, no targets are sources) and you're only adding the values for the sources, so when the layout algorithm looks up any target it returns null and that eventually cascades out as the NaNs in the layout.

    Resolve by adding the targets too:

    .addValuesToMap('source')
    .addValuesToMap('target')
    

    There is the question of whether a chord diagram is best to show a bipartite graph but it does seem to keep the two groups together (probably just because they were added separately)