RoastRabbit
RoastRabbit

Reputation: 39

Links never show up

I was working on one of my topic modeling project where I try to use D3.js to visualize the result. And sadly this is my first experience with D3.js

I tried to link the main topics to their subtopics, so I followed the tutorial about how to use "force simulation" in D3. And now the nodes looks very good, however, the link never shows up.

Compare to the tutorial, the only difference is in this project I have to fix x-axis since all topic was binding to a time slot.

Also, after calling "function restart()", my text in the small bubble disappeared.

Please give me some advice on this.

data= [
        {id: "Topic1", date: "2017-08-21", name: "Topic1", count: .4, subtopics: ["sub1", "sub2", "sub3", "sub5"]},
        {id: "Topic2", date: "2017-08-23", name: "Topic2", count: 1, subtopics: ["sub3", "sub6", "sub7", "sub8"]},
        {id: "Topic3", date: "2017-08-25", name: "Topic3",count: 2, subtopics: ["sub7", "sub9"]},
        {id: "Topic4", date: "2017-08-27", name: "Topic4", count: 2, subtopics: ["sub8"]},
        
        {id: "sub1",date:"2017-08-21", name:"sub1", count: .1, subtopics: []},
        {id: "sub2",date:"2017-08-22", name:"sub2", count: .2, subtopics: []},
        {id: "sub3",date:"2017-08-22", name:"sub3", count: .2, subtopics: []},       
        {id: "sub4",date:"2017-08-28", name:"sub4", count: .1, subtopics: []},
        {id: "sub5",date:"2017-08-20", name:"sub5", count: .2, subtopics: []},
        {id: "sub6",date:"2017-08-23", name:"sub6", count: .1, subtopics: []},
        {id: "sub7",date:"2017-08-24", name:"sub7", count: .3, subtopics: []},
        {id: "sub8",date:"2017-08-24", name:"sub8", count: .1, subtopics: []},
        {id: "sub9",date:"2017-08-25", name:"sub9", count: .1, subtopics: []},
        {id: "sub10",date:"2017-08-27", name:"sub10", count: .1, subtopics: []},
        {id: "sub11",date:"2017-08-29", name:"sub11", count: .1, subtopics: []},
        {id: "sub12",date:"2017-08-30", name:"sub12", count: .4, subtopics: []},        
]


var vis_node = [];
var vis_link = [];
var r = d3.scaleSqrt()
        .domain([0, d3.max(data, function (d) {
            return d.count;
        })])
        .range([0, 65]);


var margin = {top: 50, right: 20, bottom: 100, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

    
    
//map elements to time
var parseTime = d3.timeParse("%Y-%m-%d");

data.forEach(function(d) {
      d.date = parseTime(d.date);
      d.close = +d.close;
});


//separete big topic and subtopics and setup the visualization data
var parents_node = data.filter(function(d){return d.subtopics.length != 0;});
var child_node = data.filter(function(d){return d.subtopics.length == 0;});
vis_node = data;  

//link all the big topic with common subtopic
for(i = 0;i<data.length;i++){
    var tmps = data[i].subtopics
    for(j = 0; j < tmps.length; j++){
        for(k = i+1; k < data.length;k++){
            if(data[k].subtopics.includes(tmps[j])){
                vis_link.push({source: data[i], target: data[k]})
                continue;
            }                
        }
    }
}

//link all the big topic with its subtopic
for(i = 0;i<parents_node.length;i++){
    for(j = 0;j<child_node.length;j++){
        if(parents_node[i].subtopics.includes(child_node[j].name)){
            vis_link.push({source: parents_node[i], target: child_node[j]});
        }        
    }   
}

//setup a force field for the d3
var simulation = d3.forceSimulation()
            .force("charge", d3.forceManyBody().strength(-700).distanceMin(100).distanceMax(280)) 
            .force("link", d3.forceLink().id(function(d) { return d.index })) 
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("y", d3.forceY(0.0001))
            .force("x", d3.forceX(0.0001))
    
    
var svg = d3.select('body').append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom),
g = svg.append('g')
    .attr('transform','translate(' + margin.left + ',' + margin.top + ')');


var formatNumber = d3.format('');
var x = d3.scaleTime()
.range([0, width]);
x.domain(d3.extent(data, function(d) { return d.date; }));



//begin to render the circle
svg.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x)
              .tickFormat(d3.timeFormat("%Y-%m-%d")))


var node = g.selectAll('.node')
  .data(vis_node)
  .enter().append('g')
    .attr("class", "node");
    
node.call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

node.append('circle')
    .attr('r', function(d) {return Math.max(16, r(d.count)); })
    .style("stroke", function(d){
      if(d.subtopics.length == 0){
          return "blue"
      }  
      else{
          return "pink"          
      }       
    })
    .style("fill", "transparent");      
  
 
node.append('text')
    .text(function(d){return d.name})
    .attr("text-anchor", "middle")
    .style('fill', function(d){
      if(d.subtopics.length == 0){
          return "darkblue"
      }  
      else{
          return "darkred"          
      }       
    })
    .style('font-size','20px')
    .attr("pointer-events", "none");

node.on("click", function(d){
    var subs = d.subtopics
    child_node.forEach(function(d){
        if(subs.includes(d.name)){
            if(vis_node.includes(d)){
                var index = vis_node.indexOf(d)
                vis_node.splice(index, 1)
            }
            else{
                vis_node.push(d);               
            }
        }        
    })
    restart();
});


//begin to render a link
var link = g.selectAll('.link')
        .data(vis_link)
        .enter().append('g')
        .attr('class','link')        
link.append('line')
    .attr("stroke","black")

   


//when click on the big topic call restart function to redraw everything   
function restart(){
    vis_node.forEach(function(d){console.log(d.name)})
    node = node.data(vis_node)
    node.exit().remove()
    node = node.enter().append('circle')
        .attr('r', function(d) {return Math.max(16, r(d.count)); })
        .style("stroke", function(d){
          if(d.subtopics.length == 0){
              return "blue"
          }  
          else{
              return "pink"          
          }       
        })
        .style("fill", "transparent")
        .merge(node);     
 
    node.call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended))
         
    node.append('text')
        .text(function(d){return d.name})
        .attr("text-anchor", "middle")
        .style('fill', function(d){
              if(d.subtopics.length == 0){
                  return "darkblue"
              }  
              else{
                  return "darkred"          
              }       
            })
        .style('font-size','20px')
        .attr("pointer-events", "none"); 
    simulation.nodes(vis_node);
    simulation.alphaTarget(0.3).restart();
    
}


//I think something wrong here
var ticked = function() {
    node.attr("transform", function (d) {
        return "translate(" + x(d.date) + "," + d.y + ")";    
    })  
    link.attr("x1", function (d) {return x(d.source.date); })
        .attr("y1", function (d) {return d.source.y;})
        .attr("x2", function (d) {return x(d.target.date)})
        .attr("y2", function (d) {return d.target.y;});
}         


//add link and node to the force field
simulation.force("link").links(vis_link);
simulation.nodes(vis_node);
simulation.on("tick", ticked);

//drag related functions
function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
}
        
function dragged(d) {
        d.fy = d3.event.y;
}
        
function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
    <title>Topic Explorer</title>
    <base href="/">
    <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
    <link rel="stylesheet" href="./styles/simple-style.css">

   
    
</head>

<body style="margin:10px 0">

</div>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="./scripts/test.js"></script>


</body>
</html>

Upvotes: 1

Views: 39

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

Since your link selection is a selection of groups...

var link = g.selectAll('.link')
  .data(vis_link)
  .enter().append('g')
  .attr('class', 'link');

... you're applying the x1, x2, y1 and y2 attributes to the groups, not to the lines.

An easy solution is just naming another selection, just for the lines:

var line = link.append('line')
  .attr("stroke", "black");

Here is your modified code:

data = [{
    id: "Topic1",
    date: "2017-08-21",
    name: "Topic1",
    count: .4,
    subtopics: ["sub1", "sub2", "sub3", "sub5"]
  },
  {
    id: "Topic2",
    date: "2017-08-23",
    name: "Topic2",
    count: 1,
    subtopics: ["sub3", "sub6", "sub7", "sub8"]
  },
  {
    id: "Topic3",
    date: "2017-08-25",
    name: "Topic3",
    count: 2,
    subtopics: ["sub7", "sub9"]
  },
  {
    id: "Topic4",
    date: "2017-08-27",
    name: "Topic4",
    count: 2,
    subtopics: ["sub8"]
  },

  {
    id: "sub1",
    date: "2017-08-21",
    name: "sub1",
    count: .1,
    subtopics: []
  },
  {
    id: "sub2",
    date: "2017-08-22",
    name: "sub2",
    count: .2,
    subtopics: []
  },
  {
    id: "sub3",
    date: "2017-08-22",
    name: "sub3",
    count: .2,
    subtopics: []
  },
  {
    id: "sub4",
    date: "2017-08-28",
    name: "sub4",
    count: .1,
    subtopics: []
  },
  {
    id: "sub5",
    date: "2017-08-20",
    name: "sub5",
    count: .2,
    subtopics: []
  },
  {
    id: "sub6",
    date: "2017-08-23",
    name: "sub6",
    count: .1,
    subtopics: []
  },
  {
    id: "sub7",
    date: "2017-08-24",
    name: "sub7",
    count: .3,
    subtopics: []
  },
  {
    id: "sub8",
    date: "2017-08-24",
    name: "sub8",
    count: .1,
    subtopics: []
  },
  {
    id: "sub9",
    date: "2017-08-25",
    name: "sub9",
    count: .1,
    subtopics: []
  },
  {
    id: "sub10",
    date: "2017-08-27",
    name: "sub10",
    count: .1,
    subtopics: []
  },
  {
    id: "sub11",
    date: "2017-08-29",
    name: "sub11",
    count: .1,
    subtopics: []
  },
  {
    id: "sub12",
    date: "2017-08-30",
    name: "sub12",
    count: .4,
    subtopics: []
  },
]


var vis_node = [];
var vis_link = [];
var r = d3.scaleSqrt()
  .domain([0, d3.max(data, function(d) {
    return d.count;
  })])
  .range([0, 65]);


var margin = {
    top: 50,
    right: 20,
    bottom: 100,
    left: 50
  },
  width = 960 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;



//map elements to time
var parseTime = d3.timeParse("%Y-%m-%d");

data.forEach(function(d) {
  d.date = parseTime(d.date);
  d.close = +d.close;
});


//separete big topic and subtopics and setup the visualization data
var parents_node = data.filter(function(d) {
  return d.subtopics.length != 0;
});
var child_node = data.filter(function(d) {
  return d.subtopics.length == 0;
});
vis_node = data;

//link all the big topic with common subtopic
for (i = 0; i < data.length; i++) {
  var tmps = data[i].subtopics
  for (j = 0; j < tmps.length; j++) {
    for (k = i + 1; k < data.length; k++) {
      if (data[k].subtopics.includes(tmps[j])) {
        vis_link.push({
          source: data[i],
          target: data[k]
        })
        continue;
      }
    }
  }
}

//link all the big topic with its subtopic
for (i = 0; i < parents_node.length; i++) {
  for (j = 0; j < child_node.length; j++) {
    if (parents_node[i].subtopics.includes(child_node[j].name)) {
      vis_link.push({
        source: parents_node[i],
        target: child_node[j]
      });
    }
  }
}

//setup a force field for the d3
var simulation = d3.forceSimulation()
  .force("charge", d3.forceManyBody().strength(-700).distanceMin(100).distanceMax(280))
  .force("link", d3.forceLink().id(function(d) {
    return d.index
  }))
  .force("center", d3.forceCenter(width / 2, height / 2))
  .force("y", d3.forceY(0.0001))
  .force("x", d3.forceX(0.0001))


var svg = d3.select('body').append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom),
  g = svg.append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');


var formatNumber = d3.format('');
var x = d3.scaleTime()
  .range([0, width]);
x.domain(d3.extent(data, function(d) {
  return d.date;
}));



//begin to render the circle
svg.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x)
    .tickFormat(d3.timeFormat("%Y-%m-%d")))


var node = g.selectAll('.node')
  .data(vis_node)
  .enter().append('g')
  .attr("class", "node");

node.call(d3.drag()
  .on("start", dragstarted)
  .on("drag", dragged)
  .on("end", dragended));

node.append('circle')
  .attr('r', function(d) {
    return Math.max(16, r(d.count));
  })
  .style("stroke", function(d) {
    if (d.subtopics.length == 0) {
      return "blue"
    } else {
      return "pink"
    }
  })
  .style("fill", "transparent");


node.append('text')
  .text(function(d) {
    return d.name
  })
  .attr("text-anchor", "middle")
  .style('fill', function(d) {
    if (d.subtopics.length == 0) {
      return "darkblue"
    } else {
      return "darkred"
    }
  })
  .style('font-size', '20px')
  .attr("pointer-events", "none");

node.on("click", function(d) {
  var subs = d.subtopics
  child_node.forEach(function(d) {
    if (subs.includes(d.name)) {
      if (vis_node.includes(d)) {
        var index = vis_node.indexOf(d)
        vis_node.splice(index, 1)
      } else {
        vis_node.push(d);
      }
    }
  })
  restart();
});


//begin to render a link
var link = g.selectAll('.link')
  .data(vis_link)
  .enter().append('g')
  .attr('class', 'link')
var line = link.append('line')
  .attr("stroke", "black")




//when click on the big topic call restart function to redraw everything   
function restart() {
  vis_node.forEach(function(d) {
    console.log(d.name)
  })
  node = node.data(vis_node)
  node.exit().remove()
  node = node.enter().append('circle')
    .attr('r', function(d) {
      return Math.max(16, r(d.count));
    })
    .style("stroke", function(d) {
      if (d.subtopics.length == 0) {
        return "blue"
      } else {
        return "pink"
      }
    })
    .style("fill", "transparent")
    .merge(node);

  node.call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended))

  node.append('text')
    .text(function(d) {
      return d.name
    })
    .attr("text-anchor", "middle")
    .style('fill', function(d) {
      if (d.subtopics.length == 0) {
        return "darkblue"
      } else {
        return "darkred"
      }
    })
    .style('font-size', '20px')
    .attr("pointer-events", "none");
  simulation.nodes(vis_node);
  simulation.alphaTarget(0.3).restart();

}


//I think something wrong here
var ticked = function() {
  node.attr("transform", function(d) {
    return "translate(" + x(d.date) + "," + d.y + ")";
  })
  line.attr("x1", function(d) {
      return x(d.source.date);
    })
    .attr("y1", function(d) {
      return d.source.y;
    })
    .attr("x2", function(d) {
      return x(d.target.date)
    })
    .attr("y2", function(d) {
      return d.target.y;
    });
}


//add link and node to the force field
simulation.force("link").links(vis_link);
simulation.nodes(vis_node);
simulation.on("tick", ticked);

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

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

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<!DOCTYPE html>
<meta charset="utf-8">

<head>
  <title>Topic Explorer</title>
  <base href="/">
  <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  <link rel="stylesheet" href="./styles/simple-style.css">



</head>

<body style="margin:10px 0">

  </div>

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="./scripts/test.js"></script>


</body>

</html>

Upvotes: 1

Related Questions