0

Back in October 2012, this question was posed ( d3.js - how to automatically calculate arc lengths in radial dendrogram ) on how to generate arcs in a radial dendogram to denote groups. User "wceo" posted an elegant solution that works very well.

However, is it possible to change the color of each arc individually, rather than have them as the same color? Even trying...

var color = d3.scale.category20();

... and...

var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) { return (d.key=='400' || d.key == '500' || d.key == '600' || d.key == 'Current_Missions' || d.key == 'Future_Missions') && d.children; }))
.enter().append("group")
.attr("class", "group")
.attr("fill", function(d) { return color(d.group); });

... just returns each of the five arcs as the same color.

(I apologize for not responding to the original topic. I apparently don't have the required 50 reputation to do so.)

(Edited to include the entire sample code)

var w = 1800,
    h = 1600,
    rx = w / 2,
    ry = h / 2,
    m0,
    rotate = 0
    pi = Math.PI;

var splines = [];

var cluster = d3.layout.cluster()
    .size([360, ry - 400])
    .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(.85)
    .radius(function(d) { return d.y; })
    .angle(function(d) { return d.x / 180 * Math.PI; });

// Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
var div = d3.select("#bundle")
    .style("top", "50px")
    .style("left", "50px")    
    .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 + ")");

var color = d3.scale.ordinal();

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("missions.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 == '400' || d.key == '500' || d.key == '600' || d.key == 'Current_Missions' || d.key == 'Future_Missions') && d.children; }))
  .enter().append("group")
    .attr("class", "group");

  var groupArc = d3.svg.arc()
  .innerRadius(ry - 430)
  .outerRadius(ry - 400)
  .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")
  .style("fill-opacity", 0.25);

  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 < 180 ? 2 : -2; })
      .attr("dy", ".31em")
      .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
      .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
      .attr("color", function (d) { return (d.color) })
      .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 > 360) rotate -= 360;
    else if (rotate < 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 < 180 ? 2 : -2; })
        .attr("text-anchor", function(d) { return (d.x + rotate) % 360 < 180 ? "start" : "end"; })
        .attr("transform", function(d) { return (d.x + rotate) % 360 < 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 < min)
           min = d.x;
    });
    return min;
}

function findEndAngle(children) {
    var max = children[0].x;
    children.forEach(function(d) {
       if (d.x > max)
           max = d.x;
    });
    return max;
}

(function() {
  packages = {

    // Lazily construct the package hierarchy from class names.
    root: function(classes) {
      var map = {};

      function find(name, data) {
        var node = map[name], i;
        if (!node) {
          node = map[name] = data || {name: name, children: []};
          if (name.length) {
            node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
            node.parent.children.push(node);
            node.key = name.substring(i + 1);
          }
        }
        return node;
      }

      classes.forEach(function(d) {
        find(d.name, d);
      });

      return map[""];
    },

    // Return a list of imports for the given array of nodes.
    imports: function(nodes) {
      var map = {},
          imports = [];

      // Compute a map from name to node.
      nodes.forEach(function(d) {
        map[d.name] = d;
      });

      // For each import, construct a link from the source to target node.
      nodes.forEach(function(d) {
        if (d.imports) d.imports.forEach(function(i) {
          imports.push({source: map[d.name], target: map[i]});
        });
      });

      return imports;
    }

  };
})();
Community
  • 1
  • 1
Lokitez
  • 205
  • 1
  • 3
  • 11
  • Are you certain that d.group is defined? If it wasn't it would be passing "undefined" into the scale in all cases which would produce the same color. Stick in a breakpoint or do a console.log(d.group) inside the function to see what the value is. – Scott Cameron Aug 10 '13 at 06:17
  • I'm not entirely certain on how the groups are handled for this. For example, one of my main groups is "400" and all items under it are defined in the JSON by "name":"400.400_-_Flight_Projects_Directorate". Then the arcs are generated by using d.key == '400' it seems. But I'm not clear on how to reference the "400" group. – Lokitez Aug 10 '13 at 14:20

0 Answers0