Related
I am in need to implement a various link distance. I added a function call during the initialization of the force / simulation. Still, it looks like the settings are ignored. I want to add another node very close, that barely a hair would fit between. Is this even possible?
I noticed a similar question on stackoverflow but this requires another plugin, furthermore this question was created 4 years ago. Maybe vanilla d3.js already added the feature.
var graph = {
"nodes": [
{
"id": 0
},
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"links": [
{
"source": 0,
"target": 1,
"distance": 0.1
},
{
"source": 0,
"target": 2,
"distance": 150
},
{
"source": 0,
"target": 3,
"distance": 20
}
]
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var sourceNode;
var force = d3.forceSimulation()
//.force("link", d3.forceLink().id(function (d) { return d.id }).distance(80))
.force("link", d3.forceLink().distance(linkDistance).strength(0.1))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(90))
function linkDistance(d) {
console.log(d.distance)
return d.distance;
}
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("stroke-width", 1)
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", addNode)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 15)
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function ticked() {
// update link positions
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;
});
// update node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
PosX = d.x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
function addNode(event, d) {
const nodeId = graph.nodes.length
graph.nodes.push({id: nodeId})
graph.links.push({source: d, target: nodeId, distance: 1})
initialize()
}
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {][1]
opacity: 0.1;
transition: 0.3s opacity;
}
.highlight {
opacity: 1;
}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
</style>
<body>
<svg id="svg"></svg>
</body>
The distance function is correctly set and specified.
Problem comes from the forceCollide, which is set to a radius of 90, and pushes the nodes apart regardless of specified distance.
Decreasing the radius of forceCollide fixes the problem, as illustrated below :)
var graph = {
"nodes": [
{
"id": 0
},
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"links": [
{
"source": 0,
"target": 1,
"distance": 0.1
},
{
"source": 0,
"target": 2,
"distance": 150
},
{
"source": 0,
"target": 3,
"distance": 20
}
]
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var sourceNode;
var force = d3.forceSimulation()
//.force("link", d3.forceLink().id(function (d) { return d.id }).distance(80))
.force("link", d3.forceLink().distance(linkDistance).strength(.1))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(9))
function linkDistance(d) {
console.log('distance: ', d.distance)
return d.distance;
}
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("stroke-width", 1)
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", addNode)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 15)
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function ticked() {
// update link positions
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;
});
// update node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
PosX = d.x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
function addNode(event, d) {
const nodeId = graph.nodes.length
graph.nodes.push({id: nodeId})
graph.links.push({source: d, target: nodeId, distance: 1})
initialize()
}
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {][1]
opacity: 0.1;
transition: 0.3s opacity;
}
.highlight {
opacity: 1;
}
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
<svg id="svg"></svg>
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>
I have created a horizontal tree diagram as shown in below image. I want straight lines between nodes. The curved lines between nodes are default in d3 js. I saw some answers on google for this but did not found any satisfactory result. So is it possible to draw straight lines between nodes in d3 js? If yes then how can I do that?
enter image description here
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
.node circle {
fill: #ff9900;
stroke: #ff9900;
stroke-width: 1px;
}
.node text {
font: 16px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<script language="javascript">
var treeData = [{
"name": "1",
"parent": "null",
"children": [{
"name": "2",
"parent": "Persons",
"children": [{
"name": "3",
"parent": "Country of residence"
}, {
"name": "4",
"parent": "Country of residence"
}, {
"name": "5",
"parent": "Country of residence"
}, {
"name": "6",
"parent": "Country of residence"
}]
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree().size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.lx;
})
.y(function(d) {
return d.ly;
});
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];
update(root);
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 * 180;
});
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.append("circle")
.attr("r", 40)
.style("fill", "#ff9900");
// append icon inside circle
nodeEnter.append("image")
.attr("xlink:href", "http://localhost/d3/user2.jpg")
.attr("x", "-18px")
.attr("y", "-18px")
.attr("width", "35px")
.attr("height", "35px");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -40 : -50;
})
.attr("y", function(d) {
return d.children || d._children ? 55 : 55;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "start" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
</script>
</body>
</body>
</html>
Define your line :
var line = d3.svg.line()
.x(function(d) {
return d.y; // because tree is horizontal
})
.y(function(d) {
return d.x; // because tree is horizontal
});
Change your links function to this because d3.svg.line() takes array of points as argument
Hope this helps
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
return line([d.source, d.target]);
});
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
.node circle {
fill: #ff9900;
stroke: #ff9900;
stroke-width: 1px;
}
.node text {
font: 16px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<script language="javascript">
var treeData = [{
"name": "1",
"parent": "null",
"children": [{
"name": "2",
"parent": "Persons",
"children": [{
"name": "3",
"parent": "Country of residence"
}, {
"name": "4",
"parent": "Country of residence"
}, {
"name": "5",
"parent": "Country of residence"
}, {
"name": "6",
"parent": "Country of residence"
}]
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree().size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.y;
})
.y(function(d) {
return 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];
update(root);
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 * 180;
});
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.append("circle")
.attr("r", 40)
.style("fill", "#ff9900");
// append icon inside circle
nodeEnter.append("image")
.attr("xlink:href", "http://localhost/d3/user2.jpg")
.attr("x", "-18px")
.attr("y", "-18px")
.attr("width", "35px")
.attr("height", "35px");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -40 : -50;
})
.attr("y", function(d) {
return d.children || d._children ? 55 : 55;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "start" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) { return line([d.source, d.target])});
}
</script>
</body>
</body>
</html>
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>
I am working on plotting a network of pie charts and I have been working all weekend to try and put either a legend or a tooltip for each pie wedge. Here is my working Fiddle without labels and I have pasted the code below. How do I either put in the group numbers or labels from the labels variable?
Thanks in advance!!
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #808080;
stroke-opacity: .6;
}
</style>
</head>
<body>
<script type="text/javascript">
graph = { "nodes":[{"proportions": [
{"group":1, "value": 25 },
{"group":2, "value": 0 },
{"group":3, "value": 0 },
{"group":4, "value": 0 }],"radius":25,"x":1000,"y":262.5},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 25 },
{"group":3, "value": 0 },
{"group":4, "value": 0 }],"radius":25,"x":191.3501125,"y":70.44565171875},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 0 },
{"group":3, "value": 25 },
{"group":4, "value": 0 }],"radius":25,"x":25,"y":30.9375},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 0 },
{"group":3, "value": 0 },
{"group":4, "value": 25 }],"radius":25,"x":833.572375,"y":222.9734390625}],"links": [{ "source":0, "target":1, "length":900, "width":9},
{ "source":0, "target":3, "length":900, "width":9},
{ "source":1, "target":2, "length":900, "width":9},
{ "source":2, "target":3, "length":900, "width":9}]
}
var labels = ['mycave1','mycave2','mycave3','mycave4'];
var width = 4000,
height = 1000,
radius = 25,
color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(0);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(4 * radius)
.size([width, height]);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.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; })
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});
node.each(function(d){
arc = arc.outerRadius(d.radius);
d3.select(this)
.selectAll("path")
.data(function(d){ return pie(d.proportions); })
.enter().append("svg:path")
.attr("d", arc)
.style("fill", function(d,i){ return color(d.data.group) });
});
</script>
</body>
</html>
For tooltips, just append a title element to each path.
.append("svg:title")
.text(function(d) {return "Group "+d.data.group;});
Here's a fiddle: http://jsfiddle.net/ctsprjyq/18/
If you want the corresponding labels instead (Group 1=mycave1, Group 2=mycave2, etc.), just use the group value as an index to the labels array.
.append("svg:title")
.text(function(d) {return labels[d.data.group-1];});