Force directed graphs nodes stick to the center - javascript
After following up from this question Insert text inside Circle in D3 chart
My nodes are sticking to the center. I am not sure which property is directing my nodes and their x and y coordinates. I recently chnaged my code to add a g layer to the circles so that i can append text along with shape.
DATA
https://api.myjson.com/bins/hwtj0
UPDATED CODE
async function d3function() {
d3.selectAll("svg > *").remove();
const svg = d3.select("svg");
file = document.getElementById("selectFile").value;
console.log("File: " + file)
var width = 900
var height = 900
svg.style("width", width + 'px').style("height", height + 'px');
data = (await fetch(file)).json()
d3.json(file).then(function(data) {
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
console.log(links.length);
console.log(nodes.length);
const simulation = forceSimulation(nodes, links).on("tick", ticked);
var categorical = [
{ "name" : "schemeAccent", "n": 8},
{ "name" : "schemeDark2", "n": 8},
]
// var colorScale = d3.scaleOrdinal(d3[categorical[6].name])
var color = d3.scaleOrdinal(d3[categorical[1].name]);
var drag = simulation => {
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;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", d => Math.sqrt(d.value));
// link.append("title").text(d => d.value);
// var circles = svg.append("g")
// .attr("stroke", "#fff")
// .attr("stroke-width", 1.5)
// .selectAll(".circle")
// .data(nodes)
// const node = circles.enter().append("circle")
// .attr("r", 5)
// .attr("fill", d => color(d.group))
// .call(drag(simulation));
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circles")
.data(nodes)
.enter()
.append("g")
.classed('circles', true)
.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
node.append("circle")
.classed('circle', true)
.attr("r", 5)
.attr("fill", d => color(d.group))
.call(drag(simulation));
node
.append("text")
.classed('circleText', true)
.attr('dy', '0.35em')
.attr('dx', 5)
.text(d => "Node: " + d.id);
node.append("title").text(d => "Node: " + d.id);
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
});
}
function forceSimulation(nodes, links) {
return d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter());
}
UPDATED OUTPUT
EXPECTED OUTPUT
UPDATED HTML
<g stroke="#fff" stroke-width="1.5">
<g class="circle" cx="-35.89111508769784" cy="131.13965804447696">
<circle class="circle" r="5" fill="#1b9e77"></circle>
<text class="circleText" dy="0.35em" dx="5">Node: 0</text>
<title>Node: 0</title>
</g>
<g class="circle" cx="70.97799024729613" cy="-195.71408429254427">
<circle class="circle" r="5" fill="#d95f02"></circle>
<text class="circleText" dy="0.35em" dx="5">Node: 3</text>
<title>Node: 3</title>
</g>
[....]
</g>
You have to adapt your code slightly as it currently assumes that you're working with circle elements, where you specify the centres using cx and cy, but you are now using g elements, which use standard x and y coordinates.
First, remove the transform from the g element (that's a leftover from my demo code):
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll(".circles") // note - should be .circles!
.data(nodes)
.enter()
.append("g")
.classed('circles', true)
and in the ticked() function, change the node updating code into a transform that works on g elements (which don't have cx or cy):
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')' )
Demo:
var json = {"nodes":[{"id":"0","group":0},{"id":"1","group":1},{"id":"2","group":2},{"id":"3","group":3},{"id":"4","group":4},{"id":"5","group":5},{"id":"6","group":6},{"id":"7","group":7},{"id":"8","group":8},{"id":"9","group":9},{"id":"10","group":10},{"id":"11","group":11},{"id":"12","group":12},{"id":"13","group":13},{"id":"14","group":14},{"id":"15","group":15},{"id":"16","group":16},{"id":"17","group":17},{"id":"18","group":18},{"id":"19","group":19}],"links":[{"source":"0","target":"1","value":1},{"source":"0","target":"18","value":1},{"source":"0","target":"10","value":1},{"source":"0","target":"12","value":1},{"source":"0","target":"5","value":1},{"source":"0","target":"8","value":1},{"source":"1","target":"0","value":1},{"source":"1","target":"9","value":1},{"source":"1","target":"4","value":1},{"source":"2","target":"4","value":1},{"source":"2","target":"17","value":1},{"source":"2","target":"13","value":1},{"source":"2","target":"15","value":1},{"source":"3","target":"6","value":1},{"source":"4","target":"14","value":1},{"source":"4","target":"2","value":1},{"source":"4","target":"5","value":1},{"source":"4","target":"19","value":1},{"source":"4","target":"1","value":1},{"source":"5","target":"4","value":1},{"source":"5","target":"0","value":1},{"source":"6","target":"3","value":1},{"source":"7","target":"18","value":1},{"source":"7","target":"16","value":1},{"source":"8","target":"0","value":1},{"source":"9","target":"1","value":1},{"source":"10","target":"0","value":1},{"source":"10","target":"15","value":1},{"source":"12","target":"0","value":1},{"source":"13","target":"15","value":1},{"source":"13","target":"2","value":1},{"source":"14","target":"4","value":1},{"source":"15","target":"13","value":1},{"source":"15","target":"10","value":1},{"source":"15","target":"2","value":1},{"source":"16","target":"7","value":1},{"source":"17","target":"2","value":1},{"source":"18","target":"0","value":1},{"source":"18","target":"7","value":1},{"source":"19","target":"4","value":1},{"source":"19","target":"4","value":1}]};
d3.selectAll("svg > *").remove();
const svg = d3.select("svg");
var width = 900
var height = 900
svg.style("width", width + 'px').style("height", height + 'px');
const links = json.links.map(d => Object.create(d));
const nodes = json.nodes.map(d => Object.create(d));
const simulation = forceSimulation(nodes, links).on("tick", ticked);
var categorical = [
{
"name": "schemeAccent",
"n": 8
},
{
"name": "schemeDark2",
"n": 8
}, ]
var color = d3.scaleOrdinal(d3[categorical[1].name]);
var drag = simulation => {
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;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", d => Math.sqrt(d.value));
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll(".circles")
.data(nodes)
.enter()
.append("g")
.classed('circles', true)
.call(drag(simulation))
// .attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
const circle = node.append("circle")
.classed('circle', true)
.attr("r", 5)
.attr("fill", d => color(d.group))
node
.append("text")
.classed('circleText', true)
.attr('dy', '0.35em')
.attr('dx', 5)
.text(d => "Node: " + d.id);
node.append("title").text(d => "Node: " + d.id);
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')')
}
function forceSimulation(nodes, links) {
return d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter());
}
.circleText { fill: black; stroke: none }
<script src="//d3js.org/d3.v5.js"></script>
<svg></svg>
Related
D3.js: How to highlight connected edges and nodes in a Force-Directed Graph?
I'm trying to build a Force-Directed Graph with Link Highlighting. Initially the graph will look like the following: But after hovering on a particular node I want it to become like the following (only connected nodes and edges will be highlighted): I've tried the following code. But the highlighting part is not working. 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.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("g") .data(graph.nodes) .enter() .append("g") .on("mouseover", mouseover) .on("mouseout", mouseout) .call( d3 .drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) ); var circles = node.append("circle") .attr("r", 10) .attr("fill", function (d) { return color(d.group); }); var lables = node.append("text") .text(function (d) { return d.id; }) .attr("x", 0) .attr("dy", ".35em") .attr("text-anchor", "middle"); 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("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; } graph.links.forEach(function(d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; linkedByIndex[d.target.index + "," + d.source.index] = 1; }); function neighboring(a, b) { return a.index == b.index || linkedByIndex[a.index + "," + b.index]; } function mouseover(d) { d3.selectAll("link").transition().duration(500) .style("opacity", function (o) { return o.source === d || o.target === d ? 1 : 0; }); d3.selectAll("node").transition().duration(500) .style("opacity", function (o) { return neighboring(d, o) ? 1 : 0; }); } function mouseout() { d3.selectAll("link").transition().duration(500) .style("opacity", 1); d3.selectAll("node").transition().duration(500) .style("opacity", 1); } The full code can be found at https://plnkr.co/edit/BA9OfjVQjOwyHDkh Could you please help me find the issue here and make it work?
d3.js fixed x,y coords for some nodes in force
I'm new with d3.js. I have a disjointed network, it means, in a same file I have many small network like: a a a b a c b a b b b c c a c b c c e e e f e g f e f f f g g e g f g g In this example, has two disjointed networks, one has [a,b,c] nodes other[e,f,g] nodes. I may tag only one node per network to be my "hub" (not a hub itself necessarily, hubs are highly connected) or "chosen" just inserting 1 if it is chosen or 0 if it is not. Now the problem is: "In a force directed graph how do I keep fixed the hubs?". Actually, what I really would like is, distribute in the space all networks, left-to-right and top-to-bottom, from largest to smallest (in number of nodes). What did I think. Take only the hubs and assign the x and y coords and if (d.choose == 1){ //returns x,y}else{ //free coords}. The problem isn't create x and y columns, but the d3.js code itself to keep some choords frozen. Follow my code <!DOCTYPE html> <meta charset="utf-8"> <!-- width="2480" height="1720 --> <svg width="2600" height="1020"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script type="text/javascript"> var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"), radius = 6; var simulation = d3.forceSimulation() // One thing to consider is the overall strength, negative spaced, positive agglomerates .force("charge", d3.forceManyBody().strength( function(d){ if ( d.choose == 1 ) { return -2;} if ( d.choose == 0 ) { return -0.2; } } ) ) // Another issue is the distance + distance more distant the dots, // d is the jason file, here it is accessing the d.id and d.line .force("link", d3.forceLink().iterations(10).id(function(d) { return d.id; } ).distance(function (d) {if (d.line == "none"){ return 15;} else { return 15; } })) // center graph .force("center", d3.forceCenter(width / 2, height / 2)); // load json file (?) d3.json("genomes_net.json", function(error, graph) { if (error) throw error; var canvas = d3.select("body").append("canvas") .attr("width", width) .attr("height", height); // draw line for each pair var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") // line color .attr("stroke", function (d) {return d.colorsamp;}) // draw lines (% of total, or gradient?) .attr("stroke-width", 1); // draw circles for each node var node = svg.append("g") .attr("class","nodes") .selectAll("circle") .data(graph.nodes) .enter().append("circle") .attr("r", (d) => { if (d.choose == 0 ) {return 8;} if (d.choose == 1) {return 16;} }) //.style("fill", "#FF69B4") //function(d) { return d.colorsamp; }) .attr("fill", function(d) { return d.colorsamp ; }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); node.append("title") .text(function(d) { return (d.name+ "\n" + d.lineage + " (" + d.genera +")"); }); var text = svg.append("g") .attr("class","labels") .selectAll("text") .data(graph.nodes) .enter().append("text") .text(function (d) { if (d.direto == "yes"){ return d.id; } }) .attr("font-size", "8px") .attr("font-family", "sans-serif") .attr("dx", 15) .attr("dy", ".31em") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); 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 = Math.max(radius, Math.min(width - radius, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); }); text .attr("dx", function (d) {return d.x -20; }) .attr("dy", function (d) {return d.y + 15; }) } }); 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; } // Important notes: // Understanding forces: https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03 // </script>
D3 force-directed graph adding new nodes causes x & y to be NaN
When I click on a node, I want a new node to be added to it. They should both have labels (I'm trying to build a thesaurus visualization). I'm very new to D3, so I apologize if you have to explain things in a bit more detail. This is my code so far: var width = 960; var height = 500; var force = d3.layout.force() .gravity(0.05) .distance(100) .charge(-100) .size([width, height]) .nodes([{ "name": "One", "group": 1 }]) .start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var nodes = force.nodes(); var links = force.links(); var link = svg.selectAll(".link") .data(links) .enter().append("line") .attr("class", "link"); var node = svg.selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 10) .on("mousedown", onClick); node.append("text") .attr("dx", 12) .attr("dy", ".35em") .text(d => d.name); force.on("tick", function() { link.attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); node.attr("transform", d => { // d.x & d.y are NaN for new nodes return "translate(" + d.x + "," + d.y + ")"; }); }); restart(); function restart() { node = node.data(nodes); node.enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 10) .on("mousedown", onClick); node.append("text") .attr("dx", 12) .attr("dy", ".35em") .text(d => d.name); node.exit().remove(); link = link.data(links); } function onClick(clicked_node) { console.log("click!"); console.log(clicked_node); var new_node = { name: "Test", group: 2 }; nodes.push(new_node); // Has x & y set to NaN after adding links.push({ source: clicked_node, target: new_node }); restart(); } As soon as I click on the first node, causing the node { name: "Test", group: 2 }; to be added, D3 throws errors within node.attr("transform", d => { // d.x & d.y are NaN for new nodes return "translate(" + d.x + "," + d.y + ")"; }); because the d.x and d.y for this new node are NaN. I tried setting them explicitly: { name: "Test", group: 2, x: clicked_node.y, y: clicked_node.y }; But I get the same error. In the inspector, when this node is added to the screen, the x and y values becomes the px and py values instead! I don't understand why this happens.
You are missing one line of code. After you add your new node you need to restart the simulation for d3 to calculate it's position: function restart() { node = node.data(nodes); node.enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 10) .on("mousedown", onClick); node.append("text") .attr("dx", 12) .attr("dy", ".35em") .text(d => d.name); node.exit().remove(); link = link.data(links); force.start(); //<-- start simulation } Running code: <!DOCTYPE html> <html> <head> <script data-require="d3#3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script> </head> <body> <script> var width = 960; var height = 500; var force = d3.layout.force() .gravity(0.05) .distance(100) .charge(-100) .size([width, height]) .nodes([{ "name": "One", "group": 1 }]) .start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var nodes = force.nodes(); var links = force.links(); var link = svg.selectAll(".link") .data(links) .enter().append("line") .attr("class", "link"); var node = svg.selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 10) .on("mousedown", onClick); node.append("text") .attr("dx", 12) .attr("dy", ".35em") .text(d => d.name); force.on("tick", function() { link.attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); node.attr("transform", d => { // d.x & d.y are NaN for new nodes return "translate(" + d.x + "," + d.y + ")"; }); }); restart(); function restart() { node = node.data(nodes); node.enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 10) .on("mousedown", onClick); node.append("text") .attr("dx", 12) .attr("dy", ".35em") .text(d => d.name); node.exit().remove(); link = link.data(links); force.start(); } function onClick(clicked_node) { console.log("click!"); console.log(clicked_node); var new_node = { name: "Test", group: 2 }; nodes.push(new_node); // Has x & y set to NaN after adding links.push({ source: clicked_node, target: new_node }); restart(); } </script> </body> </html>
D3 x and y coordinates applied to last appened element instead of the group
I have a force directed graph where each node is a group and contains a foreignObject and an img inside it. x and y coordiantes are applied to the img tag but I would like to have them on the foreignObject or g. I am not sure how this can be achieved. I tried setting x and y coordinates to the foreignObject but they are always returned as NaN. const plot = (data) => { data.nodes = data.nodes.map((d, index) => { d['id'] = index; return d; }); const margin = { top: 20, right: 20, bottom: 10, left: 100 }; const width = Math.max((((window.innerWidth / 100) * 80) - margin.right - margin.left), 700); const height = ((window.innerHeight / 100) * 80) - margin.bottom - margin.top; const svg = d3.select('svg') .attr('width', width + margin.left + margin.right + 100) .attr('height', height + margin.top + margin.bottom + 100) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); const simulation = d3.forceSimulation() .force('link', d3.forceLink().id(function (d) { return d.id; }).distance(100).strength(1)) .force('charge', d3.forceManyBody()) .force('center', d3.forceCenter(width / 2, height / 2)); const dragstarted = d => { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } const dragged = d => { d.fx = d3.event.x; d.fy = d3.event.y; } const dragended = d => { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } const ticked = () => { link .attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); node .attr("x", function (d) { return d.x = Math.max(5, Math.min(width - 5, d.x)); }) .attr("y", function (d) { return d.y = Math.max(5, Math.min(height - 5, d.y)); }); } const link = svg.append('g') .attr('class', 'links') .selectAll('line') .data(data.links) .enter().append('line') .attr('stroke-width', function (d) { return Math.sqrt(d.value); }); const node = svg.append('g') .attr('class', 'nodes') .selectAll('.node-group') .data(data.nodes) .enter() .append('g') .append('foreignObject') .attr('class', 'node-group') .attr('width', '10') .attr('height', '10') .insert('xhtml:img') .attr('src', 'flags/blank.png') .attr('class', d => `flag flag-${d.code}`) .call(d3.drag() .on('start', dragstarted) .on('drag', dragged) .on('end', dragended)); node.append("title") .text(function (d) { return d.country; }) .exit(); simulation .nodes(data.nodes) .on("tick", ticked); simulation.force("link") .links(data.links); }
If you look at your node selection... const node = svg.append('g') .attr('class', 'nodes') .selectAll('.node-group') .data(data.nodes) .enter() .append('g') .append('foreignObject') .attr('class', 'node-group') .attr('width', '10') .attr('height', '10') .insert('xhtml:img') .attr('src', 'flags/blank.png') .attr('class', d => `flag flag-${d.code}`) .call(d3.drag() .on('start', dragstarted) .on('drag', dragged) .on('end', dragended)); ... you'll see that it is a selection with the images. Since you want to apply the x and y properties to the foreignObject, just break it: const node = svg.append('g') .attr('class', 'nodes') .selectAll('.node-group') .data(data.nodes) .enter() .append('g') .append('foreignObject') .attr('class', 'node-group') .attr('width', '10') .attr('height', '10'); node.insert('xhtml:img') .attr('src', 'flags/blank.png') .attr('class', d => `flag flag-${d.code}`) .call(d3.drag() .on('start', dragstarted) .on('drag', dragged) .on('end', dragended)); That way, node is a selection with the foreignObjects.
D3.js Force Direct Graph - png's in circles - making the images circular
I asked this question yesterday and got a great duplicate question answer - many thanks Gerardo. The example used images with transparent backgrounds (see Opera and Chrome image below) and it works a treat however, I would like to provide square images which fit inside the circle. I've tried the border radius in CSS and Bootstrap circular image but these don't work - probably because it is an tag not an tag. Here is the rendered tag in case that is helpful. <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="female1.png" class="circle-image" height="40" width="40" x="-20" y="-20"></image> Would setting it up as a pattern help?
You can use a SVG pattern: var defs = svg.append("defs"); defs.append('pattern') .attr("id", "foo") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "foo.jpg") .attr("width", someValue) .attr("height", someValue) .attr("y", someValue) .attr("x", someValue); Then, in your circles: .attr("fill", "url(#foo") Here is a demo: var width = 400; var height = 300; var svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height); var defs = svg.append("defs"); defs.append('pattern') .attr("id", "dog") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "http://cdn2-www.dogtime.com/assets/uploads/2010/12/senior-dog-2.jpg") .attr("width", 100) .attr("height", 100) .attr("y", -20) .attr("x", -20); defs.append('pattern') .attr("id", "cat") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "https://s-media-cache-ak0.pinimg.com/736x/92/9d/3d/929d3d9f76f406b5ac6020323d2d32dc.jpg") .attr("width", 120) .attr("height", 120) .attr("x", -30) .attr("y", -10); var nodes = [{id:"foo"},{id:"bar"}, {id:"baz"},{id:"barbaz"}]; var edges = [{ "source": 0, "target": 1 }, { "source": 0, "target": 2 }, { "source": 0, "target": 3 }]; var simulation = d3.forceSimulation() .force("link", d3.forceLink().distance(80)) .force("charge", d3.forceManyBody().strength(-100)) .force("center", d3.forceCenter(width / 2, height / 2)); var links = svg.selectAll("foo") .data(edges) .enter() .append("line") .style("stroke", "#ccc") .style("stroke-width", 1); var node = svg.selectAll("foo") .data(nodes) .enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var nodeCircle = node.append("circle") .attr("r", 30) .attr("stroke", "gray") .attr("stroke-width", "2px") .attr("fill", function(d,i){ return i%2 === 0 ? "url(#dog)" : "url(#cat)" }); var texts = node.append("text") .style("fill", "black") .attr("dx", 36) .attr("dy", 8) .text(function(d) { return d.id; }); simulation.nodes(nodes); simulation.force("link") .links(edges); simulation.on("tick", function() { links.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", (d) => "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>