Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.forcesimulation() link distance

I have looked around on stack for varying link distance and it would seem that in order to vary the link distance you need to implement a function and then pass that to dynamically allocate link distance as such:

function linkDistance(d) {
    return d.distance;
}

which then I thought I would be able to pass to the svg but instead return error of function not existing linkdistance or distance

var link = svg.selectAll(".link")
  .data(bilinks)    
  .enter().append("path")
  .style("stroke", "#6b7071")    //gunmetal grey link
  .attr("class", "link")
  .linkDistance(linkDistance)
  .attr("fill", "none")

using the .linkDistance from the documentation: https://github.com/d3/d3/blob/master/API.md#forces-d3-force Ideally I would like to modify parameters such as link distance, charge, force, and link color using data much like I have done with the color of the dots however, I believe I am lacking knowledge on how to properly do this. for example in the last line of code if I was to change from:to

.style("stroke", "#6b7071")    //gunmetal grey link

.style("stroke", function(d) { return color(d.group);})

the link color is one color vs the 39 colors I would have expected based on the group. Further more, I have also tried

 var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().distance(function(d) {return d.distance}).strength(0.1))
    .force("charge", d3.forceManyBody(30))
    .force("center", d3.forceCenter(width / 2, height / 2));

UPDATE: I unsured that the data for length was in the array for links and bilinks so that I could reference the distance however cant seem to use the value out of the array however console.log shows it being stored properly

CODE

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.node {
  stroke: #fff;
  stroke-width: 1.5px;
}



</style>
<svg width="15000" height="15000"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

//change background color to black
backgroundColor = d3.rgb('#000000')
d3.select("body").style("background-color", backgroundColor)

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

//var color = d3.scaleOrdinal(d3.schemeCategory20);

 var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().distance(500).strength(0.1))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));


d3.json("hc7data.json", function(error, graph) {
  if (error) throw error;


  var nodes = graph.nodes,
      nodeById = d3.map(nodes, function(d) { return d.id; }),
      links = graph.links,
      bilinks = [];


//get graphics to make color scale us scaleOrdinal if every color chosen
  var color = d3.scaleOrdinal()  
      .domain([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40])
      .range(["#af1f45", "#be4f5e","#cd767c","#dc9d9e","#ecc9c8","#fbdbe9","#f7bbd5","#f49ac1","#f179ae","#ef509c",
      "#e3d4e4","#cdb1cf","#9a5699","#b990ba", "#a973a9","#d6eaf3","#b0daeb","#8acce4","#5ebfde","#00a5db","#6dbe46","#e0efd4",
      "#c3e0ae","#a7d48b","#8cc866","#fff2d1","#ffe8a8","#ffdf80","#ffd751","#fecf07","#fee1c9","#fcc79c","#faae74","#f69d58",
      "#f7964a","#fde3d9","#fcccbc","#f58870","#f9b4a0","#f79e87"]);


  links.forEach(function(link) {
    var s = link.source = nodeById.get(link.source),
        t = link.target = nodeById.get(link.target),
        i = {}, // intermediate node
        linkDist = link.distance;
    nodes.push(i);
    //console.log(linkDist);
    links.push({source: s, target: i, linkDist:linkDist}, {source: i, target: t, linkDist:linkDist });
    bilinks.push([s, i, t,linkDist]);
  });

  var link = svg.selectAll(".link")
    .data(bilinks)  
    .enter().append("path")
    .style("stroke", "#6b7071")    //gunmetal grey
    .attr("class", "link")
    .attr("fill", "none")



  var node = svg.selectAll(".node")
    .data(nodes.filter(function(d) { return d.id; }))
    .enter().append("circle")
      .attr("class", "node")
      //change circle size according to new function
      .attr("r", function(d) {return d.size})
      .attr("fill", function(d) { return color(d.group); })
      .style("stroke", "#000000")
      //.style("stroke", function(d) { return color(d.group); })
      .call(d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended));

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

  simulation
      .nodes(nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(links);

  function ticked() {
    link.attr("d", positionLink);
    node.attr("transform", positionNode);
  }
});




function positionLink(d) {
  return "M" + d[0].x + "," + d[0].y
       + "S" + d[1].x + "," + d[1].y
       + " " + d[2].x + "," + d[2].y;
}

function positionNode(d) {
  return "translate(" + d.x + "," + d.y + ")";
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x, d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x, d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null, d.fy = null;
}



</script>

sample of json doc

{
  "nodes": [
  {
    "id": "test1",
    "group": 1,
    "size": 10
  },
  {
    "id": "test2",
    "group": 1,
    "size": 10
  } 
  ],
  "links": [
  {
    "source": "test1",
    "target": "test2",
    "value": 1,
    "distance": 5
  },
  {
    "source": "test2",
    "target": "test1",
    "value": 1,
    "distance": 5
  } 
  ]
}
like image 605
Tyler Cowan Avatar asked Nov 29 '16 17:11

Tyler Cowan


2 Answers

I believe you are almost there. Using a custom function like function(d) {return d.distance} is the correct approach. However, you do not need to push extra information in the links array, because the links already include the properties of the JSON file.

As you mentioned, the simulation can be declared to use the distance property as follows:

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().distance(function(d) {return d.distance;}).strength(0.1))

and it is not necessary to push extra information to the links array, so you can remove this line:

links.push({source: s, target: i, linkDist:linkDist}, {source: i, target: t, linkDist:linkDist });

I created a JSFiddle with some modified JSON to show the results here. I added an extra link to another node with a shorter distance to show the effect.

like image 128
jobB Avatar answered Oct 23 '22 15:10

jobB


You certainly can have link distance vary based on the link. It just needs to be set in a slightly different spot. In your case, you are setting link distance to 500. Instead you would want to do something like this:

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().distance(linkDistance).strength(0.1))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

function linkDistance(d) {
    return d.distance;
}

https://github.com/d3/d3-force/blob/master/README.md#link_distance

like image 30
Jamil R Khan Avatar answered Oct 23 '22 13:10

Jamil R Khan