D3 Tree layout visualization - Inherit child with multiple parents - d3.js

I'm fresh bee for D3-visulization. Currently working with creating D3 tree layout visualization for data lineage. In a data lineage flow, a child node can be derived from more than one parent. Here is the example. In below example, a 'DevLead' may work with 2 managers.
var data = [
{ "name": "Director", "parent": "null", "depth": 0 },
{ "name": "Manager1", "parent": "Director", "depth": 1 },
{ "name": "Manager2", "parent": "Director", "depth": 1 },
{ "name": "DevLead", "parent": "Manager1", "depth": 2 },
{ "name": "DevLead", "parent": "Manager2", "depth": 2 }
];
Getting output refer below image attached.
I'd like to see 'DevLead' children should show only one, and there should be a derivation from 'Manager1' and 'Manager2'. Could any one help with this.

D3 Tree Layout does not exactly supports multiple parents
What Can you do?
Use network graph instead - downside is that node positioning is
hard
I had similar requirements and tried building network graph similar
with tree layout, but when there are many nodes, it gets messy ...
you can check it on
codepen
use hack on tree layout - draw additional link from other node
check this example
another hack using hidden nodes - jsfiddle
Also, I think, these links will help you further :
Family Tree in d3.js
d-tree library - data with multiple parents
If you go with first option, here, you can play with this snippet by removing and adding nodes in data
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" type="image/x-icon" href="https://production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" />
<link rel="mask-icon" type="" href="https://production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" />
<title>CodePen - A Pen by dato</title>
</head>
<body translate="no" >
<script src='https://d3js.org/d3.v3.min.js'></script>
<script>
var width = window.innerWidth - 20,
height = window.innerHeight - 20,
radius = 30;
var min_zoom = 0.1;
var max_zoom = 7;
var zoom = d3.behavior.zoom().scaleExtent([min_zoom, max_zoom])
var fill = d3.scale.category20();
var force = d3.layout.force()
.charge(-8000)
.linkDistance(200)
.size([width, height]);
force.drag().on("dragstart", dragstarted)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var chart = svg.append('g');
var json = {
"nodes": [{
"name": "node0"
}, {
"name": "node1"
}, {
"name": "node2"
}, {
"name": "node3"
}, {
"name": "node4"
}, {
"name": "node5"
}, {
"name": "node6"
}, {
"name": "node7"
}, {
"name": "node8"
}, {
"name": "node9"
}, {
"name": "node10"
}, {
"name": "node11"
}, {
"name": "node12"
}, {
"name": "node13"
}, {
"name": "node14"
}, {
"name": "node15"
}, {
"name": "node16"
}, {
"name": "node17"
}, {
"name": "node18"
}, {
"name": "node19"
}, {
"name": "node20"
}, {
"name": "node21"
}, {
"name": "node22"
}, {
"name": "node23"
}, {
"name": "node24"
}, {
"name": "node25"
}, {
"name": "node26"
}, {
"name": "node27"
}, {
"name": "node28"
}, {
"name": "node29"
}, {
"name": "node30"
}, {
"name": "node31"
}, {
"name": "node32"
}, {
"name": "node33"
}, {
"name": "node34"
}, {
"name": "node35"
}, {
"name": "node36"
}, {
"name": "node37"
}, {
"name": "node38"
}, {
"name": "node39"
}, {
"name": "node40"
}, {
"name": "node41"
}, {
"name": "node42"
}, {
"name": "node43"
}, {
"name": "node44"
}, {
"name": "node45"
}, {
"name": "node46"
}, {
"name": "node47"
}, {
"name": "node48"
}, {
"name": "node49"
}, {
"name": "node50"
}, {
"name": "node51"
}, {
"name": "node52"
}, {
"name": "node53"
}, {
"name": "node54"
}, {
"name": "node55"
}, {
"name": "node56"
}, {
"name": "node57"
}, {
"name": "node58"
}, {
"name": "node59"
}, {
"name": "node60"
}, {
"name": "node61"
}, {
"name": "node62"
}, {
"name": "node63"
}, {
"name": "node64"
}, {
"name": "node65"
}, {
"name": "node66"
}, {
"name": "node67"
}, {
"name": "node68"
}, {
"name": "node69"
}, {
"name": "node70"
}, {
"name": "node71"
}, {
"name": "node72"
}, {
"name": "node73"
}, {
"name": "node74"
}, {
"name": "node75"
}, {
"name": "node76"
}, {
"name": "node77"
}, {
"name": "node78"
}, {
"name": "node79"
}, {
"name": "node80"
}, {
"name": "node81"
}, {
"name": "node82"
}, {
"name": "node83"
}, {
"name": "node84"
}, {
"name": "node85"
}, {
"name": "node86"
}, {
"name": "node87"
}, {
"name": "node88"
}, {
"name": "node89"
}, {
"name": "node90"
}, {
"name": "node91"
}, {
"name": "node92"
}, {
"name": "node93"
}, {
"name": "node94"
}, {
"name": "node95"
}, {
"name": "node96"
}, {
"name": "node97"
}, {
"name": "node98"
}, {
"name": "node99"
}],
"links": [ {
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 2,
"target": 5
}, {
"source": 2,
"target": 6
}, {
"source": 3,
"target": 7
}, {
"source": 3,
"target": 8
}, {
"source": 4,
"target": 9
}, {
"source": 4,
"target": 10
}, {
"source": 5,
"target": 11
}, {
"source": 5,
"target": 12
}, {
"source": 6,
"target": 13
}, {
"source": 6,
"target": 14
}, {
"source": 7,
"target": 15
}, {
"source": 7,
"target": 16
}, {
"source": 8,
"target": 17
}, {
"source": 8,
"target": 18
}, {
"source": 9,
"target": 19
}, {
"source": 9,
"target": 20
}, {
"source": 10,
"target": 21
}, {
"source": 10,
"target": 22
}, {
"source": 11,
"target": 23
}, {
"source": 11,
"target": 24
}, {
"source": 12,
"target": 25
}, {
"source": 12,
"target": 26
}, {
"source": 13,
"target": 27
}, {
"source": 13,
"target": 28
}, {
"source": 14,
"target": 29
}, {
"source": 14,
"target": 30
}, {
"source": 15,
"target": 31
}, {
"source": 15,
"target": 32
}, {
"source": 16,
"target": 33
}, {
"source": 16,
"target": 34
}, {
"source": 17,
"target": 35
}, {
"source": 17,
"target": 36
}, {
"source": 18,
"target": 37
}, {
"source": 18,
"target": 38
}, {
"source": 19,
"target": 39
}, {
"source": 19,
"target": 40
}, {
"source": 20,
"target": 41
}, {
"source": 20,
"target": 42
}, {
"source": 21,
"target": 43
}, {
"source": 21,
"target": 44
}, {
"source": 22,
"target": 45
}, {
"source": 22,
"target": 46
}, {
"source": 23,
"target": 47
}, {
"source": 23,
"target": 48
}, {
"source": 24,
"target": 49
}, {
"source": 24,
"target": 50
}, {
"source": 25,
"target": 51
}, {
"source": 25,
"target": 52
}, {
"source": 26,
"target": 53
}, {
"source": 26,
"target": 54
}, {
"source": 27,
"target": 55
}, {
"source": 27,
"target": 56
}, {
"source": 28,
"target": 57
}, {
"source": 28,
"target": 58
}, {
"source": 29,
"target": 59
}, {
"source": 29,
"target": 60
}, {
"source": 30,
"target": 61
}, {
"source": 30,
"target": 62
}, {
"source": 31,
"target": 63
}, {
"source": 31,
"target": 64
}, {
"source": 32,
"target": 65
}, {
"source": 32,
"target": 66
}, {
"source": 33,
"target": 67
}, {
"source": 33,
"target": 68
}, {
"source": 34,
"target": 69
}, {
"source": 34,
"target": 70
}, {
"source": 35,
"target": 71
}, {
"source": 35,
"target": 72
}, {
"source": 36,
"target": 73
}, {
"source": 36,
"target": 74
}, {
"source": 37,
"target": 75
}, {
"source": 37,
"target": 76
}, {
"source": 38,
"target": 77
}, {
"source": 38,
"target": 78
}, {
"source": 39,
"target": 79
}, {
"source": 39,
"target": 80
}, {
"source": 40,
"target": 81
}, {
"source": 40,
"target": 82
}, {
"source": 41,
"target": 83
}, {
"source": 41,
"target": 84
}, {
"source": 42,
"target": 85
}, {
"source": 42,
"target": 86
}, {
"source": 43,
"target": 87
}, {
"source": 43,
"target": 88
}, {
"source": 44,
"target": 89
}, {
"source": 44,
"target": 90
}, {
"source": 45,
"target": 91
}, {
"source": 45,
"target": 92
}, {
"source": 46,
"target": 93
}, {
"source": 46,
"target": 94
}, {
"source": 47,
"target": 95
}, {
"source": 47,
"target": 96
}, {
"source": 48,
"target": 97
}, {
"source": 48,
"target": 98
}, {
"source": 49,
"target": 99
},{
"source": 0,
"target": 99
}]
}
var link = chart.selectAll("line")
.data(json.links)
.enter()
.append("line")
.attr("stroke", function(d) {
return 'blue'
})
var node = chart.selectAll("circle")
.data(json.nodes)
.enter().append("circle")
.attr("r", radius - .75)
.style("fill", function(d) {
return fill(d.group);
})
.style("stroke", function(d) {
return d3.rgb(fill(d.group)).darker();
})
.on('mouseover', d => console.log(d))
.call(force.drag);
function dragstarted() {
d3.event.sourceEvent.stopPropagation();
}
zoom.on("zoom", function(d) {
var evt = d3.event;
debugger;
/*
var dcx = (window.innerWidth/2-d.x*zoom.scale());
var dcy = (window.innerHeight/2-d.y*zoom.scale());
*/
var dcx = evt.translate[0]
var dcy = evt.translate[1]
zoom.translate([dcx, dcy]);
chart.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");;
});
force
.nodes(json.nodes)
.links(json.links)
.on("tick", tick)
.start();
svg.call(zoom)
function tick(e) {
console.log(e)
var k = 6 * e.alpha;
// Push sources up and targets down to form a weak tree.
link
.each(function(d,i) {
d.source.y -= k * 60, d.target.y += k * 100;
/*
if(i%2==1){
d.source.x -= 0.4/k
}else{
d.source.x += 0.4/k
}
*/
})
.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) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
</script>
</body>
</html>

use hack on tree layout - draw additional link from other node
check this example
If a hack is the solution you're looking for the implementation below might be helpful. It's a d3 collapsable tree based on Rob Schmueckers Blog Multiple Parent Nodes D3.js example which can handle multiple parent links and the basic tree events. It's typically split into index.html building the html structure, a stylesheet style.css and the actual script tree.js
index.html :
Define a div with id tree_view later holding the tree. Call tree.js to create the tree.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 collapsable multiple parents tree</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
</head>
<body>
<div id="tree_view"></div> <!-- div holding tree -->
<script src="tree.js"></script> <!-- script to create tree -->
</body>
</html>
style.css :
#tree_view {
width: 100%;
height: 100%;
margin-top: 30px;
}
/* basic node */
.node {
cursor: pointer;
text-anchor: start;
}
/* rectangle node */
.node rect {
stroke: gray;
stroke-width: 1.5px;
}
/* node text */
.node text {
font: 12px sans-serif;
}
/* standard links (.link) and multiparent links (.mpLink) */
.link, .mpLink {
fill: none;
stroke: #ccc;
}
tree.js : (full code below)
Basically you need to define a new link object for each additional link you want to add. Each link needs a source and target node. The backup nodes are necessary for event handling:
let link = new Object();
link.source = pairNode1;
link.target = pairNode2;
link._source = pairNode1; // backup source
link._target = pairNode2; // backup target
additionalLinks.push(link)
Now you can handle all additional links in the update process (updateTree(source)):
// ======== add additional links (mpLinks) ========
let mpLink = svg.selectAll("path.mpLink")
.data(additionalLinks);
mpLink.enter().insert("path", "g")
.attr("class", "mpLink")
.attr("x", nodeWidth / 2)
.attr("y", nodeHeight / 2)
.attr("d", function (d) {
var o = { x: source.x0, y: source.y0 };
return diagonal({ source: o, target: o });
});
mpLink.transition()
.duration(duration)
.attr("d", diagonal)
.attr("stroke-width", 1.5)
mpLink.exit().transition()
.duration(duration)
.attr("d", function (d) {
let o = { x: source.x, y: source.y };
return diagonal({ source: o, target: o });
})
.remove();
and define the on event behavior in method click(d).
The full tree.js code:
// plot properties
let root;
let tree;
let diagonal;
let svg;
let duration = 750;
let treeMargin = { top: 0, right: 20, bottom: 20, left: 20 };
let treeWidth = window.innerWidth - treeMargin.right - treeMargin.left;
let treeHeight = window.innerHeight - treeMargin.top - treeMargin.bottom;
let treeDepth = 5;
let maxTextLength = 90;
let nodeWidth = maxTextLength + 20;
let nodeHeight = 36;
let scale = 1;
// tree data
let data = [
{
"name": "Root",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "A1",
"parent": "Level 2: A"
},
{
"name": "A2",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
// additional (multiparent) links data array
let additionalLinks = []
/**
* Initialize tree properties
* #param {Object} treeData
*/
function initTree(treeData) {
// init
tree = d3.layout.tree()
.size([treeWidth, treeHeight]);
diagonal = d3.svg.diagonal()
.projection(function (d) { return [d.x + nodeWidth / 2, d.y + nodeHeight / 2]; });
svg = d3.select("div#tree_view")
.append("svg")
.attr("width", treeWidth + treeMargin.right + treeMargin.left)
.attr("height", treeHeight + treeMargin.top + treeMargin.bottom)
.attr("transform", `translate(${treeMargin.left},${treeMargin.top})scale(${scale},${scale})`);
root = treeData[0];
root.x0 = treeHeight / 2;
root.y0 = 0;
// fill additionalLinks array
let pairNode1 = tree.nodes(root).filter(function(d) {
return d['name'] === 'Level 2: B';
})[0];
let pairNode2 = tree.nodes(root).filter(function(d) {
return d['name'] === 'A2';
})[0];
let link = new Object();
link.source = pairNode1;
link.target = pairNode2;
link._source = pairNode1; // backup source
link._target = pairNode2; // backup target
additionalLinks.push(link)
// update
updateTree(root);
d3.select(self.frameElement).style("height", "500px");
// add resize listener
window.addEventListener("resize", function (event) {
resizeTreePlot();
});
}
/**
* Perform tree update. Update nodes and links
* #param {Object} source
*/
function updateTree(source) {
let i = 0;
let nodes = tree.nodes(root).reverse();
let links = tree.links(nodes);
nodes.forEach(function (d) { d.y = d.depth * 80; });
// ======== add nodes and text elements ========
let node = svg.selectAll("g.node")
.data(nodes, function (d) { return d.id || (d.id = ++i); });
let nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) { return `translate(${source.x0},${source.y0})`; })
.on("click", click);
nodeEnter.append("rect")
.attr("width", nodeWidth)
.attr("height", nodeHeight)
.attr("rx", 2)
.style("fill", function(d) { return d._children ? "#ace3b5": "#f4f4f9"; });
nodeEnter.append("text")
.attr("y", nodeHeight / 2)
.attr("x", 13)
.attr("dy", ".35em")
.text(function (d) { return d.name; })
.style("fill-opacity", 1e-6);
let nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) { return `translate(${d.x},${d.y})`; });
nodeUpdate.select("rect")
.attr("width", nodeWidth)
.style("fill", function(d) { return d._children ? "#ace3b5": "#f4f4f9"; });
nodeUpdate.select("text").style("fill-opacity", 1);
let nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) { return `translate(${source.x},${source.y})`; })
.remove();
nodeExit.select("rect")
.attr("width", nodeWidth)
.attr("rx", 2)
.attr("height", nodeHeight);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// ======== add links ========
let link = svg.selectAll("path.link")
.data(links, function (d) { return d.target.id; });
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", nodeWidth / 2)
.attr("y", nodeHeight / 2)
.attr("d", function (d) {
var o = { x: source.x0, y: source.y0 };
return diagonal({ source: o, target: o });
});
link.transition()
.duration(duration)
.attr("d", diagonal)
link.exit().transition()
.duration(duration)
.attr("d", function (d) {
let o = { x: source.x, y: source.y };
return diagonal({ source: o, target: o });
})
.remove();
// ======== add additional links (mpLinks) ========
let mpLink = svg.selectAll("path.mpLink")
.data(additionalLinks);
mpLink.enter().insert("path", "g")
.attr("class", "mpLink")
.attr("x", nodeWidth / 2)
.attr("y", nodeHeight / 2)
.attr("d", function (d) {
var o = { x: source.x0, y: source.y0 };
return diagonal({ source: o, target: o });
});
mpLink.transition()
.duration(duration)
.attr("d", diagonal)
.attr("stroke-width", 1.5)
mpLink.exit().transition()
.duration(duration)
.attr("d", function (d) {
let o = { x: source.x, y: source.y };
return diagonal({ source: o, target: o });
})
.remove();
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
/**
* Handle on tree node clicked actions
* #param {Object} d node
*/
function click(d) {
// update regular links
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
// update additional links
additionalLinks.forEach(function(link){
let sourceVisible = false;
let targetVisible = false;
tree.nodes(root).filter(function(n) {
if(n["name"] == link._source.name){
sourceVisible = true;
}
if(n["name"] == link._target.name){
targetVisible = true;
}
});
if(sourceVisible && targetVisible){
link.source = link._source;
link.target = link._target;
}
else if(!sourceVisible && targetVisible
|| !sourceVisible && !targetVisible){
link.source = d;
link.target = link.source;
}
else if(sourceVisible && !targetVisible){
link.source = link._source;
link.target = link.source;
}
});
// define more links behavior here...
updateTree(d);
}
/**
* Update tree dimension
*/
function updateTreeDimension() {
tree.size([treeWidth, treeHeight]);
svg.attr("width", treeWidth + treeMargin.right + treeMargin.left)
.attr("height", treeHeight + treeMargin.top + treeMargin.bottom)
.attr("transform", `translate(${treeMargin.left},${treeMargin.top})scale(${scale},${scale})`);
}
/**
* Resize the tree using current window dimension
*/
function resizeTreePlot() {
treeWidth = 0.9 * window.innerWidth - treeMargin.right - treeMargin.left;
treeHeight = (treeDepth + 2) * nodeHeight * 2;
updateTreeDimension();
updateTree(root);
}
// plot tree
initTree(data);
updateTree(root);
Here you can find the full demo. You can also fork the project.

Related

D3 JS - How to set maximum length of links in force graph?

I am trying to create a simple force graph. Graph contains nodes and links and not any labels. I came across this weird issue in which length of links is increasing automatically when I group some nodes at one position. Nodes are sticky. When I drag and place nodes at upper left side then other non sticky nodes goes down right side and length of links also increases.
Is there any way I can set maximum length of links? Code is given below.
Or is there any force to control this kind of behavior?
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//d3 code goes here
//Characters
var nodes_data = [{
"name": "Lillian",
"sex": "F"
}, {
"name": "Gordon",
"sex": "M"
}, {
"name": "Sylvester",
"sex": "M"
}, {
"name": "Mary",
"sex": "F"
}, {
"name": "Helen",
"sex": "F"
}, {
"name": "Jamie",
"sex": "M"
}, {
"name": "Jessie",
"sex": "F"
}, {
"name": "Ashton",
"sex": "M"
}, {
"name": "Duncan",
"sex": "M"
}, {
"name": "Evette",
"sex": "F"
}, {
"name": "Mauer",
"sex": "M"
}, {
"name": "Fray",
"sex": "F"
}, {
"name": "Duke",
"sex": "M"
}, {
"name": "Baron",
"sex": "M"
}, {
"name": "Infante",
"sex": "M"
}, {
"name": "Percy",
"sex": "M"
}, {
"name": "Cynthia",
"sex": "F"
}]
//Relationships
//type: A for Ally, E for Enemy
var links_data = [{
"source": "Sylvester",
"target": "Gordon",
"type": "A"
}, {
"source": "Sylvester",
"target": "Lillian",
"type": "A"
}, {
"source": "Sylvester",
"target": "Mary",
"type": "A"
}, {
"source": "Sylvester",
"target": "Jamie",
"type": "A"
}, {
"source": "Sylvester",
"target": "Jessie",
"type": "A"
}, {
"source": "Sylvester",
"target": "Helen",
"type": "A"
}, {
"source": "Helen",
"target": "Gordon",
"type": "A"
}, {
"source": "Mary",
"target": "Lillian",
"type": "A"
}, {
"source": "Ashton",
"target": "Mary",
"type": "A"
}, {
"source": "Duncan",
"target": "Jamie",
"type": "A"
}, {
"source": "Gordon",
"target": "Jessie",
"type": "A"
}, {
"source": "Sylvester",
"target": "Fray",
"type": "E"
}, {
"source": "Fray",
"target": "Mauer",
"type": "A"
}, {
"source": "Fray",
"target": "Cynthia",
"type": "A"
}, {
"source": "Fray",
"target": "Percy",
"type": "A"
}, {
"source": "Percy",
"target": "Cynthia",
"type": "A"
}, {
"source": "Infante",
"target": "Duke",
"type": "A"
}, {
"source": "Duke",
"target": "Gordon",
"type": "A"
}, {
"source": "Duke",
"target": "Sylvester",
"type": "A"
}, {
"source": "Baron",
"target": "Duke",
"type": "A"
}, {
"source": "Baron",
"target": "Sylvester",
"type": "E"
}, {
"source": "Evette",
"target": "Sylvester",
"type": "E"
}, {
"source": "Cynthia",
"target": "Sylvester",
"type": "E"
}, {
"source": "Cynthia",
"target": "Jamie",
"type": "E"
}, {
"source": "Mauer",
"target": "Jessie",
"type": "E"
}]
//set up the simulation
//nodes only for now
var simulation = d3.forceSimulation()
.nodes(nodes_data);
//add forces
//we're going to add a charge to each node
//also going to add a centering force
simulation
.force("charge_force", d3.forceManyBody().strength([-400]))
.force("center_force", d3.forceCenter(width / 2, height / 2))
.on("tick", tickActions);
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 3)
.attr("stroke", linkColour);
function linkColour(d) {
if (d.type == "A") {
return "green";
} else {
return "red";
}
}
//draw circles for the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", circleColour);
function circleColour(d) {
if (d.sex == "M") {
return "blue";
} else {
return "pink";
}
}
//Create the link force
//We need the id accessor to use named sources and targets
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.name;
});
//Add a links force to the simulation
//Specify links in d3.forceLink argument
simulation.force("links", link_force);
// setup drag handler
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
//same as using .call on the node variable as in https://bl.ocks.org/mbostock/4062045
drag_handler(node)
//drag handler
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// // for non sticky behaviour
// function drag_end(d) {
// if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = null;
// d.fy = null;
// }
// // for sticky behaviour
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = d.x;
d.fy = d.y;
}
// // Sticky nodes on x axis
// function drag_end(d) {
// if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = d.x;
// d.fy = null;
// }
// // Sticky nodes on y axis
// function drag_end(d) {
// if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = null;
// d.fy = d.y;
// }
// // Fixed node position on dragged
// function drag_end(d) {
// if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = 300;
// d.fy = 200;
// }
// Invert node position on dragged
// function drag_end(d) {
// if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = d.y;
// d.fy = d.x;
// }
function tickActions() {
//update circle positions to reflect node updates on each tick of the simulation
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update link positions
//simply tells one end of the line to follow one node around
//and the other end of the line to follow the other node around
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;
});
}
.links line {
/* stroke: #999; */
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<h2>Draggable force graph with sticky nodes and other behaviours of node positioning</h2>
<svg width="960" height="600"></svg>

Root element is not showing its children in sunburst

I am trying to make a sunburst by following the 3-part tutorial on https://bl.ocks.org/denjn5/3b74baf5edc4ac93d5e487136481c601 My json contains sell information based on country and product division. I am trying to show in the first layer sell based on country and in the 2nd layer sell based on product division. My Json file looks like this:
{
"country": "All",
"shares":[
{
"country": "at",
"shares":[
{
"productdivision": "accessorie",
"label": 53222
},
{
"productdivision": "apparel",
"label": 365712
},
{
"productdivision": "footwear",
"label": 523684
}
]
},
{
"country": "be",
"shares":[
{
"productdivision": "accessorie",
"label": 57522
},
{
"productdivision": "apparel",
"label": 598712
},
{
"productdivision": "footwear",
"label": 52284
}
]
},
{
"country": "DE",
"shares":[
{
"productdivision": "accessorie",
"label": 56982
},
{
"productdivision": "apparel",
"label": 55312
},
{
"productdivision": "footwear",
"label": 67284
}
]
},
{
"country": "Fr",
"shares":[
{
"productdivision": "accessorie",
"label": 5862
},
{
"productdivision": "apparel",
"label": 45312
},
{
"productdivision": "footwear",
"label": 26284
}
]
}
]
}
This json file's name is kpiDrillDown2.json and I call it in my code with d3.json(). I have made slight changes to the code to work for my data. The code is as follows:
<html>
<head>
<style>
#import url('https://fonts.googleapis.com/css?family=Raleway');
body {
font-family: "Raleway", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg></svg>
<script>
//initialize variables
var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
//setting up svg workspace
var g = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
//formatting the data
var partition = d3.partition()
.size([2 * Math.PI, radius]);
function draw(nodeData){
debugger;
//finding the root node
var root = d3.hierarchy(nodeData)
.sum(function (d) { return d.label});
//calculating each arc
partition(root);
var arc = d3.arc()
.startAngle(function (d) { return d.x0; })
.endAngle(function (d) { return d.x1; })
.innerRadius(function (d) { return d.y0; })
.outerRadius(function (d) { return d.y1; });
g.selectAll('g')
.data(root.descendants())
.enter()
.append('g')
.attr("class", "node")
.append('path')
.attr("display", function (d) { return d.depth ? null : "none"; })
.attr("d", arc)
.style('stroke', '#fff')
.style("fill", function (d) { return color((d.parent ? d : d.parent).data.productdivision); })
g.selectAll(".node")
.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")rotate(" + computeTextRotation(d) + ")"; })
.attr("dx", "-20")
.attr("dy", ".5em")
.text(function(d) { return d.parent ? d.data.productdivision : "" });
function computeTextRotation(d) {
var angle = (d.x0 + d.x1) / Math.PI * 90;
// Avoid upside-down labels
return (angle < 90 || angle > 270) ? angle : angle + 180;
}
}
d3.json('kpiDrillDown3.json', draw);
</script>
</body>
</html>
I put a debbuger in the draw functin to inspect root element. Root doesn't have any children. This is what I see in the console:
When I continue it gives me the error:"Cannot read property 'data' of null". As shown in console, root doesn't have children. My question is, do I need to change my json data format to make root recogninze the chilren, or am I doing something wrong. I am new to d3js and basically by getting the source code and modifying it, I am making my way through. This is the error in console:
I appreciate your help and thank you very much.
According to the API:
The specified children accessor function is invoked for each datum, starting with the root data, and must return an array of data representing the children, or null if the current datum has no children. If children is not specified, it defaults to:
function children(d) {
return d.children;
}
However, in your data structure, you don't have children, but shares instead.
So, the hierarchy should be:
var root = d3.hierarchy(data, function(d) {
return d.shares;
})
Pay attention to the fact that in the JSON of that tutorial you linked (just like in the API's example) the children's array is named children.
Here is a demo, look at the console (your browser's console, not the snippet one):
var data = {
"country": "All",
"shares": [{
"country": "at",
"shares": [{
"productdivision": "accessorie",
"label": 53222
},
{
"productdivision": "apparel",
"label": 365712
},
{
"productdivision": "footwear",
"label": 523684
}
]
},
{
"country": "be",
"shares": [{
"productdivision": "accessorie",
"label": 57522
},
{
"productdivision": "apparel",
"label": 598712
},
{
"productdivision": "footwear",
"label": 52284
}
]
},
{
"country": "DE",
"shares": [{
"productdivision": "accessorie",
"label": 56982
},
{
"productdivision": "apparel",
"label": 55312
},
{
"productdivision": "footwear",
"label": 67284
}
]
},
{
"country": "Fr",
"shares": [{
"productdivision": "accessorie",
"label": 5862
},
{
"productdivision": "apparel",
"label": 45312
},
{
"productdivision": "footwear",
"label": 26284
}
]
}
]
};
var root = d3.hierarchy(data, function(d) {
return d.shares;
})
.sum(function(d) {
return d.label
});
console.log(root)
<script src="https://d3js.org/d3.v4.min.js"></script>

How to give color to edges of d3js graph?

I am trying to give color to the edges but the desire outcome is not according to my desire.The color scheme changes every time I change the JSON file.
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="D3js_edges_connected_by_nodes_id.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<title>Weighted Citation Graph</title>
<style>
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
body {
background-color: white;
margin: 0px;
}
.graphContainer {
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
}
</style>
<script>
function load_graph(text) {
var color = d3.scale.category20();
try{
//var data = JSON.parse(text);
} catch (e) {
window.alert("sometext: "+e);
}
var data = { "nodes": [{ "id": 127230, "name": "Optimization of query evaluation algorithms", "citation": 26, "group": 7 }, { "id": 127254, "name": "Flow algorithms for parallel query optimization", "citation": 22, "group": 7 }, { "id": 127380, "name": "Randomized approximation algorithms for query optimization problems on two processors", "citation": 14, "group": 7 }, { "id": 127438, "name": "Optimization algorithms for simultaneous multidimensional queries in OLAP environments", "citation": 12, "group": 7 }, { "id": 127063, "name": "Query optimization in database systems", "citation": 230, "group": 7 }, { "id": 127158, "name": "Query optimization in a memory-resident domain relational calculus database system", "citation": 41, "group": 7 }, { "id": 129760, "name": "An Overview of TQuel", "citation": 22, "group": 7 }, { "id": 129867, "name": "ADVISORS", "citation": 10, "group": 7 }, { "id": 129872, "name": "Tellabs and THRIP through the Telkom Centre of Excellence at Rhodes University.", "citation": 10, "group": 7 }, { "id": 127412, "name": "Optimal service ordering in decentralized queries over web services", "citation": 13, "group": 7 }, { "id": 130856, "name": "Queries over Web Services", "citation": 10, "group": 7 }, { "id": 130959, "name": "Exploiting Parallelism to Accelerate Keyword Search On Deep-web Sources", "citation": 10, "group": 7 }, { "id": 131199, "name": "Contents lists available at ScienceDirect Future Generation Computer Systems", "citation": 10, "group": 7 }, { "id": 131211, "name": "Flow Algorithms for Parallel Query Optimization", "citation": 10, "group": 7 }, { "id": 127373, "name": "Multi-query Optimization for On-Line Analytical Processing", "citation": 14, "group": 7 }, { "id": 133379, "name": "Concise descriptions of subsets of structured sets", "citation": 21, "group": 7 }], "links": [{ "source": 127230, "target": 127063, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127230, "target": 127158, "name": "71 %", "value": 71, "grouo": "#1F75FE" }, { "source": 127230, "target": 129760, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127230, "target": 129867, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127230, "target": 129872, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127230, "target": 127063, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127230, "target": 127158, "name": "71 %", "value": 71, "grouo": "#1F75FE" }, { "source": 127230, "target": 129760, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127230, "target": 129867, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127230, "target": 129872, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127254, "target": 127412, "name": "5 %", "value": 5, "grouo": "#1F75FE" }, { "source": 127254, "target": 130856, "name": "2 %", "value": 2, "grouo": "#1F75FE" }, { "source": 127254, "target": 130959, "name": "Least Similar", "value": 10, "grouo": "#ACE5EE" }, { "source": 127254, "target": 131199, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127254, "target": 131211, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127254, "target": 127412, "name": "5 %", "value": 5, "grouo": "#1F75FE" }, { "source": 127254, "target": 130856, "name": "2 %", "value": 2, "grouo": "#1F75FE" }, { "source": 127254, "target": 130959, "name": "Least Similar", "value": 10, "grouo": "#ACE5EE" }, { "source": 127254, "target": 131199, "name": "Nothing Matched", "value": 10, "grouo": "#EE204D" }, { "source": 127254, "target": 131211, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127438, "target": 127373, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127438, "target": 133379, "name": "3 %", "value": 3, "grouo": "#1F75FE" }, { "source": 127438, "target": 127373, "name": "Most Similar", "value": 100, "grouo": "#1A4876" }, { "source": 127438, "target": 133379, "name": "3 %", "value": 3, "grouo": "#1F75FE" }] };
// used to store the number of links between two nodes.
// mLinkNum[data.links[i].source + "," + data.links[i].target] = data.links[i].linkindex;
var mLinkNum = {};
// sort links first
// sortLinks();
data.links.sort(function (a, b) {
if (a.source > b.source) { return 1; }
else if (a.source < b.source) { return -1; }
else {
if (a.target > b.target) { return 1; }
if (a.target < b.target) { return -1; }
else { return 0; }
}
})
// set up linkIndex and linkNumer, because it may possible multiple links share the same source and target node
setLinkIndexAndNum();
var w = 1345,
h = 1000;
//var w = 3000,
// h = 3000;
var force = d3.layout.force()
.size([w, h])
.linkDistance(200)
.charge(-800)
.on("tick", tick);
var svg = d3.select(".graphContainer").append("svg:svg")
.attr("width", w)
.attr("height", h);
var color = d3.scale.category10()
var edges = [];
data.links.forEach(function (e) {
var sourceNode = data.nodes.filter(function (n) {
return n.id === e.source;
})[0],
targetNode = data.nodes.filter(function (n) {
return n.id === e.target;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
name: e.name,
value: e.value,
linkindex: e.linkindex,
grouo: e.grouo
});
});
console.log(edges)
force
.nodes(data.nodes)
.links(edges)
.start();
var path = svg.append("svg:g")
.selectAll("line")
.data(edges)
.enter().append("svg:path")
.attr("class", "link")
.style("stroke-width", function (d, i) {
console.log(d.value)
return Math.sqrt(d.value);
}).style('stroke', function (d) {
return color(d.grouo);
});
//path.append("title").text(function (d) { return d.name });
var circle = svg.append("svg:g")
.selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", function (d) {
return (Math.sqrt(d.citation));
})
.style("fill", function (d) {
return color(d.group);
})
.call(force.drag);
circle.append("title").text(function (d) { return d.name });
//circle.on("click", function () {
// d3.select(this)
// .attr("r", function (d) {
// return (Math.sqrt(d.citation) * 2);
// })
// .style("fill", "lightsteelblue");
//});
circle.on("click", function (d) {
var thisNode = d.id
var connected = data.links.filter(function (e) {
return e.source === thisNode || e.target === thisNode
});
circle.attr("opacity", function (d) {
return (connected.map(d => d.source).indexOf(d.id) > -1 || connected.map(d => d.target).indexOf(d.id) > -1) ? 1 : 0.1
});
path.attr("opacity", function (d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 0.1
});
})
circle.on("dblclick", function (d) {
var thisNode = d.id
var connected = data.links.filter(function (e) {
return e.source === thisNode || e.target === thisNode
});
circle.attr("opacity", function (d) {
return (connected.map(d => d.source).indexOf(d.id) > -1 || connected.map(d => d.target).indexOf(d.id) > -1) ? 1 : 1
});
path.attr("opacity", function (d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 1
});
})
var text = svg.append("svg:g")
.selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
console.log('test');
//A copy of the text with a thick white stroke for legibility.
//text.append("svg:text")
//.attr("x", 8)
//.attr("y", ".31em")
//.attr("class", "shadow")
//.text(function (d) {
//return d.name;
//});
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function (d) {
// return d.name;
});
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function (d, i) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = 75 * d.linkindex; //linknum is defined above
var output = "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
//console.log(d)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// Add tooltip to the connection path
path.append("svg:title")
.text(function (d, i) {
return d.name;
});
circle.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
// sort the links by source, then target
function sortLinks1() {
data.links.sort(function (a, b) {
if (a.source > b.source) {
return 1;
} else if (a.source < b.source) {
return -1;
} else {
if (a.target > b.target) {
return 1;
}
if (a.target < b.target) {
return -1;
} else {
return 0;
}
}
});
}
//any links with duplicate source and target get an incremented 'linknum'
function setLinkIndexAndNum1() {
for (var i = 0; i < data.links.length; i++) {
if (i != 0 &&
data.links[i].source == data.links[i - 1].source &&
data.links[i].target == data.links[i - 1].target) {
data.links[i].linkindex = data.links[i - 1].linkindex + 1;
console.log(data.links[i].linkindex)
} else {
data.links[i].linkindex = 1;
console.log(data.links[i].linkindex)
}
// save the total number of links between two nodes
if (mLinkNum[data.links[i].target + "," + data.links[i].source] !== undefined) {
mLinkNum[data.links[i].target + "," + data.links[i].source] = data.links[i].linkindex;
} else {
mLinkNum[data.links[i].source + "," + data.links[i].target] = data.links[i].linkindex;
}
}
}
function setLinkIndexAndNum() {
for (var i = 0; i < data.links.length; i++) {
if (i != 0 &&
data.links[i].source == data.links[i - 1].source &&
data.links[i].target == data.links[i - 1].target) {
data.links[i].linkindex = data.links[i - 1].linkindex + 1;
}
else {
data.links[i].linkindex = 1;
};
};
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<script src="//d3js.org/d3.v3.min.js"></script>
<%--<textarea runat="server" id="textarea" cols="80" rows="20"></textarea>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>--%>
</div>
<div id="graphContainer" class="graphContainer"></div>
</form>
</body>
</html>
Can anybody help me out. thanks!
If you look at the data variable I have given different colors for each edge according to description of "name" attribute.
The reason you see different colours is because the colour scale d3.scale.category10() - like all d3 ordinal scales (at least in v3) - assigns its colours on a first come first served basis.
https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#_ordinal
So if you changed your json so a node with group 'A' is encountered first, then it and subsequent group 'A' nodes will be given the first colour in the scale. If previously it was a node with group 'B' that was encountered first all the nodes with group 'B' would have got that colour instead.
One way to avoid this is to run through your data, collate the groups, order them, and pass them as a domain to your colour scale before setting the colours for individual nodes. That way A, B etc would be in the same order each time.
However, this would not help in situations where a change in your data meant particular groups were missing altogether, you'd still see a shift in the colour assignment. For that you'd need to supply a fixed list of all possible groups to assign to the colour scale, even if those groups weren't all in your current data.
PS. You assign color to the category20 colour scale, and then later on reassign it to category10. That doesn't have any effect on why you're seeing what you're seeing though as you don't use color till afterwards.
PPS. Another thing I've noticed is that your link colours, as defined by the .grouo data, are already hexadecimal colour strings. I suspect you don't mean to pass them to the color scale and mean to use them as they are i.e. style ("stroke", function(d) { return d.grouo; ]);

how to change the color of a link in d3js?

here is my code and i want to change the color of my link.
i have tried so many techniques but no effect.
please kindly help me out.
im using asp.net
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="D3js_demo.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<title>Weighted Citation Graph</title>
<style>
path.link {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
body {
background-color: white;
margin: 0px;
}
.graphContainer {
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
$(function () {
var color = d3.scale.category20();
var data = {
"nodes": [
{ "id": 0, "name": "paper1", "citation": 5, "group": 1 },
{ "id": 1, "name": "paper2", "citation": 8, "group": 2 },
{ "id": 2, "name": "paper3", "citation": 12, "group": 3 },
{ "id": 3, "name": "paper4", "citation": 25, "group": 4 },
{ "id": 4, "name": "paper5", "citation": 15, "group": 5 },
{ "id": 5, "name": "paper6", "citation": 5, "group": 1 },
{ "id": 6, "name": "paper7", "citation": 8, "group": 2 },
{ "id": 7, "name": "paper8", "citation": 12, "group": 3 },
{ "id": 8, "name": "paper9", "citation": 25, "group": 4 },
{ "id": 9, "name": "paper10", "citation": 15, "group": 5 }
],
"links": [
{ "source": 0, "target": 1, "name": "A-B-1", "value": 8 , "grouo": 1},
{ "source": 0, "target": 1, "name": "A-B-2", "value": 24, "grouo": 2 },
{ "source": 0, "target": 2, "name": "A-C-1", "value": 100, "grouo": 1 },
{ "source": 0, "target": 2, "name": "A-C-3", "value": 44, "grouo": 2 },
{ "source": 2, "target": 3, "name": "A-D-1", "value": 169, "grouo": 1 },
{ "source": 2, "target": 3, "name": "A-D-2", "value": 80, "grouo": 2 },
{ "source": 2, "target": 4, "name": "A-E-1", "value": 16, "grouo": 1 },
{ "source": 2, "target": 4, "name": "A-E-5", "value": 200, "grouo": 2 },
{ "source": 4, "target": 5, "name": "A-B-1", "value": 8, "grouo": 1 },
{ "source": 4, "target": 5, "name": "A-B-2", "value": 24, "grouo": 2 },
{ "source": 5, "target": 6, "name": "A-C-1", "value": 12, "grouo": 1 },
{ "source": 5, "target": 6, "name": "A-C-3", "value": 44, "grouo": 2 },
{ "source": 5, "target": 7, "name": "A-D-1", "value": 125, "grouo": 1 },
{ "source": 5, "target": 7, "name": "A-D-2", "value": 225, "grouo": 2 },
{ "source": 7, "target": 8, "name": "A-E-1", "value": 36, "grouo": 1 },
{ "source": 7, "target": 8, "name": "A-E-5", "value": 81, "grouo": 2 },
{ "source": 8, "target": 3, "name": "A-C-1", "value": 9, "grouo": 1 },
{ "source": 8, "target": 3, "name": "A-C-3", "value": 16, "grouo": 2 },
{ "source": 8, "target": 9, "name": "A-D-1", "value": 50, "grouo": 1 },
{ "source": 8, "target": 9, "name": "A-D-2", "value": 100, "grouo": 2 }
]
};
// used to store the number of links between two nodes.
// mLinkNum[data.links[i].source + "," + data.links[i].target] = data.links[i].linkindex;
var mLinkNum = {};
// sort links first
sortLinks();
// set up linkIndex and linkNumer, because it may possible multiple links share the same source and target node
setLinkIndexAndNum();
var w = 960,
h = 500;
var force = d3.layout.force()
.nodes(d3.values(data.nodes))
.links(data.links)
.size([w, h])
.linkDistance(200)
.charge(-1000)
.on("tick", tick)
.start();
var svg = d3.select(".graphContainer").append("svg:svg")
.attr("width", w)
.attr("height", h);
var path = svg.append("svg:g")
.selectAll("line")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var circle = svg.append("svg:g")
.selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", function (d) { return (d.citation); })
.style("fill", function (d) { return color(d.group); })
.call(force.drag);
var text = svg.append("svg:g")
.selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function (d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function (d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function (d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
// get the total link numbers between source and target node
var lTotalLinkNum = mLinkNum[d.source.id + "," + d.target.id] || mLinkNum[d.target.id + "," + d.source.id];
if (lTotalLinkNum > 1) {
// if there are multiple links between these two nodes, we need generate different dr for each path
dr = dr / (1 + (1 / lTotalLinkNum) * (d.linkindex - 1));
}
// generate svg path
return "M" + d.source.x + "," + d.source.y +
"A" + dr + "," + dr + " 0 0 1," + d.target.x + "," + d.target.y +
"A" + dr + "," + dr + " 0 0 0," + d.source.x + "," + d.source.y;
});
// Add tooltip to the connection path
path.append("svg:title")
.text(function (d, i) { return d.name; });
circle.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
// sort the links by source, then target
function sortLinks() {
data.links.sort(function (a, b) {
if (a.source > b.source) {
return 1;
}
else if (a.source < b.source) {
return -1;
}
else {
if (a.target > b.target) {
return 1;
}
if (a.target < b.target) {
return -1;
}
else {
return 0;
}
}
});
}
//any links with duplicate source and target get an incremented 'linknum'
function setLinkIndexAndNum() {
for (var i = 0; i < data.links.length; i++) {
if (i != 0 &&
data.links[i].source == data.links[i - 1].source &&
data.links[i].target == data.links[i - 1].target) {
data.links[i].linkindex = data.links[i - 1].linkindex + 1;
}
else {
data.links[i].linkindex = 1;
}
// save the total number of links between two nodes
if (mLinkNum[data.links[i].target + "," + data.links[i].source] !== undefined) {
mLinkNum[data.links[i].target + "," + data.links[i].source] = data.links[i].linkindex;
}
else {
mLinkNum[data.links[i].source + "," + data.links[i].target] = data.links[i].linkindex;
}
}
}
});
</script>
</div>
<div id="graphContainer" class="graphContainer"></div>
</form>
</body>
</html>
If you want to color them according to the stroke-width and using the same color scale, just change your var path:
var path = svg.append("svg:g")
.selectAll("line")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); })
.style("stroke", function(d){
return color(d.value)
});
This is how it looks like: https://jsfiddle.net/4xt5v51m/

How to render a general lattice with D3JS?

I am looking to an algorithm or layout to be implemented in D3JS to render a graph of a "general" lattice, a lattice that is not (necessarily) binomial.
An example of the kind of result I would like to obtain is:
I want to visualize the lattice as a hierarchy from the bottom to the top, similar to a tree-styled structure.
I tried to adapt a force layout, but this worked badly, especially because it was hard to locate the levels of the hierarchy properly.
I don't have any constraint about the form of the input, that it can be the more appropriate for the procedure.
You could do something like this, but I'm not sure how useful it would be. It makes use of the cola.js library.
// Sample data set
var json = {
nodes: [
{ "name": 'Beverage' },
{ "name": 'nonAlcoholic' },
{ "name": 'sparkling' },
{ "name": 'alcoholic' },
{ "name": 'hot' },
{ "name": 'caffeinic' },
{ "name": 'MineralWater' },
{ "name": 'madeFromGrain' },
{ "name": 'madeFromGrapes' },
{ "name": 'HerbTea' },
{ "name": 'Coffee' },
{ "name": 'Cola' },
{ "name": 'Beer' },
{ "name": 'Wine' },
{ "name": 'Champagne' },
{ "name": '' }
],
links: [
{ "source": 0, "target": 1},
{ "source": 0, "target": 2},
{ "source": 0, "target": 3},
{ "source": 1, "target": 4},
{ "source": 1, "target": 5},
{ "source": 1, "target": 6},
{ "source": 2, "target": 6},
{ "source": 2, "target": 7},
{ "source": 2, "target": 14},
{ "source": 3, "target": 7},
{ "source": 3, "target": 8},
{ "source": 4, "target": 9},
{ "source": 4, "target": 10},
{ "source": 5, "target": 10},
{ "source": 5, "target": 11},
{ "source": 6, "target": 11},
{ "source": 7, "target": 12},
{ "source": 8, "target": 13},
{ "source": 8, "target": 13},
{ "source": 13, "target": 14},
{ "source": 10, "target": 15},
{ "source": 11, "target": 15},
{ "source": 12, "target": 15},
{ "source": 14, "target": 15},
]
};
var width = 800, height = 600, n = 10000;
var svg = d3.select('#vis').append('svg').attr('width', width).attr('height', height);
var force = cola.d3adaptor()
.linkDistance(80)
.size([width, height])
.nodes(json.nodes)
.links(json.links)
.flowLayout("y", 25)
.on("tick", tick)
.start();
var node = svg.selectAll("circle.node")
.data(json.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5);
var text = svg.selectAll("text.label")
.data(json.nodes)
.enter().append("text")
.attr("class", "label")
.text(function(d) { return d.name; });
var link = svg.selectAll("line.link")
.data(json.links)
.enter().append("line")
.attr("class", "link");
function tick() {
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) { return d.x; })
.attr("cy", function(d) { return d.y; });
text.attr("x", function(d) { return d.x + 8; })
.attr("y", function(d) { return d.y; });
}
function hover(d) {
switch(d3.event.type) {
case "mouseover": var tip = svg.append("g")
.attr("class", "tip")
.attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
var rect = tip.append("rect")
.attr("fill", "white");
var text = tip.append("text")
.text(d.name);
var bounds = text.node().getBBox();
rect.attr("x", bounds.x)
.attr("y", bounds.y)
.attr("width", bounds.width)
.attr("height", bounds.height);
break;
case "mouseout": svg.selectAll("g.tip")
.remove();
break;
default: break;
}
}
text {
font: 10px sans-serif;
}
line {
stroke: #000;
stroke-width: 1.5px;
}
circle {
stroke: #fff;
stroke-width: 1.5px;
}
<div id="vis"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script>
Using cola.js you can specify additional constraints to assist with laying things out correctly. As it stands, the only constraint that I've placed on it is a flowLayout parameter. The good thing about this is the layout is repeatable and achieving a similar result with a d3 force layout may be quite difficult (although, in fairness, it's not really intended to be used that way).
I wouldn't claim that this is a great result, but short of manually determining node positions, or accepting a standard d3 force layout, this might be among the easiest and quickest methods to get something approaching what you're after.
Edit: showing how the hover was used.
// Sample data set
var json = {
nodes: [
{ "name": 'Beverage' },
{ "name": 'nonAlcoholic' },
{ "name": 'sparkling' },
{ "name": 'alcoholic' },
{ "name": 'hot' },
{ "name": 'caffeinic' },
{ "name": 'MineralWater' },
{ "name": 'madeFromGrain' },
{ "name": 'madeFromGrapes' },
{ "name": 'HerbTea' },
{ "name": 'Coffee' },
{ "name": 'Cola' },
{ "name": 'Beer' },
{ "name": 'Wine' },
{ "name": 'Champagne' },
{ "name": '' }
],
links: [
{ "source": 0, "target": 1},
{ "source": 0, "target": 2},
{ "source": 0, "target": 3},
{ "source": 1, "target": 4},
{ "source": 1, "target": 5},
{ "source": 1, "target": 6},
{ "source": 2, "target": 6},
{ "source": 2, "target": 7},
{ "source": 2, "target": 14},
{ "source": 3, "target": 7},
{ "source": 3, "target": 8},
{ "source": 4, "target": 9},
{ "source": 4, "target": 10},
{ "source": 5, "target": 10},
{ "source": 5, "target": 11},
{ "source": 6, "target": 11},
{ "source": 7, "target": 12},
{ "source": 8, "target": 13},
{ "source": 8, "target": 13},
{ "source": 13, "target": 14},
{ "source": 10, "target": 15},
{ "source": 11, "target": 15},
{ "source": 12, "target": 15},
{ "source": 14, "target": 15},
]
};
var width = 800, height = 600, n = 10000;
var svg = d3.select('#vis').append('svg').attr('width', width).attr('height', height);
var force = cola.d3adaptor()
.linkDistance(80)
.size([width, height])
.nodes(json.nodes)
.links(json.links)
.flowLayout("y", 25)
.on("tick", tick)
.start();
var link = svg.selectAll("line.link")
.data(json.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll("circle.node")
.data(json.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.on("mouseover", hover)
.on("mouseout", hover);
function tick() {
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) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function hover(d) {
switch(d3.event.type) {
case "mouseover":
console.log(d3.event);
var tip = svg.append("g")
.attr("class", "tip")
.attr("transform", "translate(" + d3.event.clientX + "," + d3.event.clientY + ")");
var rect = tip.append("rect")
.attr("fill", "white");
var text = tip.append("text")
.text(d.name);
var bounds = text.node().getBBox();
rect.attr("x", bounds.x)
.attr("y", bounds.y)
.attr("width", bounds.width)
.attr("height", bounds.height);
break;
case "mouseout": svg.selectAll("g.tip")
.remove();
break;
default: break;
}
}
text {
font: 10px sans-serif;
}
line {
stroke: #000;
stroke-width: 1.5px;
}
circle {
stroke: #fff;
stroke-width: 1.5px;
}
<div id="vis"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script>

Resources