4

I'm trying to group the circles in the force directed graph layout, so that i could add the path of semi-circle that would make the circle into two semi-circle clickable sides. I managed to make two sides semi-circle clickable with d3 works just like this fiddle. I seems can't wrap d3 concept how to group, and connect the links.

How can i group the circles and the path and make it works with force layout? What are the concepts that i failed to understand to make this work? What did i do wrong?

Two sides clickable Semi-circle code

var vis = d3.select("body").append("svg")
var pi = Math.PI;

var arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(70)
    .startAngle(0) //converting from degs to radians
    .endAngle(pi) //just radians

var canvas = vis.attr("width", "400").attr("height", "400").append("g");


 // Added height and width so arc is visible
        canvas.append("circle")
    .attr("r", "70px")
    .attr("fill","blue")
    .attr("cx",400/2)
    .attr("cy",400/2)
    .on("click", function(d){
        console.log("View");
    })
    .on("mouseover", function(){            d3.select(this).style("fill", "blue");
    console.log(d3.select(this.parentNode).select(".view").style("visibility","visible"));
    })
    .on("mouseout", function(){
            d3.select(this).style("fill", "blue");
        console.log(d3.select(this.parentNode).select(".view").style("visibility","hidden"));
     });

    canvas.append("path")
    .attr("d", arc)
    .attr("fill", "blue")
    .attr("transform", "translate(200,200)")
    .on("click",function(d){
         console.log("Expand");

    })
    .on("mouseover", function(){            d3.select(this).style("fill", "blue");
    console.log(d3.select(this.parentNode).select(".expand").style("visibility","visible"));

    })
    .on("mouseout", function(){
            d3.select(this).style("fill", "blue");
        console.log(d3.select(this.parentNode).select(".expand").style("visibility","hidden"));

     });

   canvas.append("text")
    .attr("dx", "190")
    .attr("dy","200")
    .attr("class","view")
      .text("VIEW")

    canvas.append("text")
    .attr("dx", "190")
    .attr("dy","200")
    .attr("class","expand")
      .text("Expand")

Force Layout that uses circle as node code, inspired by this stackoverflow question

This fiddle

var words = [{
  "group": "n",
  "word": "main node",
  "children": [{
    "group": "n",
    "name": "sub node 1"
  }, {
    "group": "n",
    "name": "sub node 2"
  }, {
    "group": "n",
    "name": "sub node 3"
  }, {
    "group": "v",
    "name": "sub node 4"
  }, {
    "group": "s",
    "name": "sub node 5"
  }, {
    "group": "s",
    "name": "sub node 6"
  }, {
    "group": "s",
    "name": "sub node 7"
  }, {
    "group": "s",
    "name": "sub node 8"
  }, {
    "group": "s",
    "name": "sub node 9"
  }, {
    "group": "s",
    "name": "sub node 10"
  }, {
    "group": "s",
    "name": "sub node 11"
  }, {
    "group": "r",
    "name": "sub node 12",
    "children": [{
      "group": "r",
      "name": "sub sub node 1"
    }, {
      "group": "r",
      "name": "sub sub node 2"
    }, {
      "group": "r",
      "name": "sub sub node 3"
    }]
  }]
}]


var w = 600,
  h = 600,
  radius = 30,
  node,
  link,
  root;

var pi = Math.PI;  
var arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(radius)
    .startAngle(0) //converting from degs to radians
    .endAngle(pi)  

var force = d3.layout.force()
  .on("tick", tick)
  .charge(function(d) {
    return -1000;
  })
  .linkDistance(function(d) {
    return d.target._children ? 200 : 120;
  })
  .size([w, h - 160]);

var svg = d3.select("#viz").append("svg")
  .attr("width", w)
  .attr("height", h);

root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();

function update() {
  var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);

  // Restart the force layout.
  force
    .nodes(nodes)
    .links(links)
    .start();

  // Update the links…
  link = svg.selectAll(".link")
    .data(links);

  // Enter any new links.
  link.enter().insert("svg:line", ".node")
    .attr("class", "link")
    .attr("x1", function(d) {
      return d.source.x;
    })
    .attr("y1", function(d) {
      return d.source.y;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    });

  // Exit any old links.
  link.exit().remove();

  // Update the nodes…
  node = svg.selectAll("circle.node")
    .data(nodes, function(d) {
      return d.name;
    })
    .style("fill", color);

  node.transition()
    .attr("r", radius);


  // Enter any new nodes.
  node.enter().append("svg:circle")
    .attr("class", "node")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    //.on("click", click)
    .on("click",function(){
          console.log("Click Main Circle")
    })
    //.call(force.drag);


  node.append("title")
    .text(function(d) {
      return d.name;
    });


  // Exit any old nodes.
  node.exit().remove();
}

function tick() {
  link.attr("x1", function(d) {
      return d.source.x;
    })
    .attr("y1", function(d) {
      return d.source.y;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    });

  node.attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
  if (d._children) {
    return "#95a5a6";
  } else {
    switch (d.group) {
      case 'r': //adverb
        return "#e74c3c";
        break;
      case 'n': //noun
        return "#3498db";
        break;
      case 'v': //verb
        return "#2ecc71";
        break;
      case 's': //adjective
        return "#e78229";
        break;
      default:
        return "#9b59b6";
    }
  }
}

// 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();
}

// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [],
    i = 0;

  function recurse(node) {
    if (node.children) node.size = node.children.reduce(function(p, v) {
      return p + recurse(v);
    }, 0);
    if (!node.id) node.id = ++i;
    nodes.push(node);
    return node.size;
  }

  root.size = recurse(root);
  return nodes;
}

I have tried to group the circles, by grouping the circles and adding the semi-circle path in the group, but the layout breaks.

 var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(30)
.startAngle(0) //converting from degs to radians
.endAngle(pi)

var g = node.enter().append("g")
  .attr("class", "node");

    g.append("svg:circle")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    //.on("click", click)
    .on("click",function(){
          console.log("Click Main Circle")
    })


   g.append("path")
   .attr("d", arc)
   .attr("fill", "blue")
   .attr("transform", "translate(200,200)")
   .on("click",function(d){
     console.log("path");
    })
Community
  • 1
  • 1
diehell
  • 739
  • 2
  • 9
  • 19

1 Answers1

2

Here is how you can fix your layout problem.

Instead of setting cx and cy to circle in the tick.

Do following:

  1. Make a group
  2. Add circle to above group (do not set cx/cy)
  3. Add path to the above group. (do not set transform)

In tick do following to transform both circle and path which is contained in it.

function tick() {
  link.attr("x1", function(d) {
      return d.source.x;
    })
    .attr("y1", function(d) {
      return d.source.y;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    });
  //transform for nodes.
  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")"
  })

}

Working code here

Hope this helps!

Cyril Cherian
  • 32,177
  • 7
  • 46
  • 55
  • 1
    I definitely does Cyril, Thank You so much! – diehell Feb 24 '16 at 09:24
  • Dear Cyril, i change the click event on one of the half, so that i expand and collapse the children, but it shows node.exit error. When the children collapse,expand should i remove the group, but why doesn't it work? I updated the code https://jsfiddle.net/fretwiz/1v1ece6v/ – diehell Feb 24 '16 at 09:37
  • 1
    Cool! you learnt the trick now :)..yeah you had to do `var onEnter = node.enter();` for exit fix – Cyril Cherian Feb 24 '16 at 10:10
  • would you mind have a look at another question, http://stackoverflow.com/questions/35617438/d3js-tree-as-family-tree-initial-data-doesnt-visualize-as-it-is , i knew i miss something but can't nail it down. – diehell Feb 25 '16 at 03:27