title: D3.js hierarchical edge bundling coloring by group
body: <p>I am trying to color the connections in my hierarchical edge bundling visualization based on the groups they are connecting to. An example of this can be seen <a href="https://www.seas.harvard.edu/academics#vis" rel="noreferrer">here</a>.</p>
  
  <p><a href="https://i.stack.imgur.com/1FLQ9.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/1FLQ9.jpg" alt="enter image description here"></a></p>
  
  <p>Here is my current mouseover function:</p>
  
  <pre><code>    function mouseover(d) {
          svg.selectAll("path.link.target-" + d.key)
              .classed("target", true)
              .each(updateNodes("source", true));
  
          svg.selectAll("path.link.source-" + d.key)
              .classed("source", true)
              .each(updateNodes("target", true));
      }
  </code></pre>
  
  <p>And here is the mouseover function from the example I've posted:</p>
  
  <pre><code>function mouseovered(d) 
  {
          // Handle tooltip
          // Tooltips should avoid crossing into the center circle
  
          d3.selectAll("#tooltip").remove();
          d3.selectAll("#vis")
              .append("xhtml:div")
              .attr("id", "tooltip")
              .style("opacity", 0)
              .html(d.title);
          var mouseloc = d3.mouse(d3.select("#vis")[0][0]),
              my = ((rotateit(d.x) &gt; 90) &amp;&amp; (rotateit(d.x) &lt; 270)) ? mouseloc[1] + 10 : mouseloc[1] - 35,
              mx = (rotateit(d.x) &lt; 180) ? (mouseloc[0] + 10) :  Math.max(130, (mouseloc[0] - 10 - document.getElementById("tooltip").offsetWidth));
          d3.selectAll("#tooltip").style({"top" : my + "px", "left": mx + "px"});
          d3.selectAll("#tooltip")
              .transition()
              .duration(500)
              .style("opacity", 1);
          node.each(function(n) { n.target = n.source = false; });
  
          currnode = d3.select(this)[0][0].__data__;
  
          link.classed("link--target", function(l) { 
                  if (l.target === d) 
                  { 
                      return l.source.source = true; 
                  }
                  if (l.source === d) 
                  { 
                      return l.target.target = true; 
                  }
              })
              .filter(function(l) { return l.target === d || l.source === d; })
              .attr("stroke", function(d){
                  if (d[0].name == currnode.name)
                  {
                      return color(d[2].cat);
                  }
                  return color(d[0].cat);
              })
              .each(function() { this.parentNode.appendChild(this); });
  
          d3.selectAll(".link--clicked").each(function() { this.parentNode.appendChild(this); });
  
          node.classed("node--target", function(n) { 
                  return (n.target || n.source); 
              });
  }
  </code></pre>
  
  <p>I am somewhat new to D3, but I am assuming what I'll need to do is check the group based on the key and then match it to the same color as that group.</p>
  
  <p>My full code is here:</p>
  
  <pre><code> &lt;script type="text/javascript"&gt;
      color = d3.scale.category10(); 
  
      var w = 840,
          h = 800,
          rx = w / 2,
          ry = h / 2,
          m0,
          rotate = 0
      pi = Math.PI;
  
      var splines = [];
  
      var cluster = d3.layout.cluster()
          .size([360, ry - 180])
          .sort(function(a, b) {
              return d3.ascending(a.key, b.key);
          });
  
      var bundle = d3.layout.bundle();
  
      var line = d3.svg.line.radial()
          .interpolate("bundle")
          .tension(.5)
          .radius(function(d) {
              return d.y;
          })
          .angle(function(d) {
              return d.x / 180 * Math.PI;
          });
  
      // Chrome 15 bug: &lt;http://code.google.com/p/chromium/issues/detail?id=98951&gt;
      var div = d3.select("#bundle")
          .style("width", w + "px")
          .style("height", w + "px")
          .style("position", "absolute");
  
      var svg = div.append("svg:svg")
          .attr("width", w)
          .attr("height", w)
          .append("svg:g")
          .attr("transform", "translate(" + rx + "," + ry + ")");
  
      svg.append("svg:path")
          .attr("class", "arc")
          .attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
          .on("mousedown", mousedown);
  
      d3.json("TASKS AND PHASES.json", function(classes) {
  
          var nodes = cluster.nodes(packages.root(classes)),
              links = packages.imports(nodes),
              splines = bundle(links);
  
          var path = svg.selectAll("path.link")
              .data(links)
              .enter().append("svg:path")
              .attr("class", function(d) {
                  return "link source-" + d.source.key + " target-" + d.target.key;
              })
              .attr("d", function(d, i) {
                  return line(splines[i]);
              });
  
          var groupData = svg.selectAll("g.group")
              .data(nodes.filter(function(d) {
                  return (d.key == 'Department' || d.key == 'Software' || d.key == 'Tasks' || d.key == 'Phases') &amp;&amp; d.children;
              }))
              .enter().append("group")
              .attr("class", "group");
  
          var groupArc = d3.svg.arc()
              .innerRadius(ry - 177)
              .outerRadius(ry - 157)
              .startAngle(function(d) {
                  return (findStartAngle(d.__data__.children) - 2) * pi / 180;
              })
              .endAngle(function(d) {
                  return (findEndAngle(d.__data__.children) + 2) * pi / 180
              });        
  
          svg.selectAll("g.arc")
              .data(groupData[0])
              .enter().append("svg:path")
              .attr("d", groupArc)
              .attr("class", "groupArc")
              .attr("id", function(d, i) {console.log(d.__data__.key); return d.__data__.key;})
              .style("fill", function(d, i) {return color(i);})
              .style("fill-opacity", 0.5)
              .each(function(d,i) {
  
                  var firstArcSection = /(^.+?)L/;
  
                  var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
  
                  newArc = newArc.replace(/,/g , " ");
  
                  svg.append("path")
                      .attr("class", "hiddenArcs")
                      .attr("id", "hidden"+d.__data__.key)
                      .attr("d", newArc)
                      .style("fill", "none");
              });
  
  
  
          svg.selectAll(".arcText")
              .data(groupData[0])
              .enter().append("text")
              .attr("class", "arcText")
              .attr("dy", 15)
              .append("textPath")
              .attr("startOffset","50%")
              .style("text-anchor","middle")
              .attr("xlink:href",function(d,i){return "#hidden" + d.__data__.key;})
              .text(function(d){return d.__data__.key;});    
  
          svg.selectAll("g.node")
              .data(nodes.filter(function(n) {
                  return !n.children;
              }))
              .enter().append("svg:g")
              .attr("class", "node")
              .attr("id", function(d) {
                  return "node-" + d.key;
              })
              .attr("transform", function(d) {
                  return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
              })
              .append("svg:text")
              .attr("dx", function(d) {
                  return d.x &lt; 180 ? 25 : -25;
              })
              .attr("dy", ".31em")
              .attr("text-anchor", function(d) {
                  return d.x &lt; 180 ? "start" : "end";
              })
              .attr("transform", function(d) {
                  return d.x &lt; 180 ? null : "rotate(180)";
              })
              .text(function(d) {
                  return d.key.replace(/_/g, ' ');
              })
              .on("mouseover", mouseover)
              .on("mouseout", mouseout);
  
          d3.select("input[type=range]").on("change", function() {
              line.tension(this.value / 100);
              path.attr("d", function(d, i) {
                  return line(splines[i]);
              });
          });
      });
  
      d3.select(window)
          .on("mousemove", mousemove)
          .on("mouseup", mouseup);
  
      function mouse(e) {
          return [e.pageX - rx, e.pageY - ry];
      }
  
      function mousedown() {
          m0 = mouse(d3.event);
          d3.event.preventDefault();
      }
  
      function mousemove() {
          if (m0) {
              var m1 = mouse(d3.event),
                  dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
              div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
          }
      }
  
      function mouseup() {
          if (m0) {
              var m1 = mouse(d3.event),
                  dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
  
              rotate += dm;
              if (rotate &gt; 360) rotate -= 360;
              else if (rotate &lt; 0) rotate += 360;
              m0 = null;
  
              div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
  
              svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
                  .selectAll("g.node text")
                  .attr("dx", function(d) {
                      return (d.x + rotate) % 360 &lt; 180 ? 25 : -25;
                  })
                  .attr("text-anchor", function(d) {
                      return (d.x + rotate) % 360 &lt; 180 ? "start" : "end";
                  })
                  .attr("transform", function(d) {
                      return (d.x + rotate) % 360 &lt; 180 ? null : "rotate(180)";
                  });
          }
      }
  
      function mouseover(d) {
          svg.selectAll("path.link.target-" + d.key)
              .classed("target", true)
              .each(updateNodes("source", true));
  
          svg.selectAll("path.link.source-" + d.key)
              .classed("source", true)
              .each(updateNodes("target", true));
      }
  
      function mouseout(d) {
          svg.selectAll("path.link.source-" + d.key)
              .classed("source", false)
              .each(updateNodes("target", false));
  
          svg.selectAll("path.link.target-" + d.key)
              .classed("target", false)
              .each(updateNodes("source", false));
      }
  
      function updateNodes(name, value) {
          return function(d) {
              if (value) this.parentNode.appendChild(this);
              svg.select("#node-" + d[name].key).classed(name, value);
          };
      }
  
      function cross(a, b) {
          return a[0] * b[1] - a[1] * b[0];
      }
  
      function dot(a, b) {
          return a[0] * b[0] + a[1] * b[1];
      }
  
      function findStartAngle(children) {
          var min = children[0].x;
          children.forEach(function(d) {
              if (d.x &lt; min)
                  min = d.x;
          });
          return min;
      }
  
      function findEndAngle(children) {
          var max = children[0].x;
          children.forEach(function(d) {
              if (d.x &gt; max)
                  max = d.x;
          });
          return max;
      }
  &lt;/script&gt;
  </code></pre>