D3.v4 graph freezes when re-rendered - d3.js

I am porting my D3.v3 code to the latest D3.v4. I used to invoke my rendering simulation function additional times after my data changes (additional nodes, additional links, etc).
Now, if i invoke again my render function the graph freezes, and it does not adjust new nodes or links and dragging won't work either.
Following is the code in jsfiddle
https://jsfiddle.net/za0xpdc5/4/
Graph.prototype.render = function() {
this.simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(200, 200));
link = this.linksG
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("id", function(d) {
return d.id
});
node = this.nodesG
.selectAll("g")
.data(this.nodes)
.enter()
.append("g")
.attr("id", function(d) {return d.id;})
.append("circle")
.attr("id", function(d) {return "node_" + d.id})
.attr("r", 5)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
# ====================================
# <---- It appears the freeze occurs at this point
# during the 2nd execution of graph.render()
# ====================================
this.simulation.nodes(this.nodes).on("tick", this.tick);
this.simulation.force("link").links(this.links);
}

Related

D3 adding list of text to a node in forecDirected layout

I have a lot of success creating forceDirect layouts with a single line of text as a label for each node. Now I have a project where I need to add a list (multiple text) to each node.
I have been able to add multiple text to using the same model that works for adding a single label and a single to each node.
The label, rect, and multiline text are each added to separate but only the list of text is not "pinned" to the node.
var node = svg.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "nodes")
.selectAll("rect")
.data(data.nodes)
.enter()
.append("rect")
.style("width",function(d){
return setRectWidth(d)
})
.style("height",function(d){
return 30
})
.attr("fill",function(d){
return setBackground(d)
})
//.attr("x",0)
.attr("rx",4)
.attr("ry",4)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
var text = svg.append("g")
.attr("class", "text")
.selectAll("txt")
.data(data.nodes)
.enter()
.append("text")
.attr("x", 0)
.text(function(d){
return d.label
})
.style("text-anchor","middle")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var subtext = svg.append("g")
.attr("class", "subtext")
.selectAll("subtext")
.data(data.node_items)
.enter()
.append("text")
.attr("x", 0)
.text(function(d){
return d.label
})
.style("text-anchor","middle")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Here is the raw result
Note Subtext has no xy assigned
Here is what I am going for:
The tick function looks ok to me so I don't know what I am missing.
var ticked = function() {
node
.attr("x", function(d) { return d.x + setRectWidth(d)*-.5; })
.attr("y", function(d) { return d.y + setRectHeight(d)*-.5 });
link
.attr("d", function(d) {
var a = []
a.push({x: d.source.x,y:d.source.y});
a.push({x: d.target.x,y:d.target.y});
return line(a)
})
text
.attr("x", function(d) { return d.x })
.attr("y", function(d) { return d.y + setTextY(d) });
subtext
.attr("x", function(d) { return d.x })
.attr("y", function(d) { return d.y });
}
The solution I came up with was to add list times to each node as node items, then use the ticked function to iterate the list and re-assign the x, y positions based on the items index.
var ticked = function(){.....
nodeitems.attr("x", function (d) {
let new_x = d.fixed_x ? d.fixed_x : d.x;
return new_x - setRectWidth(d) * .4;
})
.attr("y", function (d, i) {
let new_y = d.fixed_y ? d.fixed_y : d.y;
return new_y + (i * row_height) + setTextY(d) + row_height + 5
});
You can see the working solution here - https://alqemist.github.io/EAGIR/erd/
Source code is here - look at erd folder
https://github.com/alQemist/EAGIR

d3 v4 force layout with drag,zoom and edge label

I am trying implement labels on links in d3 v4 force layout. Although I am following code from a b.lock, I face difficulty to achieve the labelling. I am probably missing some basics at svg.selectAll and svg.append .
svg = d3.select("#svgdiv").append('svg').attr('width',width).attr('height',height).style("border","1px solid black"),
width = +svg.attr("width"),
height = +svg.attr("height");
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "black")
.style("pointer-events", "all")
.call(d3.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
g=svg.append("g");
function zoomed() {
g.attr("transform", d3.event.transform);
}
link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.edge)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); })
.attr("id",function(d,i) {return 'edge'+i});
node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.node)
.enter().append("circle")
.attr("r", radius)
.attr("fill", function(d) { return color(d.group); })
.on("click", mouseClick(.2))
.on("dblclick", mouseDblClick)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Till here, the code is ok. But when I add the following code for labelling, it stops working.
edgepaths =svg.selectAll(".edgepath")
.data(graph.edge)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'green',
'stroke':'yellow',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
edgelabels =svg.selectAll(".edgelabel")
.data(graph.edge)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':0,
'font-size':10,
'fill':'white'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d,i){return 'label '+i});
function ticked() {
node
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
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; });
edgepaths.attr('d', function(d) {
var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
//console.log(d)
return path});
edgelabels.attr('transform',function(d,i){
if (d.target.x<d.source.x){
bbox = this.getBBox();
rx = bbox.x+bbox.width/2;
ry = bbox.y+bbox.height/2;
return 'rotate(180 '+rx+' '+ry+')';
}
else {
return 'rotate(0)';
}
});
}
please help.
thank you.
JSON data.
{
"edge":[
{"source":"source1","target":"target1","value":"1"},
{"source":"source2","target":"target2","value":"0"},
.
.
],
"node":[
{"group":0,"id":"source1"},
{"group":3,"id":"source2"},
.
.
]
}

How to add text to a force directed graph in D3.js

I've been using the network graph example here:
https://bl.ocks.org/mbostock/4062045
I am trying to add text elements onto the nodes but can't seem to get it to appear next to the nodes.
I tried to add the following lines in the function below but athat this does it to display the text on the top left of the screen - while i'd expect it to appear on the top left.
Not sure if anyone could help?
var textElements = svg.append("g")
.selectAll('text')
.data(graph.nodes)
.enter().append('text').attr('font-size', 15)
.attr('dx', 15)
.attr('dy', 4)
.text(function(d) { return d.id; });
Original:
d3.json("miserables.json", function(error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", 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(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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; });
}
});
Found this guy that solved this:
myText.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
https://plnkr.co/edit/UwQfPscQiOg87IEMYtET?p=preview

D3v4: missing text in circle for force directed graph [duplicate]

This question already has an answer here:
Add text label to d3 node in Force layout
(1 answer)
Closed 5 years ago.
I am working on a simple visualisation with d3 to draw a force directed graph. I developed from the code at https://bl.ocks.org/mbostock/ad70335eeef6d167bc36fd3c04378048 and I have also added a marker. However, I am struggling to draw a text under each node. The code is as follows:
var nodes_url = "https://api.myjson.com/bins/1dedy1";
var edges_url = "https://api.myjson.com/bins/74lzt";
var marker = d3.select("svg").append('defs')
.append('marker')
.attr("id", "Triangle")
.attr("refX", 12)
.attr("refY", 6)
.attr("markerUnits", 'userSpaceOnUse')
.attr("markerWidth", 12)
.attr("markerHeight", 18)
.attr("orient", 'auto')
.append('path')
.attr("d", 'M 0 0 12 6 0 12 3 6');
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().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
queue()
.defer(d3.json, nodes_url)
.defer(d3.json, edges_url)
.await(function(error, file1, file2) {createForceLayout(file1, file2);
});
function createForceLayout (nodes, links) {
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.style("stroke", "black")
.style("opacity", .5)
.style("stroke-width", "2px");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.append("circle")
.attr("r", 5)
.style("fill", "lightgray")
.style("stroke", "black")
.style("stroke-width", "1px")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
node.append("text")
.style("text-anchor", "middle")
.attr("y", 15)
.text(function(d) {return d.id});
d3.selectAll("line").attr("marker-end", "url(#Triangle)");
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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; });
}
}
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;
}
The output returns the text, however, it is not displayed. Any thoughts on what can I be doing wrong?
Thanks a lot!
Right now, node is an "enter" selection for the circles, and you cannot append text to circles.
Solution: break the node selection, and change the ticked function accordingly:
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("gregorMendel")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
var circles = node.append("circle")
.attr("r", 5)
.style("fill", "lightgray")
.style("stroke", "black")
.style("stroke-width", "1px")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var titles = node.append("title")
.text(function(d) {
return d.id;
});
var text = node.append("text")
.style("text-anchor", "middle")
.attr("y", 15)
.text(function(d) {
return d.id
});
Here is the demo:
var nodes_url = "https://api.myjson.com/bins/1dedy1";
var edges_url = "https://api.myjson.com/bins/74lzt";
var marker = d3.select("svg").append('defs')
.append('marker')
.attr("id", "Triangle")
.attr("refX", 12)
.attr("refY", 6)
.attr("markerUnits", 'userSpaceOnUse')
.attr("markerWidth", 12)
.attr("markerHeight", 18)
.attr("orient", 'auto')
.append('path')
.attr("d", 'M 0 0 12 6 0 12 3 6');
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().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.queue()
.defer(d3.json, nodes_url)
.defer(d3.json, edges_url)
.await(function(error, file1, file2) {
createForceLayout(file1, file2);
});
function createForceLayout(nodes, links) {
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.style("stroke", "black")
.style("opacity", .5)
.style("stroke-width", "2px");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("gregorMendel")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
var circles = node.append("circle")
.attr("r", 5)
.style("fill", "lightgray")
.style("stroke", "black")
.style("stroke-width", "1px")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var titles = node.append("title")
.text(function(d) {
return d.id;
});
var text = node.append("text")
.style("text-anchor", "middle")
.attr("y", 15)
.text(function(d) {
return d.id
});
d3.selectAll("line").attr("marker-end", "url(#Triangle)");
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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("transform", function(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 src="https://d3js.org/d3.v4.min.js"></script>
<svg width="300" height="200"></svg>

d3.js Unable to call drag on label

Here is a plunker modified from mbostock
I want to make the text labels drag-able and attach a line to the circle when dragged.
.call(drag) works on the dots but not the labels
label = container.append("g")
.attr("class", "label")
.selectAll(".label")
.data(dots)
.enter().append("text")
.text(function(d) {return d.x + d.y; })
.attr("x", function(d) {return d.x; })
.attr("y", function(d) {return d.y; })
.attr("text-anchor", "middle")
.call(drag)
Here's a JSFiddle I made to demonstrate draggable text labels in D3.js
https://jsfiddle.net/h1n6fuwr/
Essentially you want to define the following variables/functions:
const drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
}
function dragged(d) {
const elem = d3.select(this)
elem.attr('x', +elem.attr('x') + d3.event.dx)
elem.attr('y', +elem.attr('y') + d3.event.dy)
}
function dragended(d) {}
And then call .call(drag) on your text labels.
const labels = ['Drag Me1', 'Drag Me2', 'Drag Me3']
d3.select('svg')
.selectAll('text')
.data(labels)
.enter()
.append('text')
.text(d => d)
.attr('fill', 'green')
.attr('x', (d, i) => 10 + i*30)
.attr('y', (d, i) => 15 + i*30)
.call(drag)
Append a rect behind the text, then .call(drag) on your rect. To get a suitable rect, you can use text.getBBox().

Resources