I'm trying to make a force directed graph, where nodes and links are added and removed as needed. However, while the chart correctly updates with added/removed links and nodes, all the old nodes are still visible.
Here is the update function. I've tried various tutorials, re-arranged the code, double checked the data being update is correct (i.e. this.dataNodes is being mutated, but replacing the object completely doesn't work either), etc. Honestly don't know what I should be looking for anymore.
// ADDING LINKS
this.link = this.linkGroup.selectAll("path")
.data(this.dataLinks, (link) => {
return link.target.id + link.source.id
});
this.link.exit().remove();
const linkEnter = this.link.enter()
.append("path")
.attr("stroke-width", 2)
.style("stroke", "#ccc")
.style('marker-start', (d) => d.sync ? 'url(#start-arrow)' : '')
.style('marker-end', (d) => 'url(#end-arrow)');
this.link = linkEnter.merge(this.link);
// ADDING NODES
this.node = this.nodeGroup.selectAll(".nodes")
.data(this.dataNodes, function (node) { return node.id });
this.node.exit().remove();
const nodeEnter = this.node.enter()
.append("g")
.call(this.dragAndDrop);
// Main circle
nodeEnter.append("circle")
.attr("r", 10)
.attr("fill", "grey")
// ADDING CHARACTER NAMES
nodeEnter.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function (d) {return d.title;});
this.node = nodeEnter.merge(this.node);
this.simulation.nodes(this.dataNodes).on("tick", this.tickActions );
this.simulation.force('link').links(this.dataLinks);
this.simulation.alphaTarget(1).restart();
EDIT:
This code is called when the force graph is first created. this.updateSimulation is the function above and renders with no problems. Calling it again, all previously created nodes remain in the graph.
this.svg = d3.select('#relationship-chart')
.append('svg')
.attr('width', this.width)
.attr('height', this.height);
// GROUPS
this.linkGroup = this.svg.append("g").attr("class", "links");
this.nodeGroup = this.svg.append("g").attr("class", "nodes");
// MAIN SIMULATION
this.link_force = d3.forceLink()
.id(function(d) { return d.id; })
.distance(100);
this.simulation = d3.forceSimulation()
.force("link", this.link_force)
.force("charge", d3.forceManyBody().strength(-200))
.force('center', d3.forceCenter(this.width / 2, this.height / 2))
//.force('collide', d3.forceCollide(25))
.force("x", d3.forceX())
.force("y", d3.forceY())
.alphaTarget(1);
// MISC DEFINTIONS
this.dragAndDrop = d3.drag()
.on("start", this.dragstarted)
.on("drag", this.dragged)
.on("end", this.dragended);
// ADDING ARROWS
this.svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 7)
.attr('markerWidth', 4)
.attr('markerHeight', 4)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#ccc');
this.svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 1)
.attr('markerWidth', 4)
.attr('markerHeight', 4)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#ccc');
this.updateSimulation();
Turns out I was selecting the parent elements class and not the children. I added a class to the nodes I created and this cleared up the problem.
Before:
this.node = this.nodeGroup.selectAll(".nodes")
.data(this.dataNodes, function (node) { return node.id });
this.node.exit().remove();
const nodeEnter = this.node.enter()
.append("g")
.call(this.dragAndDrop);
After:
this.node = this.nodeGroup.selectAll(".onenode")
.data(this.dataNodes, (node) => { return node.id });
this.node.exit().remove();
const nodeEnter = this.node.enter()
.append("g")
.attr("class", "onenode")
.call(this.dragAndDrop);
Related
I'm trying to move from visjs to d3js cause visjs always redraw the same data in a different way. The problem I faced is that d3js draw my nodes too close to each other, so labels overlay each others(see screenshot).
D3js is not an easy tool for beginners, but I'm sure there is some way to fix this problem. Please help to solve it.
const links = edges.map((edge) => ({source: edge.from, target: edge.to}));
function getNodeColor(node) {
return node.color.background;
}
const width = 1000;
const height = 800;
const svg = d3.select('svg');
svg.selectAll('*').remove();
svg.attr('width', width).attr('height', height);
// simulation setup with all forces
const linkForce = d3
.forceLink()
.id(function (link) {
return (link as NetworkNode).id;
})
.strength(function (link) {
return 1;
});
const simulation = d3
.forceSimulation()
.force('link', linkForce)
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width / 2, height / 2));
const linkElements = svg
.append('g')
.attr('class', 'links')
.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke-width', 1)
.attr('stroke', 'rgba(50, 50, 50, 0.2)');
const nodeElements = svg
.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('r', 10)
.attr('fill', getNodeColor);
const textElements = svg
.append('g')
.attr('class', 'texts')
.selectAll('text')
.data(nodes)
.enter()
.append('text')
.text(function (node) {
return node.label;
})
.attr('font-size', 15)
.attr('dx', 15)
.attr('dy', 4);
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function (node) {
return node.x;
})
.attr('cy', function (node) {
return node.y;
});
textElements
.attr('x', function (node) {
return node.x;
})
.attr('y', function (node) {
return node.y;
});
linkElements
.attr('x1', function (link) {
return (link as d3.SimulationLinkDatum<any>).source.x;
})
.attr('y1', function (link) {
return (link as d3.SimulationLinkDatum<any>).source.y;
})
.attr('x2', function (link) {
return (link as d3.SimulationLinkDatum<any>).target.x;
})
.attr('y2', function (link) {
return (link as d3.SimulationLinkDatum<any>).target.y;
});
});
// #ts-ignore
simulation.force('link').links(links);
I have implemented d3-zoom by following this brief tutorial.
I'm using https://d3js.org/d3.v5.min.js. This is my first project with d3.
My goal is to have a kind of floor plan showing booth tables at a venue. Similar to the tutorial, I've drawn shape elements from an array. In my case I've entered an array of booth information into a grid of elements.
The zoom functionality works just fine, except when my cursor is over the border or fill of one of my rectangles, or on the text of a element. If the point of my cursor is touching any of these elements, the zooming behavior stops working.
Try to zoom with the mousewheel with your cursor in blank space versus touching a shape or text.
I've tried to fit a console.log in somewhere to see what's not getting passed in the event, but have had trouble even finding where I can get the event argument.
Any help greatly appreciated! Here is my code:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
Try creating the rect in the end such that the DOM looks like this:
<svg>
<g></g>
<rect></rect>
</svg>
Since the zoom function is attached to the large rectangle, creating the smaller boxes above it prevents a zoom event from propagating to the large rectangle below them. It works for the boxes with a fill: none; since it behaves like a hollow box.
Try modifying the code to something like:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);
After dynamically updating the data for a force simulation in d3, the node's g element is initialized in a different place than where the links point to, and they do not move in sync when I drag them.
Here is what the graph looks like after adding a node.
I followed this tutorial for the update pattern: https://bl.ocks.org/mbostock/1095795.
I create my links and nodes like so:
// build the arrow
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // different link/path types can be defined here
.enter().append("svg:marker") // this section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// create the link drawing
var link = svg.append("g")
.attr("class", "links")
.attr("marker-end", "url(#end)")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", "4px");
// node is a group element
var node = svg.selectAll(".node")
.data(graph.nodes, function (d) { return d.id; })
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// add circle to the node
node
.append("circle")
.attr("r", function (d) { return d.id == undefined ? 0 : d.width / 2 + 2 * padding; })
.attr("fill", function (d) { return color(d.type); })
.attr('stroke', '#000000')
.attr('stroke-width', '3');
// add the text to the group
node
.append('text')
.text(node => node.type)
.attr('font-size', 15)
.attr('font-family', fontFamily)
.attr('dx', function (d) { return -d.width / 2 })
.attr('dy', 4);
and update the graph when the node and link data is changed like so:
// update graph for nodes
node = node.data(graph.nodes, function (d) { return d.id; });
node.exit().remove();
node = node.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.merge(node);
node
.append("circle")
.attr("r", function (d) { return d.id == undefined ? 0 : d.width / 2 + 2 * padding; })
.attr("fill", function (d) { return color(d.type); })
.attr('stroke', '#000000')
.attr('stroke-width', '3');
console.log(graph.nodes);
// add the text to the group
node
.append('text')
.text(node => node.type)
.attr('font-size', 15)
.attr('font-family', fontFamily)
.attr('dx', function (d) { return -d.width / 2 })
.attr('dy', 4);
// update links
link = link.data(graph.links);
link.exit().remove();
link = link.enter()
.append("line")
.attr("stroke-width", "4px")
.merge(link);
// restart simulation
simulation.nodes(graph.nodes);
simulation.force("link").links(graph.links);
simulation.alpha(0.01);
simulation.restart();
I am creating a sankey diagram using D3. I am trying to redraw the diagram with additional node and link and using transition to animate the previous diagram to the new diagram. I was able to add in new node and link but the old nodes and links did not change position. Since the new node and link could be added at any place within the diagram, I do not want to clear and redraw the entire svg, but use transition to get from the old diagram to the new one. The code to draw the sankey diagram is this:
function draw(data){
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(17)
.nodePadding(27)
.size([width, height]);
var path = sankey.link();
var graph = data;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
sankey.relayout();
// add in the links
link.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", function(d){
return "grey";
})
.style("stroke-opacity", "0.4")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", "0.7") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", "0.4") } )
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.transition().duration(750);
//link.exit();
// add in the nodes
var node = nodes.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("text-shadow", "0 1px 0 #fff")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
node.transition().duration(750);
}
The JSFiddle
Is it possible to use transition to add in new node and link and reposition
old nodes and links?
Thanks!
I was able to do this by using moving the nodes and links to new position. The code for that is:
var nodes = d3.selectAll(".node")
.transition().duration(750)
.attr('opacity', 1.0)
.attr("transform", function (d) {
if(d.node == 3){
console.log(d.x, d.y);
}
return "translate(" + d.x + "," + d.y + ")";
});
var nodeRects = d3.selectAll(".node rect")
.attr("height", function (d) {
if(d.node == 3){
console.log(d.dy);
}
return d.dy;
})
var links = d3.selectAll(".link")
.transition().duration(750)
.attr('d', path)
.attr('opacity', 1.0)
Updated JSFiddle
I've made a force directed graph and I wanted to change shape of nodes for data which contains "entity":"company" so they would have rectangle shape, and other one without this part of data would be circles as they are now.
You can see my working example with only circle nodes here: http://jsfiddle.net/dzorz/uWtSk/
I've tried to add rectangles with if else statement in part of code where I append shape to node like this:
function(d)
{
if (d.entity == "company")
{
node.append("rect")
.attr("class", function(d){ return "node type"+d.type})
.attr("width", 100)
.attr("height", 50)
.call(force.drag);
}
else
{
node.append("circle")
.attr("class", function(d){ return "node type"+d.type})
.attr("r", function(d) { return radius(d.value) || 10 })
//.style("fill", function(d) { return fill(d.type); })
.call(force.drag);
}
}
But then I did not get any shape at all on any node.
What Is a proper way to set up this?
The whole code looks like this:
script:
var data = {"nodes":[
{"name":"Action 4", "type":5, "slug": "", "value":265000},
{"name":"Action 5", "type":6, "slug": "", "value":23000},
{"name":"Action 3", "type":4, "slug": "", "value":115000},
{"name":"Yahoo", "type":1, "slug": "www.yahoo.com", "entity":"company"},
{"name":"Google", "type":1, "slug": "www.google.com", "entity":"company"},
{"name":"Action 1", "type":2, "slug": "",},
{"name":"Action 2", "type":3, "slug": "",},
{"name":"Bing", "type":1, "slug": "www.bing.com", "entity":"company"},
{"name":"Yandex", "type":1, "slug": "www.yandex.com)", "entity":"company"}
],
"links":[
{"source":0,"target":3,"value":10},
{"source":4,"target":3,"value":1},
{"source":1,"target":7,"value":10},
{"source":2,"target":4,"value":10},
{"source":4,"target":7,"value":1},
{"source":4,"target":5,"value":10},
{"source":4,"target":6,"value":10},
{"source":8,"target":4,"value":1}
]
}
var w = 560,
h = 500,
radius = d3.scale.log().domain([0, 312000]).range(["10", "50"]);
var vis = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
vis.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("refX", 17 + 3) /*must be smarter way to calculate shift*/
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead
//d3.json(data, function(json) {
var force = self.force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.distance(100)
.charge(-1000)
.size([w, h])
.start();
var link = vis.selectAll("line.link")
.data(data.links)
.enter().append("svg:line")
.attr("class", function (d) { return "link" + d.value +""; })
.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; })
.attr("marker-end", function(d) {
if (d.value == 1) {return "url(#arrowhead)"}
else { return " " }
;});
function openLink() {
return function(d) {
var url = "";
if(d.slug != "") {
url = d.slug
} //else if(d.type == 2) {
//url = "clients/" + d.slug
//} else if(d.type == 3) {
//url = "agencies/" + d.slug
//}
window.open("//"+url)
}
}
var node = vis.selectAll("g.node")
.data(data.nodes)
.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("class", function(d){ return "node type"+d.type})
.attr("r", function(d) { return radius(d.value) || 10 })
//.style("fill", function(d) { return fill(d.type); })
.call(force.drag);
node.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", function(d){ return d.img_href})
.attr("x", "-16px")
.attr("y", "-16px")
.attr("width", "32px")
.attr("height", "32px")
.on("click", openLink());
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name });
force.on("tick", function() {
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 + ")"; });
});
//});
css:
.link10 { stroke: #ccc; stroke-width: 3px; stroke-dasharray: 3, 3; }
.link1 { stroke: #000; stroke-width: 3px;}
.nodetext { pointer-events: none; font: 10px sans-serif; }
.node.type1 {
fill:brown;
}
.node.type2 {
fill:#337147;
}
.node.type3 {
fill:blue;
}
.node.type4 {
fill:red;
}
.node.type5 {
fill:#1BC9E0;
}
.node.type6 {
fill:#E01B98;
}
image.circle {
cursor:pointer;
}
You can edit my jsfiddle linked on beginning of post...
Solution here: http://jsfiddle.net/Bull/4btFx/1/
I got this to work by adding a class to each node, then using "selectAll" for each class to add the shapes. In the code below, I'm adding a class "node" and a class returned by my JSON (d.type) which is either "rect" or "ellipse".
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", function(d) {
return d.type + " node";
})
.call(drag);
Then you can add the shape for all elements of each class:
d3.selectAll(".rect").append("rect")
.attr("width", window.nodeWidth)
.attr("height", window.nodeHeight)
.attr("class", function(d) {
return "color_" + d.class
});
d3.selectAll(".ellipse").append("rect")
.attr("rx", window.nodeWidth*0.5)
.attr("ry", window.nodeHeight*0.5)
.attr("width", window.nodeWidth)
.attr("height", window.nodeHeight)
.attr("class", function(d) {
return "color_" + d.class
});
In the above example, I used rectangles with radius to draw the ellipses since it centers them the same way as the rectangles. But it works with other shapes too. In the jsfiddle I linked, the centering is off, but the shapes are right.
I implemented this behavior using the filter method that I gleaned from Filtering in d3.js on bl.ocks.org.
initGraphNodeShapes() {
let t = this;
let graphNodeCircles =
t.graphNodesEnter
.filter(d => d.shape === "circle")
.append("circle")
.attr("r", 15)
.attr("fill", "green");
let graphNodeRects =
t.graphNodesEnter
.filter(d => d.shape === "rect")
.append("rect")
.attr("width", 20)
.attr("height", 10)
.attr("x", -10) // -1/2 * width
.attr("y", -5) // -1/2 * height
.attr("fill", "blue");
return graphNodeCircles.merge(graphNodeRects);
}
I have this inside of initGraphNodeShapes call because my code is relatively large and refactored. The t.graphNodesEnter is a reference to the data selection after the data join enter() call elsewhere. Ping me if you need more context. Also, I use the d => ... version because I'm using ES6 which enables lambdas. If you're using pre-ES6, then you'll have to change it to the function(d)... form.
This is an older post, but I had the same trouble trying to get this concept working with D3 v5 in July of 2020. Here is my solution in case anyone else is trying to build a force-directed graph, I used both circle and rectangle elements to represent different types of nodes:
The approach was to create the elements, and then position them separately when invoking the force simulation (since a circle takes cx, cy, and r attributes, and the rect takes x, y, width and height). Much of this code follows the example in this blog post on medium: https://medium.com/ninjaconcept/interactive-dynamic-force-directed-graphs-with-d3-da720c6d7811
FYI I've declared 'svg' previously as the d3.select("some div with id or class"), along with a few helper functions not shown that read the data (setNodeSize, setNodeColor). I've used the D3.filter method to check for boolean field in the data - is the node initial or no?
Force simulation instance:
const simulation = d3.forceSimulation()
//the higher the strength (if negative), greater distance between nodes.
.force('charge', d3.forceManyBody().strength(-120))
//places the chart in the middle of the content area...if not it's top-left
.force('center', d3.forceCenter(width / 2, height / 2))
Create the circle nodes:
const nodeCircles = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter()
.filter(d => d.initial)
.append('circle')
.attr('r', setNodeSize)
.attr('class', 'node')
.attr('fill', setNodeColor)
.attr('stroke', '#252525')
.attr('stroke-width', 2)
Then create the rectangle nodes:
const nodeRectangles = svg.append('g')
.selectAll('rect')
.data(nodes)
.enter()
.filter(d => !d.initial)
.append('rect')
.attr('width', setNodeSize)
.attr('height', setNodeSize)
.attr('class', 'node')
.attr('fill', setNodeColor)
.attr('stroke', '#252525')
.attr('stroke-width', 2)
And then when invoking the simulation:
simulation.nodes(nodes).on("tick", () => {
nodeCircles
.attr("cx", node => node.x)
.attr("cy", node => node.y)
nodeRectangles
.attr('x', node => node.x)
.attr('y', node => node.y)
.attr('transform', 'translate(-10, -7)')
Of course there's more to it to add the lines/links, text-labels etc. Feel free to ping me for more code. The medium post listed above is very helpful!
I am one step ahead of you :)
I resolved your problem with using "path" instead of "circle" or "rect", you can look my solution and maybe help me to fix problem which I have...
D3 force-directed graph: update node position