I want to add text outside the circle using d3.js but only text is not getting added,other code works fine. How to add text to circle?
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var radius = 15;
var nodes_data = [{
"name": "Bacillus",
"sex": "F"
}, {
"name": "Candida",
"sex": "M"
}, {
"name": "Dorhea",
"sex": "M"
}, {
"name": "Pichia",
"sex": "F"
},
]
//Sample links data
//type: A for Ally, E for Enemy
var links_data = [{
"source": "Candida",
"target": "Bacillus",
"type": "A"
}, {
"source": "Dorhea",
"target": "Candida",
"type": "E"
}, {
"source": "Bacillus",
"target": "Dorhea",
"type": "A"
}, {
"source": "Bacillus",
"target": "Pichia",
"type": "C"
},
]
var 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)")
var nodeElements = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 18)
.attr("fill", getNodeColor)
var textElements = svg.append("g")
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function(node) {
return node.name
})
.attr("font-size", 20)
.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.source.x
})
.attr('y1', function(link) {
return link.source.y
})
.attr('x2', function(link) {
return link.target.x
})
.attr('y2', function(link) {
return link.target.y
})
})
//set up the simulation
var simulation = d3.forceSimulation()
//add nodes
.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.name;
});
var charge_force = d3.forceManyBody()
.strength(-2500);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("links", link_force);
//add tick instructions:
simulation.on("tick", tickActions);
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2)
.style("stroke", linkColour);
//draw circles for the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", radius)
.attr("fill", circleColour);
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node)
/** Functions **/
//Function to choose what color circle we have
//Let's return blue for males and red for females
function circleColour(d) {
if (d.sex == "M") {
return "blue";
} else {
return "pink";
}
}
//Function to choose the line colour and thickness
//If the link type is "A" return green
//If the link type is "E" return red
function linkColour(d) {
if (d.type == "A") {
return "green";
} else {
return "red";
}
}
//drag handler
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function tickActions() {
//constrains the nodes to be within a box
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));
});
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;
});
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: black;
stroke-width: 0px;
}
svg {
border: 1px solid black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="400"></svg>
The above code adds circles with colors and links between circle but i want to add text also outside the circle but when I apply code to add text its not adding text.how to solve this problem?
The code to add text is:
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")`enter code here`
.text(function(d) { return d.name; });
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
Related
I tried to implement a 3D force directed graph in d3.js by the help of the following codes. But was unable to create the 3D network by adding the z-coordinates.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
fill: blue;
stroke: black;
stroke-width: 2px;
}
.node.visited {
fill: red;
}
.link {
stroke-width: 2px;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 640;
var height = 480;
var links = [{
source: 'Rohit',
target: 'Deep'
},
{
source: 'Deep',
target: 'Deepa'
},
{
source: 'Deepa',
target: 'Rohit'
},
];
var nodes = [{
"id": 1,
"desc": "Rohit",
"x": 121.0284957885742,
"y": 116.3165512084961,
"z": 59.36788940429688
},
{
"id": 2,
"desc": "Deep",
"x": 12.10284957885742,
"y": 116.3165512084961,
"z": 5.936788940429688
},
{
"id": 3,
"desc": "Deepa",
"x": 12.10284957885742,
"y": 11.63165512084961,
"z": 5.936788940429688
}
];
//adding to nodes
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] ||
(nodes[link.target] = {
name: link.target
});
});
//adding svg to body
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var defs = svg.append('defs');
var gradient = defs
.append('linearGradient')
.attr('id', 'svgGradient')
.attr('x1', '0%')
.attr('x2', '10%')
.attr('y1', '0%')
.attr('y2', '10%');
gradient
.append('stop')
.attr('class', 'start')
.attr('offset', '0%')
.attr('start-color', 'red')
.attr('start-opacity', 1);
gradient
.append('stop')
.attr('class', 'end')
.attr('offset', '100%')
.attr('stop-color', 'blue')
.attr('stop-opacity', 1);
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on("tick", tick)
.linkDistance(300)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link')
.attr('stroke', 'url(#svgGradient)');
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.on("click", clicked)
.attr('r', width * 0.03)
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('cz', function(d){
return d.z;
});
//Code for clicking func.
function clicked(event, d) {
if (event.defaultPrevented) return; // dragged
d3.select(this).transition()
.style("fill", "black")
.attr("r", width * 0.2)
.transition()
.attr("r", width * 0.03)
.transition()
.style("fill", "blue")
//.attr("fill", d3.schemeCategory10[d.index % 10]);
}
//define the tick func.
function tick(e) {
node
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('cz', function(d) {
return d.z;
})
.call(force.drag);
link
.attr('x1', function(d) {
return d.source.x;
})
.attr('y1', function(d) {
return d.source.y;
})
.attr('z1', function(d) {
return d.source.z;
})
.attr('x2', function(d) {
return d.target.x;
})
.attr('y2', function(d) {
return d.target.y;
})
.attr('z2', function(d) {
return d.source.z;
})
}
</script>
</body>
The image that came was carrying two parts one graph where the nodes are taking (x-y) coordinates assigned to them. And then I had another graph where the nodes are taking all x-y-z coordinates.
Can anyone please help us by telling how can we add the z co-ordinates to code.
I am facing two visual problems with the given D3 forced graph.
The last inch of each link is still visible. The arrowhead should end exactly at the node edge, which is the case, but the red link underneath is visible. I could adjust the link stroke-width or the arrow size but actually do not want to change those attributes.
The radius of a node increases on a mouseenter event. If node size increases, the arrowhead is out of position and does not end on the node endge anymore. I assume I need to adjust the tick function, but do not find a proper starting point.
I appreciate any link, hint, help which gets me closer to the result.
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes":[
{ "id": "A" },
{ "id": "B" },
{ "id": "C" },
],
"links": [
{ "source": "A", "target": "B"},
{ "source": "B", "target": "C"},
{ "source": "C", "target": "A"}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
svg.append("defs").append("marker")
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -5 10 10')
.attr("refX", 23.5)
.attr("refY", 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr("orient", "auto")
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var links = svg.selectAll(".links")
.data(data.links)
.join("line")
.attr("stroke", "red")
.attr("stroke-width", 3)
.attr("marker-end", "url(#arrowhead)")
var nodes = svg.selectAll("g.outer")
.data(data.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
);
nodes
.append("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
.on("mouseenter", function() {
d3.select(this)
.transition()
.duration(250)
.attr("r", 40 * 1.3)
.attr("fill", "blue")
})
.on("mouseleave", function() {
d3.select(this)
.transition()
.duration(250)
.attr("r", 40)
.attr("fill", "lightgrey")
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
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; });
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
body {
background: whitesmoke,´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>
solution
at the moment I can think of two solutions.
calculate each <path(.links)/> vector and subtract with extended circle radius
give offset to each <marker />
personally I've chosen the 2nd solution; it requires less computation and easier to understand.
1. give each links custom arrowhead
add separate arrowheads for each link because it seems individual offset is not supported by svg path.
// add custom arrowheads for each link
// to avoid exhaustive computations
svg.append("defs").selectAll('marker')
.data(data.links)
.join("marker")
.attr('id', d => `arrowhead-target-${d.target}`)
// ...
var links = svg.selectAll(".links")
.data(data.links)
.join("line")
.attr("stroke", "red")
.attr("stroke-width", 3)
.attr("marker-end", d => `url(#arrowhead-target-${d.target})`)
// add source and target so that it can be traced back
2. extend, subtract each individual marker's offset with mouseevent
the offset for <marker /> is applied with .refX, .refY attributes, as noted in MDN docs
nodes
.append("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
.on("mouseenter", function(ev, d) {
// trace back to custom arrowhead with given id
const arrowHead = d3.select(`#arrowhead-target-${d.id}`)
arrowHead.transition().duration(250).attr('refX', 40)
d3.select(this)
.transition()
.duration(250)
.attr("r", 40 * 1.3)
.attr("fill", "blue")
})
.on("mouseleave", function(ev, d) {
const arrowHead = d3.select(`#arrowhead-target-${d.id}`)
arrowHead.transition().duration(250).attr('refX', 23.5)
d3.select(this)
.transition()
.duration(250)
.attr("r", 40)
.attr("fill", "lightgrey")
})
Try run the code below. The size of extension and subtraction is purposefully exaggerated so that you can see the difference.
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes":[
{ "id": "A" },
{ "id": "B" },
{ "id": "C" },
],
"links": [
{ "source": "A", "target": "B"},
{ "source": "B", "target": "C"},
{ "source": "C", "target": "A"}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
// add custom arrowheads for each link
// to avoid exhaustive computations
svg.append("defs").selectAll('marker')
.data(data.links)
.join("marker")
.attr('id', d => `arrowhead-target-${d.target}`)
.attr('viewBox', '-0 -5 10 10')
.attr("refX", 23.5)
.attr("refY", 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr("orient", "auto")
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var links = svg.selectAll(".links")
.data(data.links)
.join("line")
.attr("stroke", "red")
.attr("stroke-width", 3)
.attr("marker-end", d => `url(#arrowhead-target-${d.target})`)
// add source and target so that it can be traced back
.attr('data-source', d => d.source)
.attr('data-target', d => d.target)
var nodes = svg.selectAll("g.outer")
.data(data.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
);
nodes
.append("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
.on("mouseenter", function(ev, d) {
// trace back to custom arrowhead with given id
const arrowHead = d3.select(`#arrowhead-target-${d.id}`)
arrowHead.transition().duration(250).attr('refX', 40)
d3.select(this)
.transition()
.duration(250)
.attr("r", 40 * 1.3)
.attr("fill", "blue")
})
.on("mouseleave", function(ev, d) {
const arrowHead = d3.select(`#arrowhead-target-${d.id}`)
arrowHead.transition().duration(250).attr('refX', 23.5)
d3.select(this)
.transition()
.duration(250)
.attr("r", 40)
.attr("fill", "lightgrey")
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
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; });
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
body {
background: whitesmoke,´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>
Dragging of rectangles and graph work as intended in the given example. But I would like to bind the graph nodes to the rectangles underneath, so that moving a rectangle also moves the bound graph node and with it the graph link.
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = { "nodes": [{ "x": 170, "y": 120 }, { "x": 400, "y": 100 }], "links": [{ "source": 0, "target": 1 }] }
graph.links.forEach(function (d) {
d.source = graph.nodes[d.source];
d.target = graph.nodes[d.target];
});
var dataRects = [
{ "x": 20, "y": 50, "width": 200, "height": 100 },
{ "x": 350, "y": 75, "width": 150, "height": 50 }
]
var rects = svg.append("g")
.attr("id", function () { return "g_0" })
.attr("class", "rect")
.selectAll("rect")
.data(dataRects)
.enter()
.append("rect")
.attr("id", function (d, i) { return "box_" + i })
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; })
.attr("width", function (d) { return d.width })
.attr("height", function (d) { return d.height })
.call(d3.drag()
//.on("start", function(d) {}
.on("drag", function (d) {
const matrix = d3.select(this).node().transform.baseVal.consolidate().matrix;
const x = matrix.e + d3.event.dx;
const y = matrix.f + d3.event.dy;
d3.select(this).attr("transform", `translate(${x},${y})`);
})
.on("end", function (d) {
idOnEnd = d3.select(this).attr("id");
})
)
.attr("transform", "translate(0,0)");
var link = svg.append("g")
.attr("class", "link")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.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; });
var node = svg.append("g")
.attr("class", "node")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("id", function (d, i) { return "node_" + i; })
.attr("r", 9)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.call(d3.drag().on("drag", dragged));
function dragged(d) {
d.x = d3.event.x, d.y = d3.event.y;
d3.select(this).attr("cx", d.x).attr("cy", d.y);
link.filter(function (l) { return l.source === d; }).attr("x1", d.x).attr("y1", d.y);
link.filter(function (l) { return l.target === d; }).attr("x2", d.x).attr("y2", d.y);
}
// var nodeSelect = d3.select("#node_0")
// d3.select("#box_0").append(function(){return nodeSelect.node()});
// d3.select("#g_0").append(() => nodeSelect.node());
.svg {
border: 1px solid red;
}
.node {
stroke: #555;
fill: #F00;
stroke-width: 3px;
}
.link {
stroke: #555;
stroke-width: 3px;
}
.rect {
fill: lightgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
Tried various things that didn't work, among them making a rectangle or its group the node parent:
var nodeSelect = d3.select("#node_0")
d3.select("#box_0").append(function(){return nodeSelect.node()});
d3.select("#g_0").append(() => nodeSelect.node());
The group change is reflected in the DOM but doesn't produce the desired effect.
Wasn't lucky searching for pertinent posts.
The larger goal to create a flow chart, where links between rectangles are created by mouse clicks, and links are preserved and move along when rectangles are dragged around.
so I am simply trying to attach text, represented through data.link (the edge variable), to my links between nodes. I have searched extensively online, but none of the examples I have found work for my code. Attached is my code and a screenshot of what the display looks like.
Sample output of given code
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.links line {
stroke: #000000;
stroke-opacity: 0.99;
}
.node text {
stroke:#ffffff;
cursos:pointer;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<body>
<jsp:include page = "/header.jsp"/>
<script>
var width = 2000,
height = 1000
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var toString = JSON.stringify(${json});
console.log(toString);
var data = JSON.parse(toString);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id;
}).distance(300))
.force("charge", d3.forceManyBody().strength(-50))
.force("center", d3.forceCenter(1000, 500));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(data.links)
.enter().append("line")
.attr("fill", "black")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); })
.attr("id",function(d,i) {return 'link '+i});
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data.nodes)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + width/12 + "," + height/12 + ")";
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var path = svg.append("g").selectAll("link")
.data(data.links)
.enter().append("path")
.attr("id",function(d,i) { return "linkid_" + i; });
var labelText = svg.selectAll(".labelText")
.data(data.links)
.enter().append("text")
.attr("class","labelText")
.attr("dx",20)
.attr("dy",0)
.style("fill","red")
.append("textPath")
.attr("xlink:href",function(d,i) { return "#linkid_" + i;})
.text(function(d) { return d.edge;});
node.append("circle")
.attr("r", 75)
.style("stroke-width", 3)
.style("stroke", function(d){
//is in query
if(d.queried)
return "ffff00"
//is not in query
else
return "black"
})
.attr("fill", function(d){
//is a person
if(d.name != null)
return "#00ccff";
//is a message
else
return "#006666";
});
simulation
.nodes(data.nodes)
.on("tick", ticked);
simulation.force("link")
.links(data.links);
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -15)
.attr("dy",20)
.text(function(d) {
return d.gender;
});
//only append id if a person
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -20)
.attr("dy",40)
.text(function(d) {
if(d.name != null)
return d.id;
});
//Add message if is message type
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -50)
.attr("dy", -20)
.text(function(d) {
if(d.name == null)
return d.message.substring(0,15);
});
//Add message if is message type
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -50)
.attr("dy", -5)
.text(function(d) {
if(d.name == null)
return d.message.substring(15,30);
});
//Add message if is message type
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -50)
.attr("dy", 10)
.text(function(d) {
if(d.name == null)
return d.message.substring(30,45);
});
//Add message if is message type
node.append("text")
.attr("class","system")
.attr("fill","white")
.attr("dx", -50)
.attr("dy", 25)
.text(function(d) {
if(d.name == null)
return d.message.substring(45,60) + "...";
});
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;
}
I have made a force directed graph which looks similar to this.
I would like the Nine Inch Nails node, in the centre, to be an image but the rest of the nodes to just be circles. Following this answer it seemed not too difficult but I just can't get my head around the combonation of svg, defs, patterns and d3.
My code is:
var simulation =
d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-50))
.force("collide", d3.forceCollide().radius(function (d) { return 15 - d.group}).strength(2).iterations(2))
.force("link", d3.forceLink().id(function(d, i) { return i;}).distance(20).strength(0.9))
.force("center", d3.forceCenter(width/2, height/2))
.force('X', d3.forceX(width/2).strength(0.15))
.force('Y', d3.forceY(height/2).strength(0.15));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
var defs = svg.append('svg:defs');
defs.append("svg:pattern")
.attr("id", "vit-icon")
.attr("width", 48)
.attr("height", 48)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", "http://placekitten.com/g/48/48")
.attr("width", 48)
.attr("height", 48)
.attr("x", width/2)
.attr("y", height/2)
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("id", function(d, i) { return 'c'+i})
.attr("r", radius)
.attr("fill", function(d) {
if(d.group==0) {return "url(#vit-icon)";}
else {return color(d.group); }
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
As I say, it seems straight forward in my mind. Basically what I think I'm trying to do is have the image in the svg pattern, then use an if statement to tell my root node to use the image url rather than fill with colour.
In dev tools I can see inspect the image and it shows the blue area it would take up and the node I want it to be associatated to has the 'url(#vit-icon)' as it's fill attribute. But it is not showing the image or any fill for that node.
What am I doing wrong? Or is this the complete wrong approach?
Thanks.
In your defs, just change:
.attr("x", width/2)
.attr("y", height/2)
To:
.attr("x", 0)
.attr("y", 0);
Here is a demo:
var nodes = [{
"id": 1,
}, {
"id": 2,
}, {
"id": 3,
}, {
"id": 4,
}, {
"id": 5,
}, {
"id": 6,
}, {
"id": 7,
}, {
"id": 8,
}];
var links = [{
source: 1,
target: 2
}, {
source: 1,
target: 3
}, {
source: 1,
target: 4
}, {
source: 2,
target: 5
}, {
source: 2,
target: 6
}, {
source: 1,
target: 7
}, {
source: 7,
target: 8
}];
var index = 10;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
var defs = svg.append('svg:defs');
defs.append("svg:pattern")
.attr("id", "vit-icon")
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", "http://66.media.tumblr.com/avatar_1c725152c551_128.png")
.attr("width", 48)
.attr("height", 48)
.attr("x", 0)
.attr("y", 0);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("collide", d3.forceCollide(50))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
link = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
link = link.enter()
.append("line")
.attr("class", "link");
node = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id;
})
node = node.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", d=> d.id === 1 ? 24 : 14)
.style("fill", function(d) {
if (d.id === 1) {
return "url(#vit-icon)";
} else {
return "teal"
}
})
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()
}
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 = undefined;
d.fy = undefined;
}
.link {
stroke: #aaa;
}
.node {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="300"></svg>