Reputation: 31
I found a nice FD Graph script to add and remove nodes interactively, written in D3 v3. (See http://bl.ocks.org/tgk/6068367). I tried to port it to v5, but it's not working. I build the following script to compare the two versions. Any example i found, just switch the links data (see https://bl.ocks.org/colbenkharrl/21b3808492b93a21de841bc5ceac4e47), but not the nodes data.
Can anyone help?
Please see line 34 of the code to switch the D3 Version!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
html {
background-color: black;
}
svg {
background-color: white;
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
</style>
<body>
<script>
let version = 5;
let loader = 'document.write("<' + 'script src=\\"https://d3js.org/d3.v' + version + '.min.js\\"></' +
'script>' +
'");';
eval(loader);
</script>
<script>
!(function () {
'use strict';
// - - - - - - - - - -
// 1. Global declaration
// - - - - - - - - - -
let
fill, simulation, svg, nodes, links, node, link, cursor,
i = false,
canvas = {},
dataset = {};
// Starting with an empty dataset
dataset.nodes = [{}];
dataset.links = [{}];
canvas = {
width: 800,
height: 400
};
// - - - - - - - - - -
// 2. Functions
// - - - - - - - - - -
// 2.1. Eventhandler functions
// - - - - - - - - - -
function onCanvasMousemove() {
cursor
.attr("transform", "translate(" + d3.mouse(this) + ")");
}
function onCanvasMousedown() {
let point = d3.mouse(this),
node = {
x: point[0],
y: point[1]
},
n = nodes.push(node);
// add links to any nearby nodes
nodes
.forEach(function (target) {
let x = target.x - node.x,
y = target.y - node.y;
if (Math.sqrt(x * x + y * y) < 30) {
links.push({
source: node,
target: target
});
}
});
restartSimulation();
}
function onNodeMousedown(d, i) {
nodes.splice(i, 1);
links = links.filter(function (l) {
return l.source !==
d && l.target !== d;
});
d3.event.stopPropagation();
restartSimulation();
}
function onTicked() {
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) {
if (i === false) {
console.log(typeof node);
console.log(typeof d);
i = true;
}
return d.x;
}).attr("cy", function (d) {
return d.y;
});
}
// - - - - - - - - - -
// 2.2 Setter functions
// - - - - - - - - - -
function setSvg() {
svg = d3.select("body")
.append("svg")
.attr("width", canvas.width)
.attr("height", canvas.height)
.on("mousemove", onCanvasMousemove)
.on("mousedown", onCanvasMousedown);
svg.append("rect")
.attr("width", canvas.width)
.attr("height", canvas.height);
}
function setCursor() {
// create the cursor
cursor = svg.append("circle")
.attr("r", 30)
.attr("transform", "translate(-100,-100)")
.attr("class", "cursor");
}
function setColorScheme() {
switch (version) {
case 3:
fill = d3.scale.category20();
break;
case 5:
d3.scaleOrdinal(d3.schemeCategory10);
break;
}
}
/** Create the force direction graph
*/
function setSimulation() {
switch (version) {
case 3:
simulation = d3.layout
.force()
.size([canvas.width, canvas.height])
.nodes(dataset.nodes) // ! data, initialize with a single node
.linkDistance(30)
.charge(-60)
.on("tick", onTicked);
break;
case 4:
case 5:
simulation = d3.forceSimulation()
.nodes(dataset.nodes)
.force('link', d3.forceLink().distance(30))
.force('charge',
d3.forceManyBody()
.strength(-60))
.force('center', d3.forceCenter(canvas.width / 2, canvas.height / 2))
.on('tick', onTicked);
break;
}
restartSimulation();
}
function restartSimulation() {
// - - - - - - - - - -
// NODES
// - - - - - - - - - -
switch (version) {
case 3:
node = svg.selectAll('.node');
nodes = simulation.nodes();
node = node.data(nodes);
node.exit().remove();
node.enter()
.insert("circle", ".cursor")
.attr("class", "node")
.attr("r", 5)
.on("mousedown", onNodeMousedown);
break;
case 4:
case 5:
node = svg.selectAll('.node');
nodes = simulation.nodes();
node = node.data(nodes);
node.exit().remove();
node.enter()
.insert("circle", ".cursor")
.attr("class", "node")
.attr("r", 5)
.merge(node)
.on("mousedown", onNodeMousedown);
break;
}
// - - - - - - - - - -
// LINKS
// - - - - - - - - - -
switch (version) {
case 3:
link = svg.selectAll('.link');
links = simulation.links();
link = link.data(links);
link.exit().remove();
link.enter()
.insert("line", ".node")
.attr("class", "link")
break;
case 4:
case 5:
links = svg.selectAll('.link')
link = link.data(links);
link.exit().remove();
link.enter()
.append("line", ".node")
.attr("class", "link")
.merge(link);
break;
}
switch (version) {
case
3:
simulation.start();
break;
case 5:
simulation.alphaTarget(0.3).restart();
break;
}
}
// - - - - - - - - - -
// 2.3 Control functions
// - - - - - - - - - -
function main() {
setSvg();
setSimulation();
setCursor();
setColorScheme();
}
function init() {
main();
};
// - - - - - - - - - -
// 3. Main control
// - - - - - - - - - -
window.addEventListener('load', init);
// - - - - - - - - - -
}())
</script>
Upvotes: 1
Views: 2694
Reputation: 31
Ok, I got it. The crucial note was https://bl.ocks.org/tezzutezzu/cd04b3f1efee4186ff42aae66c87d1a7.
There had been a lot of tiny refacotring with variables, feel free to diff the before and after. After removing the v5->merge() in my first version and clearing up the variables it works for D3v3 und v5. And it is not necessary to work with different datasets, as i thought first, one is enough. Another thing is to write
nodeElements = svg.selectAll(".node").data(dataset.nodes, function (d) {
return d.id
});
instead of
nodeElements = svg.selectAll(".node");
nodeElements.data(dataset.nodes, function (d) {
return d.id
});
Here is the code, so far. I also updated the codepen example.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
html {
background-color: black;
}
svg {
background-color: white;
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
</style>
<body>
<script>
let version = 5;
let loader = 'document.write("<' + 'script src=\\"https://d3js.org/d3.v' + version + '.min.js\\"></' +
'script>' +
'");';
eval(loader);
</script>
<script>
!(function () {
'use strict';
// - - - - - - - - - -
// 1. Global declaration
// - - - - - - - - - -
let
fill, simulation, svg, nodes, links, node, link, cursor,
nodeElements, linkElements,
i = false,
canvas = {},
dataset = {};
dataset.nodes = [];
dataset.links = [];
canvas = {
width: 800,
height: 400
};
// - - - - - - - - - -
// 2. Functions
// - - - - - - - - - -
// 2.1. Eventhandler functions
// - - - - - - - - - -
function onCanvasMousemove() {
cursor
.attr("transform", "translate(" + d3.mouse(this) + ")");
}
function onCanvasMousedown() {
let
point = d3.mouse(this),
node = {
id: String.fromCharCode(Math.floor(Math.random() * 24) + 65),
x: point[0],
y: point[1]
};
dataset.nodes.push(node);
// add links to any nearby nodes
dataset.nodes
.forEach(function (target) {
let x = target.x - node.x,
y = target.y - node.y;
if (Math.sqrt(x * x + y * y) < 30) {
dataset.links.push({
source: node,
target: target
});
}
});
startSimulation();
}
function onNodeMousedown(d, i) {
dataset.nodes.splice(i, 1);
dataset.links = dataset.links.filter(function (l) {
return l.source !==
d && l.target !== d;
});
d3.event.stopPropagation();
startSimulation();
}
function onTicked() {
let nodeElements = svg.selectAll('.node');
let linkElements = svg.selectAll('.link');
linkElements
.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;
});
nodeElements
.attr("cx", function (d) {
if (i === false) {
console.log(typeof node);
console.log(typeof d);
i = true;
}
return d.x;
}).attr("cy", function (d) {
return d.y;
});
}
// - - - - - - - - - -
// 2.2 Setter functions
// - - - - - - - - - -
function setSvg() {
svg = d3.select("body")
.append("svg")
.attr("width", canvas.width)
.attr("height", canvas.height)
.on("mousemove", onCanvasMousemove)
.on("mousedown", onCanvasMousedown);
svg.append("rect")
.attr("width", canvas.width)
.attr("height", canvas.height);
}
function setCursor() {
// create the cursor
cursor = svg.append("circle")
.attr("r", 30)
.attr("transform", "translate(-100,-100)")
.attr("class", "cursor");
}
function setColorScheme() {
switch (version) {
case 3:
fill = d3.scale.category20();
break;
case 5:
d3.scaleOrdinal(d3.schemeCategory10);
break;
}
}
/** Create the force direction graph
*/
function setSimulation() {
switch (version) {
case 3:
simulation = d3.layout
.force()
.size([canvas.width, canvas.height])
.nodes(dataset.nodes) // ! data, initialize with a single node
.linkDistance(30)
.charge(-60)
.on("tick", onTicked);
break;
case 4:
case 5:
simulation = d3.forceSimulation()
// .nodes(dataset.nodes)
.force('link', d3.forceLink()
.distance(200)
.strength(0.6)
)
.force('charge',
d3.forceManyBody()
.strength(-60)
)
.force("x", d3.forceX(canvas.width / 2))
.force("y", d3.forceY(canvas.height / 2))
// .force('center', d3.forceCenter(canvas.width / 2, canvas.height / 2))
.on('tick', onTicked);
break;
}
startSimulation();
}
function startSimulation() {
// - - - - - - - - - -
// NODES
// - - - - - - - - - -
switch (version) {
case 3:
nodeElements = svg.selectAll('.node');
dataset.nodes = simulation.nodes();
node = nodeElements.data(dataset.nodes);
node.exit().remove();
node.enter()
.insert("circle", ".cursor")
.attr("class", "node")
.attr("r", 5)
.on("mousedown", onNodeMousedown);
break;
case 4:
case 5:
nodeElements = svg.selectAll(".node")
.data(dataset.nodes, function (d) {
return d.id
});
nodeElements.enter()
.append("circle")
.attr("class", function (d) {
return "node " + d.id;
})
.attr("r", 8)
.on("mousedown", onNodeMousedown);
nodeElements.exit().remove();
simulation.nodes(dataset.nodes)
simulation.force("link").links(dataset.links)
simulation.restart();
break;
}
// - - - - - - - - - -
// LINKS
// - - - - - - - - - -
switch (version) {
case 3:
link = svg.selectAll('.link');
links = simulation.links();
link = link.data(links);
link.exit().remove();
link.enter()
.insert("line", ".node")
.attr("class", "link")
break;
case 4:
case 5:
linkElements = svg
.selectAll('.link')
.data(dataset.links);
linkElements.exit().remove();
linkElements.enter()
.append("line", ".node")
.attr("class", "link");
break;
}
switch (version) {
case
3:
simulation.start();
break;
case 5:
simulation.alphaTarget(0.3).restart();
break;
}
}
// - - - - - - - - - -
// 2.3 Control functions
// - - - - - - - - - -
function main() {
setSvg();
setSimulation();
setCursor();
setColorScheme();
}
function init() {
main();
};
// - - - - - - - - - -
// 3. Main control
// - - - - - - - - - -
window.addEventListener('load', init);
// - - - - - - - - - -
}())
</script>
Upvotes: 2