Quantcast
Channel: User i alarmed alien - Stack Overflow
Viewing all articles
Browse latest Browse all 42

Answer by i alarmed alien for Edit D3 SVG's links position in a "radial" Chart

$
0
0

Here's how to fix your links:

First, change the rectangle positioning so that they are centred over where the circle would be in the standard link-node diagram. Rather than hard-coding values in the update function, I've configured the rectangle dimensions in an object:

var rect = {l: 60, w: 20};

Edit the rectangle attributes to use the values in rect, and centre the rectangle over the node by offsetting the x and y values by half the length and half the width of the rectangle respectively.

nodeEnter.append("rect")  .attr("width", rect.l)  // long dimension  .attr("height", rect.w) // short dimension  .attr("x", -rect.l/2)   // offset by half the long dimension  .attr("y", -rect.w/2)   // offset by half the short dimension  .style("fill", function(d) {    return d._children ? "lightsteelblue" : "red";  });

To fix the links, we need to change their start and end position by the same offset. Since both exiting and entering links use the position of the source node, the only links we need to worry about are the update selection:

link.transition()  .duration(duration)  .attr("d", diagonal);

diagonal takes an object of the form

{ source: { x: 123, y: 456 }, target: { x: 234, y: 567 } }

In the case of the update selection, the data items are already in this configuration, so calling diagonal(d) works correctly. Since we're altering the coordinates to include the offset for the rectangle, we have to break out the diagonal call. Note that the projection switches the x and y coordinates, so we have to alter the y values:

  link.transition()    .duration(duration)    .attr("d", function(d){      return diagonal({      source: {x: d.source.x, y: d.source.y + rect.l/2}, // add half the rectangle length      target: {x: d.target.x, y: d.target.y - rect.l/2}  // minus half the rectangle length    })  });

I also took the liberty of centering the text in the rectangles by removing the offset you'd added and setting the text anchor to 'middle'.

Here's the result:

var pubs ={"name": "AUT-1","children": [        {"name": "PUB-1","children": [                {"name": "AUT-11","children": [                    {"name": "AFF-111"},                    {"name": "AFF-112"}                ]},                {"name": "AUT-12","children": [                    {"name": "AFF-121"}                ]},                {"name": "AUT-13","children": [                    {"name": "AFF-131"},                    {"name": "AFF-132"}                ]},                {"name": "AUT-14","children": [                    {"name": "AFF-141"}                ]}            ]        },        {"name": "PUB-2","children": [                {"name": "AUT-21"},                {"name": "AUT-22"},                {"name": "AUT-23"},                {"name": "AUT-24"},                {"name": "AUT-25"},                {"name": "AUT-26"},                {"name": "AUT-27"},                {"name": "AUT-28","children":[                    {"name": "AFF-281"},                    {"name": "AFF-282"},                    {"name": "AFF-283"},                    {"name": "AFF-284"},                    {"name": "AFF-285"},                    {"name": "AFF-286"}                ]}            ]        },        {"name": "PUB-3"},        {"name": "PUB-4","children": [                {"name": "AUT-41"},                {"name": "AUT-42"},                {"name": "AUT-43","children": [                    {"name": "AFF-431"},                    {"name": "AFF-432"},                    {"name": "AFF-433"},                    {"name": "AFF-434","children":[                        {"name": "ADD-4341"},                        {"name": "ADD-4342"},                    ]}                ]},                {"name": "AUT-44"}            ]        },        {"name": "PUB-5","children": [                {"name": "AUT-51","children":[                    {"name": "AFF-511"},                    {"name": "AFF-512"},                    {"name": "AFF-513"},                    {"name": "AFF-514"},                    {"name": "AFF-515"},                    {"name": "AFF-516"}                ]},                {"name": "AUT-52"},                {"name": "AUT-53"},                {"name": "AUT-54"},                {"name": "AUT-55","children":[                    {"name": "AFF-551"},                    {"name": "AFF-552"},                    {"name": "AFF-553"},                    {"name": "AFF-554"}                ]},                {"name": "AUT-56"},                {"name": "AUT-57"},                {"name": "AUT-58"},                {"name": "AUT-59"},                {"name": "AUT-591"},                {"name": "AUT-592"},                {"name": "AUT-593"},                {"name": "AUT-594"},                {"name": "AUT-595"},                {"name": "AUT-596"}            ]        },        {"name": "PUB-6","children": [              {"name": "AUT-61","children":[                  {"name": "AFF-611"},                  {"name": "AFF-612"},                  {"name": "AFF-613"},                  {"name": "AFF-614","children":[                      {"name": "ADD-6141"},                      {"name": "ADD-6142"},                  ]}              ]},              {"name": "AUT-62"},              {"name": "AUT-63"},              {"name": "AUT-64"},              {"name": "AUT-65"},              {"name": "AUT-66"},              {"name": "AUT-67"},              {"name": "AUT-68"},              {"name": "AUT-69"}            ]        }    ]};var diameter = 800;var margin = {top: 20, right: 120, bottom: 20, left: 120},    width = diameter,    height = diameter;var i = 0,    duration = 350,    root;var tree = d3.layout.tree()    .size([360, diameter / 2 - 80])    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 1) / a.depth; });var diagonal = d3.svg.diagonal.radial()    .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });var svg = d3.select("body").append("svg")    .attr("width", width )    .attr("height", height )  .append("g")    .attr("transform", "translate("+ diameter / 2 +","+ diameter / 2 +")");var rect = {l: 60, w: 20}root = pubs;root.x0 = height / 2;root.y0 = 0;update(root);d3.select(self.frameElement).style("height", "800px");function update(source) {  // Compute the new tree layout.  var nodes = tree.nodes(root),      links = tree.links(nodes),      offset = nodes[0].x  // Update the nodes…  var node = svg.selectAll("g.node")      .data(nodes, function(d) { return d.id || (d.id = ++i); });  // Enter any new nodes at the parent's previous position.  var nodeEnter = node.enter().append("g")      .attr("class", "node")      .on("click", click);  nodeEnter.append("rect")      .attr("width", rect.l)      .attr("height", rect.w)      .attr("x", -rect.l/2)      .attr("y", -rect.w/2)      .style("fill", function(d) {        return d._children ? "lightsteelblue" : "red";      });  nodeEnter.append("text")      .attr("x", 0)      .attr("dy", ".35em")      .attr("text-anchor", "middle")      //.attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-"+ (d.name.length * 8.5)  +")"; })      .text(function(d) { return d.name; })      .style("fill-opacity", 1e-6);  // Transition nodes to their new position.  var nodeUpdate = node.transition()      .duration(duration)      .attr("transform", function(d) { return "rotate("+ (d.x - 90) +")translate("+ d.y +")"; })  // nodeUpdate.select("circle")  //     .attr("r", 4.5)  //     .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });  nodeUpdate.select("text")      .style("fill-opacity", 1)      .attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)"; });  // TODO: appropriate transform  var nodeExit = node.exit().transition()      .duration(duration)      //.attr("transform", function(d) { return "diagonal("+ source.y +","+ source.x +")"; })      .remove();  // nodeExit.select("circle")  //     .attr("r", 1e-6);  nodeExit.select("text")      .style("fill-opacity", 1e-6);  // Update the links…  var link = svg.selectAll("path.link")      .data(links, function(d) { return d.target.id; });  // Enter any new links at the parent's previous position.  link.enter().insert("path", "g")      .attr("class", "link")      .attr("d", function(d) {        var o = {x: source.x0, y: source.y0};        return diagonal({source: o, target: o});      });  // Transition links to their new position.  link.transition()      .duration(duration)      .attr("d", function(d){      return diagonal({      source: {x: d.source.x, y: d.source.y+rect.l/2},       target: {x: d.target.x, y: d.target.y-rect.l/2}      })  });  // Transition exiting nodes to the parent's new position.  link.exit().transition()      .duration(duration)      .attr("d", function(d) {        var o = {x: source.x, y: source.y};        return diagonal({source: o, target: o});      })      .remove();  // Stash the old positions for transition.  nodes.forEach(function(d) {    d.x0 = d.x;    d.y0 = d.y;  });}// Toggle children on click.function click(d) {  if (d.children) {    d._children = d.children;    d.children = null;  } else {    d.children = d._children;    d._children = null;  }  update(d);}// Collapse nodesfunction collapse(d) {  if (d.children) {      d._children = d.children;      d._children.forEach(collapse);      d.children = null;    }}
.node {  cursor: pointer;}.node circle {  fill: #fff;  stroke: steelblue;  stroke-width: 1.5px;}.node text {  font: 10px sans-serif;}.link {  fill: none;  stroke: #ccc;  stroke-width: 1.5px;}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>

I think that straight lines would look better on this visualisation - e.g.:

var pubs ={"name": "AUT-1","children": [        {"name": "PUB-1","children": [                {"name": "AUT-11","children": [                    {"name": "AFF-111"},                    {"name": "AFF-112"}                ]},                {"name": "AUT-12","children": [                    {"name": "AFF-121"}                ]},                {"name": "AUT-13","children": [                    {"name": "AFF-131"},                    {"name": "AFF-132"}                ]},                {"name": "AUT-14","children": [                    {"name": "AFF-141"}                ]}            ]        },        {"name": "PUB-2","children": [                {"name": "AUT-21"},                {"name": "AUT-22"},                {"name": "AUT-23"},                {"name": "AUT-24"},                {"name": "AUT-25"},                {"name": "AUT-26"},                {"name": "AUT-27"},                {"name": "AUT-28","children":[                    {"name": "AFF-281"},                    {"name": "AFF-282"},                    {"name": "AFF-283"},                    {"name": "AFF-284"},                    {"name": "AFF-285"},                    {"name": "AFF-286"}                ]}            ]        },        {"name": "PUB-3"},        {"name": "PUB-4","children": [                {"name": "AUT-41"},                {"name": "AUT-42"},                {"name": "AUT-43","children": [                    {"name": "AFF-431"},                    {"name": "AFF-432"},                    {"name": "AFF-433"},                    {"name": "AFF-434","children":[                        {"name": "ADD-4341"},                        {"name": "ADD-4342"},                    ]}                ]},                {"name": "AUT-44"}            ]        },        {"name": "PUB-5","children": [                {"name": "AUT-51","children":[                    {"name": "AFF-511"},                    {"name": "AFF-512"},                    {"name": "AFF-513"},                    {"name": "AFF-514"},                    {"name": "AFF-515"},                    {"name": "AFF-516"}                ]},                {"name": "AUT-52"},                {"name": "AUT-53"},                {"name": "AUT-54"},                {"name": "AUT-55","children":[                    {"name": "AFF-551"},                    {"name": "AFF-552"},                    {"name": "AFF-553"},                    {"name": "AFF-554"}                ]},                {"name": "AUT-56"},                {"name": "AUT-57"},                {"name": "AUT-58"},                {"name": "AUT-59"},                {"name": "AUT-591"},                {"name": "AUT-592"},                {"name": "AUT-593"},                {"name": "AUT-594"},                {"name": "AUT-595"},                {"name": "AUT-596"}            ]        },        {"name": "PUB-6","children": [              {"name": "AUT-61","children":[                  {"name": "AFF-611"},                  {"name": "AFF-612"},                  {"name": "AFF-613"},                  {"name": "AFF-614","children":[                      {"name": "ADD-6141"},                      {"name": "ADD-6142"},                  ]}              ]},              {"name": "AUT-62"},              {"name": "AUT-63"},              {"name": "AUT-64"},              {"name": "AUT-65"},              {"name": "AUT-66"},              {"name": "AUT-67"},              {"name": "AUT-68"},              {"name": "AUT-69"}            ]        }    ]};var diameter = 800;var margin = {top: 20, right: 120, bottom: 20, left: 120},    width = diameter,    height = diameter;var i = 0,    duration = 350,    root;var tree = d3.layout.tree()    .size([360, diameter / 2 - 80])    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 1) / a.depth; });var diagonal_extras = {  path: {      // diagonal line      direct: function(p){        return [ p.source, p.target ];      }      // this is also the default path in radial trees      , l_shape: function(p){        return [ p.source, { x: p.target.x, y: p.source.y }, p.target ];      }      , l_shape_2: function(p){        return [ p.source, { x: p.source.x, y: p.target.y }, p.target ];      }      , dogleg: function(p){        return [ p.source,        {   x: p.source.x,            y: (p.source.y + p.target.y) / 2        },        {   x: (p.source.x + p.target.x) / 2,            y: (p.source.y + p.target.y) / 2        },        {   x: p.target.x,            y: (p.source.y + p.target.y) / 2        }, p.target];      }      , dogleg_2: function(p){        return [ p.source,        {   x: (p.source.x + p.target.x) / 2,            y: p.source.y        },        {   x: (p.source.x + p.target.x) / 2,            y: (p.source.y + p.target.y) / 2        },        {   x: (p.source.x + p.target.x) / 2,            y: p.target.y        }, p.target];      }    }    , polar_obj_to_cart: function(pt){        var angle = pt.x / 180 * Math.PI;        return [pt.y * Math.cos(angle), pt.y * Math.sin(angle)];    }    , polar_coords_to_cart: function(xy){        var angle = xy[0] / 180 * Math.PI;        return [ xy[1] * Math.cos(angle), xy[1] * Math.sin(angle)];    }}diagonal_extras.right_angle = function() {      var projection = d3.svg.diagonal().projection()      , path_type = 'dogleg'      ;      function diagonal(d) {        return diagonal.path_maker( diagonal_extras.path[ diagonal.path_type() ](d) );      }      diagonal.path_maker = function( pathData ) {        return "M"+ pathData.map( projection ).join('');      };      diagonal.valid_path_types = function() {        return Object.keys( diagonal_extras.path );      };      diagonal.path_type = function(x) {        if (! arguments.length) { return path_type; }        if ( diagonal_extras.path[ x ] ) {          path_type = x;          return diagonal;        }        throw new Error( x +' is not a valid path type' );      };      diagonal.projection = function(x) {        if (!arguments.length) { return projection; }        projection = x;        return diagonal;      };      diagonal.path = function(x) {        if (!arguments.length) { return path; }        path = x;        return diagonal;      };      diagonal.draw = function(d) {        return diagonal(d);      };      return diagonal;}diagonal_extras.radial = function() {      var diagonal = diagonal_extras.right_angle()      , projection = function(pt){        return [ pt.x, pt.y ];      };      diagonal.path_type('direct');      diagonal.projection = function(x) {        if (!arguments.length) { return projection; }        projection = x;        return diagonal;      };      diagonal.path_maker = function( pathData ) {        var projected = pathData.map( function(x){ return projection(x); })        , pl = projected.length        , points        , prev_angle        ;        // direct link:        if ( 2 === pl ) {          return 'M'+ projected.map( function(x){ return diagonal_extras.polar_coords_to_cart(x); }).join('');        }        points = projected.map( function(obj){          return { angle: obj[0] / 180 * Math.PI, radius: obj[1] };        });        return "M"+ points.map( function(pt){          var str = '';          if ( prev_angle ) {            if ( prev_angle === pt.angle ) {              // draw a straight line              str = 'L';            }            else {              // draw an arc to the new radius and angle              str = 'A'+ pt.radius +','+ pt.radius              // x axis rotation+" 0 "              // large arc flag+" 0,"              // sweep+ ( pt.angle > prev_angle ? 1 : 0) +"";            }          }          prev_angle = pt.angle;          return str + pt.radius * Math.cos(pt.angle) +","+ pt.radius * Math.sin(pt.angle);        }).join('');      };      return diagonal;}var diagonal = diagonal_extras.radial()  .path_type('dogleg')  .projection(function(d){ return [ d.x - 90, d.y ]; });var svg = d3.select("body").append("svg")    .attr("width", width )    .attr("height", height )  .append("g")    .attr("transform", "translate("+ diameter / 2 +","+ diameter / 2 +")rotate(90)"); // note the altered rotationvar rect = {l: 60, w: 20}root = pubs;root.x0 = height / 2;root.y0 = 0;//root.children.forEach(collapse); // start with all children collapsedupdate(root);d3.select(self.frameElement).style("height", "800px");function update(source) {  // Compute the new tree layout.  var nodes = tree.nodes(root),      links = tree.links(nodes),      offset = nodes[0].x  // Normalise angles so that the root is horizontal  nodes.forEach(function(d) {    d.x -= offset;    if (d.x < 180) { d.x += 360 }  });  // Update the nodes…  var node = svg.selectAll("g.node")      .data(nodes, function(d) { return d.id || (d.id = ++i); });  // Enter any new nodes at the parent's previous position.  var nodeEnter = node.enter().append("g")      .attr("class", "node")      .on("click", click);  nodeEnter.append("rect")      .attr("width", rect.l)      .attr("height", rect.w)      .attr("x", -rect.l/2)      .attr("y", -rect.w/2)      .style("fill", function(d) {        return d._children ? "lightsteelblue" : "red";      });  nodeEnter.append("text")      .attr("x", 0)      .attr("dy", ".35em")      .attr("text-anchor", "middle")      .text(function(d) { return d.name; })      .style("fill-opacity", 1e-6);  // Transition nodes to their new position.  var nodeUpdate = node.transition()      .duration(duration)      .attr("transform", function(d) { return "rotate("+ (d.x - 90) +")translate("+ d.y +")"; })  nodeUpdate.select("text")      .style("fill-opacity", 1)      .attr("transform", function(d) {        return ( d.x < 270 || d.x > 450 )        ? 'rotate(180)'        : ''       });  // TODO: appropriate transform  var nodeExit = node.exit().transition()      .duration(duration)      //.attr("transform", function(d) { return "diagonal("+ source.y +","+ source.x +")"; })      .remove();  nodeExit.select("text")      .style("fill-opacity", 1e-6);  // Update the links…  var link = svg.selectAll("path.link")      .data(links, function(d) { return d.target.id; });  // Enter any new links at the parent's previous position.  link.enter().insert("path", "g")      .attr("class", "link")      .attr("d", function(d) {        var o = {x: source.x0, y: source.y0};        return diagonal({source: o, target: o});      });  // Transition links to their new position.  link.transition()      .duration(duration)      .attr("d", function(d){      return diagonal({      source: {x: d.source.x, y: d.source.y+rect.l/2},       target: {x: d.target.x, y: d.target.y-rect.l/2}      })  });  // Transition exiting nodes to the parent's new position.  link.exit().transition()      .duration(duration)      .attr("d", function(d) {        var o = {x: source.x, y: source.y};        return diagonal({source: o, target: o});      })      .remove();  // Stash the old positions for transition.  nodes.forEach(function(d) {    d.x0 = d.x;    d.y0 = d.y;  });}// Toggle children on click.function click(d) {  if (d.children) {    d._children = d.children;    d.children = null;  } else {    d.children = d._children;    d._children = null;  }  update(d);}// Collapse nodesfunction collapse(d) {  if (d.children) {      d._children = d.children;      d._children.forEach(collapse);      d.children = null;    }}
.node {  cursor: pointer;}.node text {  font: 10px sans-serif;}.link {  fill: none;  stroke: #ccc;  stroke-width: 1.5px;}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>

Viewing all articles
Browse latest Browse all 42

Trending Articles