Reputation: 341
The graph connects the nodes proper but the links are not visible. I added the .attr("stroke", "black")
.attr("stroke-width", 2)
directly in the update()
function. Before I had it in CSS which also wasn´t working. The browser inspector is telling me that the x, y positons are 0 for my link group. Which irritates me, usually those positions are provided in the ticked()
function, by source.x,y and target.x,y.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v4 enter().exit().remove()</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<!-- import "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.node_group {
fill: whitesmoke;
stroke: white;
stroke-width: 2px
}
.node_label {
stroke: black;
stroke-width: 0px;
fill: black
}
.tooltip {
font-family: "Open Sans", sans-serif;
position: absolute;
text-align: left;
background: rgb(245, 245, 245);
border: 2px;
border-radius: 6px;
border-color: rgb(255, 255, 255);
border-style: solid;
pointer-events: none;
line-height: 150%;
padding: 8px 10px;
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "1",
},
{
"id": 2,
"name": "2",
},
{
"id": 3,
"name": "3",
},
{
"id": 4,
"name": "4",
}
],
"links": [{
"source": 1,
"target": 0,
},
{
"source": 2,
"target": 0,
},
{
"source": 3,
"target": 0,
},
{
"source": 4,
"target": 0,
},
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-80))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
var node_group = null
var link_group = null
update()
/*
console.log("Initial Nodes")
console.log(graph.nodes)
console.log("------------------")
*/
function update() {
//define group and join - nodes
node_group = svg.selectAll(".node_group")
.data(graph.nodes, d => d.id)
//exit, remove
node_group.exit().remove();
//enter
var enterN = node_group.enter()
.append("g").attr("class", "node_group");
//append
enterN.append("circle")
.attr("class", "node_circle")
.attr("r", 20)
.on("contextmenu", contextMenu)
enterN.append("text")
.attr("class", "node_label")
.text(function (d) {
return d.name
})
//merge
node_group = node_group.merge(enterN);
node_group.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
//###LINKS
//define group and join -links
link_group = svg.selectAll(".link_group")
.data(graph.links)
//exit, remove
link_group.exit().remove();
//enter
var enterL = link_group.enter()
.append("g").attr("class", "link_group")
//append
enterL.append("line")
.attr("class", "link_stroke")
.attr("stroke", "black")
.attr("sroke-width", 2)
//merge
link_group = link_group.merge(enterL);
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
simulation.alphaTarget(0.3).restart()
}
function contextMenu(d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNode)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function removeNodeClicked() {
removeNode(thisNode)
}
function addNode() {
var newID = Math.floor(Math.random() * 100000)
/*
console.log("Before adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.links.push({source: newID, target: thisNode.id})
graph.nodes.push({ id: newID, name: newID })
/*
console.log("After adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function removeNode(thisNode) {
var indexOfNode = graph.nodes.indexOf(thisNode)
var indexOfLink = graph.links.findIndex(element => element.source.id === thisNode.id)
/*
console.log("Before removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.links.splice(indexOfLink,1)
graph.nodes.splice(indexOfNode, 1)
/*
console.log("After removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function ticked() {
// update link positions
link_group
.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; });
// update node positions
node_group
.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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
Upvotes: 1
Views: 61
Reputation: 102194
For whatever reason you're appending the <line>
inside <g>
elements, and then in the ticked
function you're setting the x1
, y1
, x2
and y2
properties to those groups, not to the lines.
That said, the minimal change for that code to work is, in the ticked
function:
link_group.select("line")
.attr("x1", etc...
However, I'd ditch the groups altogether and append just the lines.
Here is your code with that change only:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v4 enter().exit().remove()</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<!-- import "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.node_group {
fill: whitesmoke;
stroke: white;
stroke-width: 2px
}
.node_label {
stroke: black;
stroke-width: 0px;
fill: black
}
.tooltip {
font-family: "Open Sans", sans-serif;
position: absolute;
text-align: left;
background: rgb(245, 245, 245);
border: 2px;
border-radius: 6px;
border-color: rgb(255, 255, 255);
border-style: solid;
pointer-events: none;
line-height: 150%;
padding: 8px 10px;
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "1",
},
{
"id": 2,
"name": "2",
},
{
"id": 3,
"name": "3",
},
{
"id": 4,
"name": "4",
}
],
"links": [{
"source": 1,
"target": 0,
},
{
"source": 2,
"target": 0,
},
{
"source": 3,
"target": 0,
},
{
"source": 4,
"target": 0,
},
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-80))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
var node_group = null
var link_group = null
update()
/*
console.log("Initial Nodes")
console.log(graph.nodes)
console.log("------------------")
*/
function update() {
//define group and join - nodes
node_group = svg.selectAll(".node_group")
.data(graph.nodes, d => d.id)
//exit, remove
node_group.exit().remove();
//enter
var enterN = node_group.enter()
.append("g").attr("class", "node_group");
//append
enterN.append("circle")
.attr("class", "node_circle")
.attr("r", 20)
.on("contextmenu", contextMenu)
enterN.append("text")
.attr("class", "node_label")
.text(function (d) {
return d.name
})
//merge
node_group = node_group.merge(enterN);
node_group.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
//###LINKS
//define group and join -links
link_group = svg.selectAll(".link_group")
.data(graph.links)
//exit, remove
link_group.exit().remove();
//enter
var enterL = link_group.enter()
.append("g").attr("class", "link_group")
//append
enterL.append("line")
.attr("class", "link_stroke")
.attr("stroke", "black")
.attr("sroke-width", 2)
//merge
link_group = link_group.merge(enterL);
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
simulation.alphaTarget(0.3).restart()
}
function contextMenu(d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNode)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function removeNodeClicked() {
removeNode(thisNode)
}
function addNode() {
var newID = Math.floor(Math.random() * 100000)
/*
console.log("Before adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.links.push({source: newID, target: thisNode.id})
graph.nodes.push({ id: newID, name: newID })
/*
console.log("After adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function removeNode(thisNode) {
var indexOfNode = graph.nodes.indexOf(thisNode)
var indexOfLink = graph.links.findIndex(element => element.source.id === thisNode.id)
/*
console.log("Before removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.links.splice(indexOfLink,1)
graph.nodes.splice(indexOfNode, 1)
/*
console.log("After removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function ticked() {
// update link positions
link_group.select("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; });
// update node positions
node_group
.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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
Upvotes: 2