I got the a D3 v4 forced graph in place. Usually I load data from an external JSON file but I simplified the code for easier jfiddle import. The graph adds an node each time a node is clicked. Further the node will be added to the data_node array. So far so good.
My problem is, I can´t figure out how to store the new data into an local JSON file. The API´s from node.js seems not to work or I am simply blind. As I want to store the changes about the nodes permanent.
Any hint?
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<html>
<head>
<title>Index jFiddle</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>
<!-- load "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
font-family: "Open Sans", sans-serif;
}
.canvas {
background-color: rgb(220,220,220);
}
.link {
stroke: rgb(0,0,0);
stroke-width: 2px;
}
.node {
stroke: rgb(255,255,255);
stroke-width: 2px;
}
.icon {
fill: rgb(0,0,0);
pointer-events: none;
}
.node:hover{
cursor: pointer;
}
</style>
<body>
<!-- SVG area for the whole graph -->
<svg id="svg"> </svg>
<!-- call app.js where the application is written -->
<script>
// define different variables
var width = window.innerWidth
height = window.innerHeight
boolColor = true
boolOpacity = true
color = null
nodes = null
// define cavnas area to draw everything
var 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")
// Removes zoom on doubleclick listener
d3.select("svg").on("dblclick.zoom", null)
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce",d3.forceManyBody().strength(70));
// load data from json file
var data_nodes = [
{
"id": "00000",
"type": "company",
"name": "Test",
"context": "",
"icon": "\uf1ad"
},
{
"id": "00100",
"type": "software",
"name": "Jira",
"context":"Jira",
"icon": "\uf7b1",
"parent" : "00000"
},
{
"id": "00200",
"type": "software",
"name": "Confluence",
"context":"Confluence",
"icon": "\uf78d",
"parent" : "00000"
},
{
"id": "00300",
"type": "software",
"name": "IVIS",
"context":"IVIS",
"icon": "\ue084",
"parent" : "00000"
},
{
"id": "00400",
"type": "software",
"name": "IPOS",
"context":"IPOS",
"icon": "\ue084",
"parent" : "00000"
},
{
"id": "00500",
"type": "software",
"name": "IDAS",
"context":"IDAS",
"icon": "\ue084",
"parent" : "00000"
},
{
"id": "99997",
"type": "hardware",
"name": "power-plug",
"context": "power-plug",
"icon": "\uf1e6",
"parent" : "00000"
},
{
"id": "99998",
"type": "hardware",
"name": "usv",
"context": "usv",
"icon": "\uf5df",
"parent" : "00000"
},
]
var data_links = [
{"source": "99998", "target": "00000"},
{"source": "99997", "target": "00000"},
{"source": "00100", "target": "00000"},
{"source": "00200", "target": "00000"},
{"source": "00300", "target": "00000"},
{"source": "00400", "target": "00000"},
{"source": "00500", "target": "00000"},
]
// create links which visualize relationships
var links = svg.selectAll("svg")
.data(data_links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", 3)
.style("stroke-linecap", "round")
var nodes = svg.selectAll("svg")
.data(data_nodes)
.enter()
.append("circle")
.attr("r", 30)
.attr("class", "node")
.attr("fill", initialColor)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", click)
var icons = svg.selectAll("svg")
.data(data_nodes)
.enter()
//.append("g")
.append("text")
.attr("class", "icon")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("font-family","FontAwesome")
.style("font-size","30px")
.text(function (d) {return d.icon;})
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
simulation.nodes(data_nodes).on("tick", ticked);
simulation.force("link").links(data_links);
function ticked() {
// update link positions
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; });
// update node positions
nodes
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
// update icon positions
icons
.attr("x", function(d) {return d.x})
.attr("y", function(d) {return d.y})
}
function click(d) {
addNode(d)
}
function addNode(d) {
var id = Math.round(Math.random()*10000);
var newLink = {source: id, target: d.id }
data_links.push(newLink);
var obj = {
"id": id,
"type": "company",
"name": "Test1",
"context": "",
"icon": "\uf1ad"
}
data_nodes.push(obj)
// changes:
var newLink = {source: id, target: d.id }
data_links.push(newLink);
links = svg.selectAll("line")
.data(data_links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", 3)
.style("stroke-linecap", "round")
// disable browser context menu on link
.merge(links);
//append the new object:
nodes = svg.selectAll("circle")
.data(data_nodes)
.enter()
.append("circle")
.attr("r", 30)
.attr("class", "node")
.attr("fill", initialColor)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", click)
.merge(nodes);
icons = svg.selectAll("text")
.data(data_nodes)
.enter()
.append("text")
.attr("class", "icon")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("font-family","FontAwesome")
.style("font-size","30px")
.text(function (d) {return d.icon;})
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.merge(icons);
// re-initialize the simulation:
simulation.nodes(data_nodes);
simulation.force("link").links(data_links);
}
/*
Set the color of each node in dependency of their d.name attribute.
*/
function initialColor(d) {
switch (d.name) {
case "power-plug":
return "lightgreen"
case "usv":
return "orange"
default:
return "whitesmoke"
}
}
// Context menu
/*
dragStarted() / dragged() and dragEnded() controlling the drag behaviour of each
object. In case all drag events are not desired, simple comment out the .call(d3.drag())
execution during the object(node) creation
*/
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>
</body>
</html>