D3 layout with draggable nodes - d3.js

I have created a forced layout graph. it is working fine.
Now I need to make the blocks draggable such that, even after dragging the block it should not affect the other blocks as shown in this example.
Here's what I tried:
d3.json("links.json", function(error, links) {
var nodes = {};
// Compute the distinct nodes from the links.
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});
});
var width = 1000,
height = 700;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.gravity(0.01)
.size([width, height])
.linkDistance(200)
.charge(-600)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["forward","back"]) //use .data(["forward","back","front"]) for different types of data
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 100)
.attr("refY", 1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-4L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var rect = svg.append("g").selectAll("rect")
.data(force.nodes())
.enter().append("rect")
.attr("x", -20)
.attr("y", -20)
.attr("width", function(d) { return (d.weight*40); })
.attr("height", function(d) { return (d.weight*20); })
.call(force.drag).on("mouseover", fade(.1))
.on("mouseout", fade(1));;
force.drag().on('drag', fade(.1));
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 0)
.attr("y", ".41em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
rect.attr("transform", transform);
path.attr("d", linkLine);
text.attr("transform", transform);
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
});
</script>
</body>
</html>
And data in links.json file is
[{"source": "Delhi", "target": "Mangalore", "type": "forward"},
{"source": "Mangalore", "target": "Delhi", "type": "back"},
{"source": "Delhi", "target": "Yashvanthpur", "type": "back"},
{"source": "Mangalore", "target": "Rome", "type": "forward"},
{"source": "Delhi", "target": "Mysore", "type": "forward"},
{"source": "Chandigarh", "target": "Vellore", "type": "forward"},
{"source": "Chandigarh", "target": "Mangalore", "type": "forward"},
{"source": "Delhi", "target": "Nagpur", "type": "forward"}
]
Are there any methods so that, I will not get any movements in the other nodes, even though I drag some node in the graph?
Is there any separate method to accomplish that?

Set property fixed to true for each node when force simulations are stopped.
force.on('end', function(d) {
links.forEach(function(l) {
l.source.fixed = true;
l.target.fixed = true;
})
});
var links = [{
"source": "Delhi",
"target": "Mangalore",
"type": "forward"
},
{
"source": "Mangalore",
"target": "Delhi",
"type": "back"
},
{
"source": "Delhi",
"target": "Yashvanthpur",
"type": "back"
},
{
"source": "Mangalore",
"target": "Rome",
"type": "forward"
},
{
"source": "Delhi",
"target": "Mysore",
"type": "forward"
},
{
"source": "Chandigarh",
"target": "Vellore",
"type": "forward"
},
{
"source": "Chandigarh",
"target": "Mangalore",
"type": "forward"
},
{
"source": "Delhi",
"target": "Nagpur",
"type": "forward"
}
];
var nodes = {};
// Compute the distinct nodes from the links.
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
});
});
var width = 1000,
height = 700;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.gravity(0.01)
.size([width, height])
.linkDistance(200)
.charge(-600)
.on("tick", tick)
.start()
force.on('end', function(d) {
links.forEach(function(l) {
l.source.fixed = true;
l.target.fixed = true;
})
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["forward", "back"]) //use .data(["forward","back","front"]) for different types of data
.enter().append("marker")
.attr("id", function(d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 100)
.attr("refY", 1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-4L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) {
return "link " + d.type;
})
.attr("marker-end", function(d) {
return "url(#" + d.type + ")";
});
var rect = svg.append("g").selectAll("rect")
.data(force.nodes())
.enter().append("rect")
.attr("x", -20)
.attr("y", -20)
.attr("width", function(d) {
return (d.weight * 40);
})
.attr("height", function(d) {
return (d.weight * 20);
})
.call(force.drag).on("mouseover", fade(.1))
.on("mouseout", fade(1));;
force.drag().on('drag', fade(.1));
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("text-anchor", "middle")
.attr("x", function(d){ return -20+(d.weight * 20) })
.attr("y", function(d){ return -20+(d.weight * 20)/2 })
.text(function(d) {
return d.name;
});
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
rect.attr("transform", transform);
path.attr("d", linkLine);
text.attr("transform", transform);
}
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] ||
linkedByIndex[b.index + "," + a.index] ||
a.index == b.index;
};
function linkLine(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
// For fade-in and fade-out effect
function fade(opacity) {
return function(d) {
rect.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
rect.style("stroke-opacity", opacity)
.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
};
.link {
fill: none;
stroke: #666;
stroke-width: 2px;
}
#forward {
fill: green;
}
#back {
fill: red;
}
<!-- #back {
fill: green;
}
-->.link.forward {
stroke: green;
}
.link.back {
stroke: green;
}
rect {
fill: #a5b0ed;
stroke: #333;
stroke-width: 2px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

D3 Tree: How to make children checked and disabled recursively when the parent node is checked

I am working on an angular application with d3. My code is as follows.
var treeData = [{
"name": "MD",
"children": [{
"name": "Professional",
"children": [{
"name": "Third A",
"children": [{
"name": "Fourth A",
"children": [{
"name": "Fifth A"
}, {
"name": "Fifth B"
}, {
"name": "Fifth C"
}, {
"name": "Fifth D"
}]
}, {
"name": "Fourth B"
}, {
"name": "Fourth C"
}, {
"name": "Fourth D"
}]
}, {
"name": "Third B"
}]
}, {
"name": "Leader",
"children": [{
"name": "Third C"
}, {
"name": "Third D"
}]
}, {
"name": "Advocate",
"children": [{
"name": "Third E"
}, {
"name": "Third F"
}]
}, {
"name": "Clinician",
"children": [{
"name": "Third G"
}, {
"name": "Third H"
}, ]
}, ]
}];
var colourScale = d3.scale.ordinal()
.domain(["MD", "Professional", "Leader", "Advocate", "Clinician"])
.range(["#6695c8", "#cd3838", "#d48440", "#a8ba5f", "#63b7c0"]);
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1200 - margin.right - margin.left,
height = 650 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
console.log('UPDATE')
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 200;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "#C0C0C0" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
nodeEnter.append('foreignObject').attr('width', '20')
.attr("x", 10)
.attr("y", 1)
.attr('height', '20').append('xhtml:input')
.attr('type', 'checkbox')
.attr("id", d => `checkbox-${d.id}`)
//.attr("fill","none")
//.style("opacity","1")
// An on click function for the checkboxes
.on("click", d => {
if (d.children) {
d.children.forEach(child => {
const cb = d3.select(`#checkbox-${child.id}`);
//console.log('CB: ', cb.node());
cb.node().checked = d3.event.target.checked;
cb.attr('disabled', d3.event.target.checked ? true : null);
})
}
else {
if (d3.event.target.checked) {
d.parent.children.forEach(child => {
console.log('CID: ', child.id, d.id);
if (child.id !== d.id) {
const cb = d3.select(`#checkbox-${child.id}`);
console.log('CB: ', cb.node())
cb.node().checked = false;
}
});
}
}
d3.event.stopPropagation();
//console.log(d);
//console.log(d3.event.target.checked);
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.attr("fill-opacity", "0.7")
.attr("stroke-opacity", "1")
.style("fill", function(d) {
return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';
})
.style("stroke", function(d) {
return colourScale(findParent(d));
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", function(d) {
return 1;
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity", "0.3")
.style("stroke", function(d) {
return colourScale(findParentLinks(d));
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
function findParentLinks(datum) {
if (datum.target.depth < 2) {
return datum.target.name
} else {
return findParent(datum.target.parent)
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
I am facing following problem:
If a collapsed node has children, and its parent is getting checked, the node itself is checked as well, but its children cannot be checked because they are not visible.
The checking should work recursively: if a node is checked, all its descendants should be checked and disabled as well regardless their state (expanded or collapsed).
How can I do this?
Here is a proposed solution:
const treeData = {
"id": 1,
"name": "Root",
"checked": false,
"color": "white",
"children": [
{
"id": 2,
"name": "Leaf A",
"checked": false,
"color": "red",
"children": [
{
"id": 3,
"name": "A - 1",
"checked": false,
"color": "brown",
},
{
"id": 4,
"name": "A - 2",
"checked": false,
"color": "orange",
},
{
"id": 5,
"name": "A - 3",
"checked": false,
"color": "yellow",
},
]
},
{
"id": 6,
"name": "Leaf B",
"checked": false,
"color": "green",
"children": [
{
"id": 7,
"name": "B - 1",
"checked": false,
"color": "#00ff40",
},
{
"id": 8,
"name": "B - 2",
"checked": false,
"color": "#00ff80",
}
]
}
]
};
const margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
};
const width = 600 - margin.right - margin.left;
const height = 400 - margin.top - margin.bottom;
var i = 0,duration = 750;
const tree = d3.layout.tree()
.size([height, width]);
const diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
const svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
const container = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const root = treeData;
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Compute the new tree layout.
const nodes = tree.nodes(root).reverse();
const links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 200;
});
// Update the nodes…
const node = container.selectAll("g.node")
.data(nodes, d => d.id);
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${source.y0},${source.x0})`)
.on("click", onClickNode);
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", d => d.color);
nodeEnter.append("text")
.attr("x", 20)
.attr("dy", 4)
.attr("text-anchor", "start")
.text(d => d.name);
nodeEnter.append('foreignObject')
.attr('width', '20')
.attr('height', '20')
.attr("x", -30)
.attr("y", -8)
.append('xhtml:input')
.attr('type', 'checkbox')
.attr("id", d => `checkbox-${d.id}`)
.on("click", onClickCheckbox)
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// ???
nodeUpdate.select("circle")
.style("stroke", 'black');
nodeUpdate.each(function(d) {
const cb = d3.select(this).select('[type="checkbox"]').node();
cb.checked = d.checked;
cb.disabled = isParentChecked(d);
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
const nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = container.selectAll("path.link")
.data(links, d => d.target.id);
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", 1)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity", "0.3")
.style("stroke", 'black');
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
function findParentLinks(datum) {
if (datum.target.depth < 2) {
return datum.target.name
} else {
return findParent(datum.target.parent)
}
}
const checkNode = (d, checked, byParent) => {
if (d.id === 2)
console.log('CHECK TO: ', checked);
d.checked = checked;
const children = d.children || d._children;
if (children)
children.forEach(child => checkNode(child, checked, true));
if (!byParent && checked && d.parent) {
console.log('UNCHECK SIBLINGS');
const siblings = d.parent.children || d.parent._children;
siblings.forEach(sibling => {
if (sibling.id !== d.id) {
console.log('UNCHECK: ', sibling)
checkNode(sibling, false, true);
}
});
}
}
function isParentChecked (d) {
if (!d.parent) {
return false;
}
if (d.parent.checked) {
return true;
}
return isParentChecked(d.parent);
}
function onClickCheckbox(d) {
d3.event.stopPropagation();
checkNode(d, d3.event.target.checked, false);
console.log('ROOT: ', root);
update(root);
}
// Toggle children on click.
function onClickNode(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
else {
d.children = d._children;
d._children = null;
}
update(d);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

why the shape can't be dragged in d3.js

I want to link circles and rects by the shapes' name, it's all right.
I also remove the force layout, cause I want to use the static position.
but the drag function doesn't work, only the link can be dragged, not the Shapes. I dont konw the reason.
does someon help me review the code?
Thanks you very much.
var graph = {
"nodes":[
{
"appId":"AP110358",
"name":"Customer Account Profile",
"type":"CIRCLE",
"x":50,
"y":50
},
{
"appId":"NB",
"name":"NB",
"type":"CIRCLE",
"x":500,
"y":500
},
{
"appId":"AP114737",
"name":"RBG",
"type":"CIRCLE",
"x":300,
"y":600
},
{
"appId":"NULL",
"name":"Account",
"type":"RECT",
"x":400,
"y":700
}
],
"links":[
{
"source":"Customer Account Profile",
"target":"NB",
"value":1,
"label":null
},
{
"source":"NB",
"target":"RBG",
"value":1,
"label":null
},
{
"source":"RBG",
"target":"Customer Account Profile",
"value":1,
"label":null
},
{
"source":"NB",
"target":"Customer Account Profile",
"value":1,
"label":null
},
{
"source":"Customer Account Profile",
"target":"Account",
"value":1,
"label":null
},
{
"source":"NB",
"target":"Account",
"value":1,
"label":null
},
{
"source":"RBG",
"target":"Account",
"value":1,
"label":null
}
]
};
var width = window.innerWidth;
var height = window.innerHeight;
var center;
if(width > 1200){
center = [(width-1200) / 2, 0]
}else{
center = [0, 0]
}
var edges = [];
graph.links.forEach(function(e) {
var sourceNode = graph.nodes.filter(function(n) {
return n.name === e.source;
})[0],
targetNode = graph.nodes.filter(function(n) {
return n.name === e.target;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
value: e.value
});
});
var color = d3.scale.category20();
var svg = d3.select("body")
.append("svg")
.attr("width", "1200")
.attr("height", "1000")
.attr("transform", "translate(" + center + ")")
.append("g");
var container = svg.append("g");
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(edges)
.enter().append("line")
.attr("class", "link")
.attr("x1", function(l) {
var sourceNode = graph.nodes.filter(function(d) {
return d == l.source
})[0];
d3.select(this).attr("y1", sourceNode.y);
return sourceNode.x
})
.attr("x2", function(l) {
var targetNode = graph.nodes.filter(function(d) {
return d == l.target
})[0];
d3.select(this).attr("y2", targetNode.y);
return targetNode.x
})
.style("stroke-width", function(d) {
return d.value;
});
link.append("title").text(function(d) {
return d.value;
});
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("cx", d.x).attr("cy", d.y);
link.each(function(l) {
if (l.source == d) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == d) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drag);
var i = 0;
node.each(function(d) {
if (d.type == "CIRCLE") {
d3.select(this).append("circle")
.attr("r", 50)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.style("fill", function(d) { return color(i & 3);});
d3.select(this).append("text")
.text(function(d) {
return d.name;
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + (d.y+5) + ")";
})
.style("text-anchor","middle");
} else {
d3.select(this).append("rect")
.attr("height", 40)
.attr("width", 140)
.attr("x", -10)
.attr("y", -(40 / 2))
.attr("transform", function(d) {
return "translate(" + d.x + "," + (d.y+5) + ")";
})
.style("fill", "green");
d3.select(this).append("text")
.text(function(d) {
return d.name;
})
.style("text-anchor","start")
.attr("transform", function(d) {
return "translate(" + (d.x - 5) + "," + (d.y+10) + ")";
});
}
i++;
});
node.on("click", function(d){
console.log(d.x + "|--|" + d.y);
});
<style type="text/css">
.node {
stroke: #fff;
stroke-width: 1.5px;
cursor: move;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
</style>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body style="background-color: #42f4e5">
<!-- main -->
</body>
I know the reason
I call the drag method in the node, but the node doesn't append the shape, so the drag doesn't work.
so I change the drag invoke position as snippet
var graph = {
"nodes": [{
"appId": "AP110358",
"name": "Customer Account Profile",
"type": "CIRCLE",
"x": 50,
"y": 50
},
{
"appId": "NB",
"name": "NB",
"type": "CIRCLE",
"x": 500,
"y": 500
},
{
"appId": "AP114737",
"name": "RBG",
"type": "CIRCLE",
"x": 300,
"y": 600
},
{
"appId": "NULL",
"name": "Account",
"type": "RECT",
"x": 400,
"y": 700
}
],
"links": [{
"source": "Customer Account Profile",
"target": "NB",
"value": 1,
"label": null
},
{
"source": "NB",
"target": "RBG",
"value": 1,
"label": null
},
{
"source": "RBG",
"target": "Customer Account Profile",
"value": 1,
"label": null
},
{
"source": "NB",
"target": "Customer Account Profile",
"value": 1,
"label": null
},
{
"source": "Customer Account Profile",
"target": "Account",
"value": 1,
"label": null
},
{
"source": "NB",
"target": "Account",
"value": 1,
"label": null
},
{
"source": "RBG",
"target": "Account",
"value": 1,
"label": null
}
]
};
var width = window.innerWidth;
var height = window.innerHeight;
var center;
if (width > 1200) {
center = [(width - 1200) / 2, 0]
} else {
center = [0, 0]
}
var edges = [];
graph.links.forEach(function(e) {
var sourceNode = graph.nodes.filter(function(n) {
return n.name === e.source;
})[0],
targetNode = graph.nodes.filter(function(n) {
return n.name === e.target;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
value: e.value
});
});
var color = d3.scale.category20();
var svg = d3.select("body")
.append("svg")
.attr("width", "1200")
.attr("height", "1000")
.attr("transform", "translate(" + center + ")")
.append("g");
var container = svg.append("g");
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(edges)
.enter().append("line")
.attr("class", "link")
.attr("x1", function(l) {
var sourceNode = graph.nodes.filter(function(d) {
return d == l.source
})[0];
d3.select(this).attr("y1", sourceNode.y);
return sourceNode.x
})
.attr("x2", function(l) {
var targetNode = graph.nodes.filter(function(d) {
return d == l.target
})[0];
d3.select(this).attr("y2", targetNode.y);
return targetNode.x
})
.style("stroke-width", function(d) {
return d.value;
});
link.append("title").text(function(d) {
return d.value;
});
var drag = d3.behavior.drag()
.on("drag", function(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("cx", d.x).attr("cy", d.y);
link.each(function(l) {
if (l.source == d) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == d) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node");
var i = 0;
node.each(function(d) {
if (d.type == "CIRCLE") {
d3.select(this).append("circle")
.attr("r", 50)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.call(drag)
.style("fill", function(d) {
return color(i & 3);
});
d3.select(this).append("text")
.text(function(d) {
return d.name;
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + (d.y + 5) + ")";
})
.style("text-anchor", "middle");
} else {
d3.select(this).append("rect")
.attr("height", 40)
.attr("width", 140)
.attr("x", -10)
.attr("y", -(40 / 2))
.attr("transform", function(d) {
return "translate(" + d.x + "," + (d.y + 5) + ")";
})
.style("fill", "green");
d3.select(this).append("text")
.text(function(d) {
return d.name;
})
.style("text-anchor", "start")
.attr("transform", function(d) {
return "translate(" + (d.x - 5) + "," + (d.y + 10) + ")";
});
}
i++;
});
node.on("click", function(d) {
console.log(d.x + "|--|" + d.y);
});
<style type="text/css">.node {
stroke: #fff;
stroke-width: 1.5px;
cursor: move;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js"></script>
<body style="background-color: #42f4e5">
<!-- main -->
</body>

Collapsible Radial Tree in d3 v4

I am new to d3 and I am trying to create a collapsible version of https://bl.ocks.org/mbostock/4063550
in v4. There are various examples that explain the same in v3, but I couldn't find a proper one for v4.
I implemented the onClick function in which the children opens up, but the issue is that the links get misplaced after the click. they shift to the right, and the nodes remain at the same position.
Please find below my code for the update function:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
<!--
.node circle {
//fill: #999;
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 10px sans-serif;
}
.node--internal circle {
fill: #555;
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
-->
.node {
cursor: pointer;
}
.node circle {
fill: #999;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var width = 960,
height = 1000,
duration = 750;
var nodes,links;
var i = 0;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var g = svg.append("g").attr("transform", "translate(" + (width / 2 + 40) + "," + (height / 2 + 90) + ")");
function connector(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y)
/*
return "M" + d.y + "," + d.x +
"C" + (d.y + d.parent.y) / 2 + "," + d.x +
" " + (d.y + d.parent.y) / 2 + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x; */
}
var treeMap = d3.tree()
.size([360,250]),
root;
var nodeSvg, linkSvg, nodeEnter, linkEnter ;
d3.json("treeData.json",function(error,treeData){
if(error) throw error;
root = d3.hierarchy(treeData,function(d){
return d.children;
});
root.each(function (d) {
console.log(d);
d.name = d.data.name; //transferring name to a name variable
d.id = i; //Assigning numerical Ids
i += i;
});
root.x0 = height / 2;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
//root.children.forEach(collapse);
update(root);
});
function update(source) {
//root = treeMap(root);
nodes = treeMap(root).descendants();
//console.log(nodes);
//links = root.descendants().slice(1);
links = nodes.slice(1);
//console.log(links);
var nodeUpdate;
var nodeExit;
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
nodeSvg = g.selectAll(".node")
.data(nodes,function(d) { return d.id || (d.id = ++i); });
//nodeSvg.exit().remove();
var nodeEnter = nodeSvg.enter()
.append("g")
//.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; })
//.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click",click)
.on("mouseover", function(d) { return "minu"; });
nodeEnter.append("circle")
.attr("r", 5)
.style("fill", color);
nodeEnter.append("text")
.attr("dy", ".31em")
//.attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; })
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.style("text-anchor", function(d) { return d.x < 180 === !d.children ? "start" : "end"; })
//.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90 : d.x + 90) + ")"; })
.text(function(d) { return d.data.name; });
// Transition nodes to their new position.
var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
.duration(duration);
// .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeSvg.select("circle")
.style("fill", color);
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = nodeSvg.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) //for the animation to either go off there itself or come to centre
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
linkSvg = g.selectAll(".link")
.data(links, function(link) { var id = link.id + '->' + link.parent.id; return id; });
// Transition links to their new position.
linkSvg.transition()
.duration(duration);
// .attr('d', connector);
// Enter any new links at the parent's previous position.
linkEnter = linkSvg.enter().insert('path', 'g')
.attr("class", "link")
.attr("d", function(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
});
/*
function (d) {
var o = {x: source.x0, y: source.y0, parent: {x: source.x0, y: source.y0}};
return connector(o);
});*/
// Transition links to their new position.
linkSvg.merge(linkEnter).transition()
.duration(duration)
.attr("d", connector);
// Transition exiting nodes to the parent's new position.
linkSvg.exit().transition()
.duration(duration)
.attr("d", /*function (d) {
var o = {x: source.x, y: source.y, parent: {x: source.x, y: source.y}};
return connector(o);
})*/function(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
})
.remove();
// Stash the old positions for transition.
}
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function color(d) {
return d._children ? "#3182bd" // collapsed package
: d.children ? "#c6dbef" // expanded package
: "#fd8d3c"; // leaf node
}
function flatten (root) {
// hierarchical data to flat data for force layout
var nodes = [];
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
else ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
function project(x, y) {
var angle = (x - 90) / 180 * Math.PI, radius = y;
return [radius * Math.cos(angle), radius * Math.sin(angle)];
}
</script>
</body>
</html>
treeData.json:
{
"name": "United States",
"children": [
{
"name": "Arizona",
"children":[
{ "name" : "Arizona Airport", "size": 13}
]
},
{
"name": "California",
"children":[
{"name": "San Francisco","size":15},
{"name": "San Jose","size":25},
{"name": "Los Angeles","size":17}
]
},
{
"name": "Illinois",
"children":[
{ "name" : "Chicago O'Hare", "size": 13},
{ "name" : "Midway", "size": 18 }
]
},
{
"name": "Colorado",
"children" : [
{ "name": "Denver","size": 7}
]
},
{
"name": "Florida","size":2
},
{
"name": "Georgia", "size": 25
},
{
"name": "Kentucky","size":2
},
{
"name": "Massachussets", "size": 25
},
{
"name": "Michigan","size":2
},
{
"name": "Minnesota", "size": 25
},
{
"name": "Missouri","size":2
},
{
"name": "North Carolina", "size": 25
},
{
"name": "Nevada","size":2
},
{
"name": "Newyork", "size": 12
},
{
"name": "Oregon","size":2
},
{
"name": "Pennsylvania", "size": 25
},
{
"name": "Washington",
"children": [
{ "name" : "Seattle","size" : 13}
]
},
{
"name": "Hawaii", "size": 25
},
{
"name": "Texas",
"children" : [
{ "name": "Dallas" ,"size": 9},
{ "name": "Houston" ,"size": 13},
{ "name": "Austin" ,"size": 17}
]
},
{
"name": "Utah", "size": 25
},
{
"name": "Virginia", "size": 25
}
]
}
#Minu didn't update the code in the question, so here's what I did to get it to work:
Just change:
// Transition nodes to their new position.
var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
.duration(duration);
to this:
// Transition nodes to their new position.
var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; });
Hopefully this is helpful to someone else who wants to make the radial tree collapsible.

How to remove node by value on d3 tree?

I would like to remove nodes that have 0 as "cardinalita".
For now I have hidden nodes but I would like remove empty link.
Here the situation:
https://jsfiddle.net/d65k3zzy/
var treeData = [
{
"name": "First",
"parent": "null",
"cardinalita": "9",
"children": [
{
"name": "Second",
"parent": "First",
"cardinalita": "1",
"children": [
{
"name": "Third",
"parent": "Second",
"cardinalita": "63",
"children": [
{
"name": "fourth",
"parent": "Third",
"cardinalita": "39",
"children": [
{
"name": "last",
"parent": "fourth",
"cardinalita": "70",
"children": [
{
"name": "special",
"parent": "last",
"cardinalita": "11"
}
]
},
{
"name": "special",
"parent": "fourth",
"cardinalita": "0"
}
]
},
{
"name": "null",
"parent": "Third",
"cardinalita": "0",
"children": [
{
"name": "special",
"parent": "null",
"cardinalita": "10"
}
]
}
]
},
{
"name": "Third",
"parent": "Second",
"cardinalita": "528"
}
]
},
{
"name": "Second",
"parent": "First",
"cardinalita": "33",
"children": [
{
"name": "Third",
"parent": "Second",
"cardinalita": "63"
}
]
}
]
}
];
// ************** Generate the tree diagram *****************
var margin = {top: 0, right: 120, bottom: 40, left: 60},
width = 1000 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
// distanza fra i nodi figli
.separation(function separation(a, b) { return a.parent == b.parent ? 1.5 : 1; })
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("#diagramma").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 160; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
//.on("click", click);
// contenitore testo
nodeEnter.append("svg:rect")
.attr("width", function(d) { if(d.name != 'null' || (d.cardinalita < 1)){return "70";} })
.attr("height", function (d) {
return 21;
})
.attr("r", 1e-6)
.attr("y", -10)
.attr("x", -50)
.attr("rx", 0)
.attr("ry", 0)
.attr("stroke", function(d) { if(d.name != 'null' && d.cardinalita >= 1){return "#23527c";} })
.attr("stroke-width", "2")
.style("fill", function (d) {
return d._children ? "#ccc" : "#fff";
})
.style("fill-opacity", function(d) { if(d.name == 'null' || (d.cardinalita < 1)){return "0";} })
//contenitore cardinalità
nodeEnter.append("svg:rect")
.attr("width", 25)
.attr("height", function (d) {
return 21;
})
.attr("r", 1e-6)
.attr("y", 11)
.attr("x", -5)
.attr("rx", 0)
.attr("ry", 0)
.attr("stroke", function(d) { if(d.name != 'null' && d.cardinalita >= 1){return "#23527c";} })
.attr("stroke-width", "2")
.style("fill", "#fff")
.style("fill-opacity", function(d) { if(d.name == 'null' || (d.cardinalita < 1)){return "0";} })
// nome del nodo
nodeEnter.append("text")
.attr("x", function (d) {
return d._children ? -8 : 8;
})
.attr("y", -4)
.attr("x", -40)
.attr("dy", "0.7em")
.style("font-size", "14")
.style("font-weight", "bold")
.text(function (d) {
if(d.name != 'null' && d.cardinalita != 0)
return d.name;
});
// cardinalità
nodeEnter.append("text")
.attr("x", function (d) {
return d._children ? -8 : 8;
})
.attr("y", 18)
.attr("x", -2)
.attr("dy", "0.7em")
.style("font-size", "14")
.text(function (d) {
if(d.name != 'null' && d.cardinalita >= 1)
return d.cardinalita;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("rect")
.attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("rect")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
header { padding: 20px; }
section { overflow: hidden; width: 1000px; margin: 0 auto;}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
#diagramma { display: inline; float: left; }
.map { display: inline; float: right; }
.p {
font-family: Arial, sans-serif;
text-align: center;
}
.map svg {
height: auto;
width: auto;
min-width: 300px;
max-width: 400px;
margin: 0 auto;
display: block;
}
.map g {
fill: #ccc;
stroke: #fff;
stroke-width: 2;
}
.map g:hover, g.active, .st0:hover, .active .st0 {
fill: #23527c !important;
cursor: help;
cursor: pointer;
}
.info_panel {
background-color: rgba(255,255,255, .8);
padding: 5px;
font-size: 12px;
font-family: Helvetica, Arial, sans-serif;
position: absolute;
border: 1px solid #333;
color: #333;
white-space: nowrap;
}
.info_panel::first-line {
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<section>
<div id="diagramma">
</div>
</section>
</body>
I think you really want to remove links that have cardinalita === "0" and have no children:
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d){
if (!d.target.children && d.target.cardinalita === "0")
return "M0,0"; //<-- draw nothing
else
return diagonal(d);
});
Full code:
var treeData = [
{
"name": "First",
"parent": "null",
"cardinalita": "9",
"children": [
{
"name": "Second",
"parent": "First",
"cardinalita": "1",
"children": [
{
"name": "Third",
"parent": "Second",
"cardinalita": "63",
"children": [
{
"name": "fourth",
"parent": "Third",
"cardinalita": "39",
"children": [
{
"name": "last",
"parent": "fourth",
"cardinalita": "70",
"children": [
{
"name": "special",
"parent": "last",
"cardinalita": "11"
}
]
},
{
"name": "special",
"parent": "fourth",
"cardinalita": "0"
}
]
},
{
"name": "null",
"parent": "Third",
"cardinalita": "0",
"children": [
{
"name": "special",
"parent": "null",
"cardinalita": "10"
}
]
}
]
},
{
"name": "Third",
"parent": "Second",
"cardinalita": "528"
}
]
},
{
"name": "Second",
"parent": "First",
"cardinalita": "33",
"children": [
{
"name": "Third",
"parent": "Second",
"cardinalita": "63"
}
]
}
]
}
];
// ************** Generate the tree diagram *****************
var margin = {top: 0, right: 120, bottom: 40, left: 60},
width = 1000 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
// distanza fra i nodi figli
.separation(function separation(a, b) { return a.parent == b.parent ? 1.5 : 1; })
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("#diagramma").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 160; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
//.on("click", click);
// contenitore testo
nodeEnter.append("svg:rect")
.attr("width", function(d) { if(d.name != 'null' || (d.cardinalita < 1)){return "70";} })
.attr("height", function (d) {
return 21;
})
.attr("r", 1e-6)
.attr("y", -10)
.attr("x", -50)
.attr("rx", 0)
.attr("ry", 0)
.attr("stroke", function(d) { if(d.name != 'null' && d.cardinalita >= 1){return "#23527c";} })
.attr("stroke-width", "2")
.style("fill", function (d) {
return d._children ? "#ccc" : "#fff";
})
.style("fill-opacity", function(d) { if(d.name == 'null' || (d.cardinalita < 1)){return "0";} })
//contenitore cardinalità
nodeEnter.append("svg:rect")
.attr("width", 25)
.attr("height", function (d) {
return 21;
})
.attr("r", 1e-6)
.attr("y", 11)
.attr("x", -5)
.attr("rx", 0)
.attr("ry", 0)
.attr("stroke", function(d) { if(d.name != 'null' && d.cardinalita >= 1){return "#23527c";} })
.attr("stroke-width", "2")
.style("fill", "#fff")
.style("fill-opacity", function(d) { if(d.name == 'null' || (d.cardinalita < 1)){return "0";} })
// nome del nodo
nodeEnter.append("text")
.attr("x", function (d) {
return d._children ? -8 : 8;
})
.attr("y", -4)
.attr("x", -40)
.attr("dy", "0.7em")
.style("font-size", "14")
.style("font-weight", "bold")
.text(function (d) {
if(d.name != 'null' && d.cardinalita != 0)
return d.name;
});
// cardinalità
nodeEnter.append("text")
.attr("x", function (d) {
return d._children ? -8 : 8;
})
.attr("y", 18)
.attr("x", -2)
.attr("dy", "0.7em")
.style("font-size", "14")
.text(function (d) {
if(d.name != 'null' && d.cardinalita >= 1)
return d.cardinalita;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("rect")
.attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("rect")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link");
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d){
if (!d.target.children && d.target.cardinalita === "0")
return "M0,0";
else
return diagonal(d);
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
header { padding: 20px; }
section { overflow: hidden; width: 1000px; margin: 0 auto;}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
#diagramma { display: inline; float: left; }
.map { display: inline; float: right; }
.p {
font-family: Arial, sans-serif;
text-align: center;
}
.map svg {
height: auto;
width: auto;
min-width: 300px;
max-width: 400px;
margin: 0 auto;
display: block;
}
.map g {
fill: #ccc;
stroke: #fff;
stroke-width: 2;
}
.map g:hover, g.active, .st0:hover, .active .st0 {
fill: #23527c !important;
cursor: help;
cursor: pointer;
}
.info_panel {
background-color: rgba(255,255,255, .8);
padding: 5px;
font-size: 12px;
font-family: Helvetica, Arial, sans-serif;
position: absolute;
border: 1px solid #333;
color: #333;
white-space: nowrap;
}
.info_panel::first-line {
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<body>
<section>
<div id="diagramma">
</div>
</section>
</body>

d3.js static force layout

I am using d3.js and would like the force layout to be NOT random every time I load up the page.
I have already read over a few questions and I set the node position but the layout still is random.
Just set static x and y positions in your node objects.
var graph = {
"nodes": [{
"name": "1",
"rating": 90,
"id": 2951,
x: 5, //Any random value
y: 10 //Any random value
}, {
"name": "2",
"rating": 80,
"id": 654654,
x: 15,
y: 20
------------------
-------------------
}],
"links": [{
"source": 5,
"target": 2,
"value": 6,
"label": "publishedOn"
}, {
"source": 1,
"target": 5,
"value": 6,
"label": "publishedOn"
},
------------------
-------------------
}
Here is the working code snippet.
var graph = {
"nodes": [{
"name": "1",
"rating": 90,
"id": 2951,
x: 5,
y: 10
}, {
"name": "2",
"rating": 80,
"id": 654654,
x: 15,
y: 20
}, {
"name": "3",
"rating": 80,
"id": 6546544,
x: 5,
y: 60
}, {
"name": "4",
"rating": 1,
"id": 68987978,
x: 55,
y: 17
}, {
"name": "5",
"rating": 1,
"id": 9878933,
x: 24,
y: 70
}, {
"name": "6",
"rating": 1,
"id": 6161,
x: 35,
y: 10
}],
"links": [{
"source": 5,
"target": 2,
"value": 6,
"label": "publishedOn"
}, {
"source": 1,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 4,
"target": 5,
"value": 4,
"label": "containsKeyword"
}, {
"source": 2,
"target": 3,
"value": 3,
"label": "containsKeyword"
}, {
"source": 3,
"target": 2,
"value": 4,
"label": "publishedBy"
}]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("#map").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
//d3.json('http://blt909.free.fr/wd/map2.json', function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.call(drag);
node.append("circle")
.attr("r", function(d) {
return d.weight * 2 + 12;
})
.style("fill", function(d) {
return color(1 / d.rating);
});
force.on("tick", function() {
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 + ")";
});
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("mouseover", function(d) {
node.classed("node-active", function(o) {
thisOpacity = isConnected(d, o) ? true : false;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.classed("link-active", function(o) {
return o.source === d || o.target === d ? true : false;
});
d3.select(this).classed("node-active", true);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", (d.weight * 2 + 12) * 1.5);
})
.on("mouseout", function(d) {
node.classed("node-active", false);
link.classed("link-active", false);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", d.weight * 2 + 12);
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node-active{
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
#map{
border: 2px #555 dashed;
width:500px;
height:400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>

Resources