smooth color transition in chord diagram

274 views Asked by At

I used this https://www.d3-graph-gallery.com/graph/chord_colors.html to create a chord diagram. But I didn't like that the colors of the links between the groups have only 1 color. So decided to make it so that it smoothly transitions from the color of the first group to the color of the second group. My code looks like this.

   svg.datum(res)
  .append("g")
  .selectAll("path")
  .data(function(d) { return d; })
  .enter()
  .append("path")
    .attr("d", d3.ribbon()
      .radius(200)
    )
    .style("fill", function(d){ 
     
       //make a color transition
       return (d3.scaleLinear()
         .domain([0, 2])
         .range([colors[d.source.index], colors[d.target.index]])
         .interpolate(d3.interpolateHcl))
    
    }) // colors depend on the source group. Change to target otherwise.
    .style("stroke", "black");

but it doesn't seem to work, because now the links between the groups are all black. Can anyone help me fix this?

// create the svg area
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("width", 440)
  .attr("height", 440)
  .append("g")
  .attr("transform", "translate(220,220)")

// create a matrix
var matrix = [
  [0, 5871, 8916, 2868],
  [1951, 0, 2060, 6171],
  [8010, 16145, 0, 8045],
  [1013, 990, 940, 0]
];

// 4 groups, so create a vector of 4 colors
var colors = ["#440154ff", "#31668dff", "#37b578ff", "#fde725ff"]

// give this matrix to d3.chord(): it will calculates all the info we need to draw arc and ribbon
var res = d3.chord()
  .padAngle(0.05)
  .sortSubgroups(d3.descending)
  (matrix)

// add the groups on the outer part of the circle
svg
  .datum(res)
  .append("g")
  .selectAll("g")
  .data(function(d) {
    return d.groups;
  })
  .enter()
  .append("g")
  .append("path")
  .style("fill", function(d, i) {
    return colors[i]
  })
  .style("stroke", "black")
  .attr("d", d3.arc()
    .innerRadius(200)
    .outerRadius(210)
  )

// Add the links between groups
svg
  .datum(res)
  .append("g")
  .selectAll("path")
  .data(function(d) {
    return d;
  })
  .enter()
  .append("path")
  .attr("d", d3.ribbon()
    .radius(200)
  )
  .style("fill", function(d) {
    return (colors[d.source.index])
  }) // colors depend on the source group. Change to target otherwise.
  .style("stroke", "black");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="my_dataviz"></div>

1

There are 1 answers

0
Ruben Helsloot On

You can use radial gradients for that. In contrast to linear gradients, which format a square that goes from one colour to the other, a radial gradient changes in every direction. This is very useful for curved lines, since their only constant is that they tend to get further from the source, but not their direction.

I've placed a radial gradient for every link, with a centre at every source, and a colour change to the colour of the target in every direction. That way, every chord has a clear gradient in a logical direction.

const size = 440;

// create the svg area
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("width", size)
  .attr("height", size)
  .append("g")
  .attr("transform", "translate(" + (size / 2) + ", " + (size / 2) + ")");

var defs = svg.append('defs');

// create a matrix
var matrix = [
  [0, 5871, 8916, 2868],
  [1951, 0, 2060, 6171],
  [8010, 16145, 0, 8045],
  [1013, 990, 940, 0]
];

// 4 groups, so create a vector of 4 colors
var colors = ["#440154ff", "#31668dff", "#37b578ff", "#fde725ff"]

// give this matrix to d3.chord(): it will calculates all the info we need to draw arc and ribbon
var res = d3.chord()
  .padAngle(0.05)
  .sortSubgroups(d3.descending)
  (matrix)

// add the groups on the outer part of the circle
svg
  .datum(res)
  .append("g")
  .selectAll("g")
  .data(function(d) {
    return d.groups;
  })
  .enter()
  .append("g")
  .append("path")
  .style("fill", function(d, i) {
    return colors[i]
  })
  .style("stroke", "black")
  .attr("d", d3.arc()
    .innerRadius(size / 2 - 20)
    .outerRadius(size / 2 - 10)
  )

// Add one gradient for each link
var gradient = defs.selectAll("radialGradient")
  .data(res)
  .enter()
  .append("radialGradient")
  .attr("id", function(d) {
    return "gradient-" + d.source.index + '-' + d.target.index;
  })
  .each(function(d) {
    var centerAngle = (d.source.endAngle - d.source.startAngle) / 2;
    centerAngle += d.source.startAngle;
    const radius = 0.5;

    d3.select(this)
      .attr('cx', function() {
        return Math.sin(centerAngle) * radius + 0.5;
      })
      .attr('cy', function() {
        return -Math.cos(centerAngle) * radius + 0.5;
      })
      .attr('r', 1);
  });

gradient.append("stop")
  .attr('class', 'start')
  .attr("offset", "0%")
  .attr("stop-color", function(d) {
    return colors[d.source.index];
  })
  .attr("stop-opacity", 1);

gradient.append("stop")
  .attr('class', 'end')
  .attr("offset", "100%")
  .attr("stop-color", function(d) {
    return colors[d.target.index];
  })
  .attr("stop-opacity", 1);

// Add the links between groups
svg
  .datum(res)
  .append("g")
  .selectAll("path")
  .data(function(d) {
    return d;
  })
  .enter()
  .append("path")
  .attr("d", d3.ribbon()
    .radius(size / 2 - 20)
  )
  .style("fill", function(d) {
    return "url(#gradient-" + d.source.index + '-' + d.target.index + ")";
  }) // colors depend on the source group. Change to target otherwise.
  .style("stroke", "black");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="my_dataviz"></div>