chart1 = {
const vis = d3.select(DOM.svg(width, height));
var
dr = 4,
off = 15,
expand = {},
data, net, force, forceLabels, hullg, hull, linkg, labelg, link, nodeg, node;
var nodes = [];
var labelAnchors = [];
var labelAnchorLinks = [];
var links = [];
setTimeout(() => {
data = createData(stakeholders_links)
for (var i=0; i<data.links.length; ++i) {
var o = data.links[i];
o.source = data.nodes[o.source];
o.target = data.nodes[o.target];
}
hullg = vis.append("g");
linkg = vis.append("g");
nodeg = vis.append("g");
labelg = vis.append("g");
init();
//transition effects
vis.attr("opacity", 1e-6)
.transition()
.duration(1000)
.attr("opacity", 1);
}, 0);
//creation of the hull curve
var curve = d3.svg.line()
.interpolate("cardinal-closed")
.tension(.85);//higher number, smoother outline of the hull
var fill = d3.scale.category20();
//Don't know the function of this function, no effect if you out comment it
// function noop() { return false; }
// if the group is expanded return the names otherwise return _g_+groupname
function nodeid(n) {
return n.size ? "_g_"+n.group : n.name;
}
//create the links between either groups and/or groups and names
function linkid(l) {
var u = nodeid(l.source),
v = nodeid(l.target);
// console.log(u<v ? u+"|"+v : v+"|"+u)
return u<v ? u+"|"+v : v+"|"+u;
}
function getGroup(n) { return n.group; }
// constructs the network to visualize
function network(data, prev, index, expand) {
expand = expand || {};
var gm = {}, // group map
nm = {}, // node map
lm = {}, // link map
gn = {}, // previous group nodes
gc = {}, // previous group centroids
nodes = [], // output nodes
links = []; // output links
// console.log( "start nodes")
// console.log(nodes);
// process previous nodes for reuse or centroid calculation
if (prev) {
prev.nodes.forEach(function(n) {
var i = index(n), o;
if (n.size > 0) {
gn[i] = n;
n.size = 0;
} else {
o = gc[i] || (gc[i] = {x:0,y:0,count:0});
o.x += n.x;
o.y += n.y;
o.count += 1;
}
});
}
// determine nodes
for (var k=0; k<data.nodes.length; ++k) {
// console.log(data.nodes[k])
var n = data.nodes[k],
i = index(n),
l = gm[i] || (gm[i]=gn[i]) || (gm[i]={group:i, size:0, nodes:[]});
if (expand[i]) {
// console.log("Expanded")
// console.log(data.nodes[k])
// the node should be directly visible
nm[n.name] = nodes.length;
nodes.push(n);
if (gn[i]) {
// place new nodes at cluster location (plus jitter)
n.x = gn[i].x + Math.random();
n.y = gn[i].y + Math.random();
}
} else {
// the node is part of a collapsed cluster
if (l.size == 0) {
// if new cluster, add to set and position at centroid of leaf nodes
// console.log("Not expanded")
// console.log(data.nodes[k])
nm[i] = nodes.length;
nodes.push(l);
if (gc[i]) {
l.x = gc[i].x / gc[i].count;
l.y = gc[i].y / gc[i].count;
}
}
l.nodes.push(n);
}
// always count group size as we also use it to tweak the force graph strengths/distances
l.size += 1;
n.group_data = l;
}
for (i in gm) { gm[i].link_count = 0; }
// determine links
for (k=0; k<data.links.length; ++k) {
var e = data.links[k],
u = index(e.source),
v = index(e.target);
if (u != v) {
gm[u].link_count++;
gm[v].link_count++;
}
u = expand[u] ? nm[e.source.name] : nm[u];
v = expand[v] ? nm[e.target.name] : nm[v];
var i = (u<v ? u+"|"+v : v+"|"+u),
l = lm[i] || (lm[i] = {source:u, target:v, size:0});
l.size += 1;
}
for (i in lm) { links.push(lm[i]); }
console.log("centroids:")
console.log(gc)
return {nodes: nodes, links: links};
}
function convexHulls(nodes, index, offset) {
var hulls = {};
// create point sets
for (var k=0; k<nodes.length; ++k) {
var n = nodes[k];
if (n.size) continue;
var i = index(n),
l = hulls[i] || (hulls[i] = []);
l.push([n.x-offset, n.y-offset]);
l.push([n.x-offset, n.y+offset]);
l.push([n.x+offset, n.y-offset]);
l.push([n.x+offset, n.y+offset]);
}
// create convex hulls
var hullset = [];
for (i in hulls) {
hullset.push({group: i, path: d3.geom.hull(hulls[i])});
}
return hullset;
}
function drawCluster(d) {
return curve(d.path); // 0.8
}
// --------------------------------------------------------
function init() {
if (force) force.stop();
labelAnchors.length=0;
labelAnchorLinks.length=0;
net = network(data, net, getGroup, expand);
//console.log("net nodes ")
//console.log(net.nodes);
//create data for repelling text data
for(var i = 0; i < net.nodes.length; i++) {
var node = {
label : "node " + i
};
//console.log("node", node)
// nodes.push(node);
labelAnchors.push({
node : net.nodes[i]
});
labelAnchors.push({
node : net.nodes[i]//node //
});
};
for(var i = 0; i < net.nodes.length; i++) {
labelAnchorLinks.push({
source : labelAnchors[i * 2],
target : labelAnchors[i * 2 + 1], //i * 2 + 1,
weight : 1
});
};
// console.log(labelAnchors);
// console.log(labelAnchorLinks)
force = d3.layout
.force()
.nodes(net.nodes)
.links(net.links)
.size([width, height])
.linkDistance(function(l, i) {
var n1 = l.source, n2 = l.target;
// larger distance for bigger groups:
// both between single nodes and _other_ groups (where size of own node group still counts),
// and between two group nodes.
//
// reduce distance for groups with very few outer links,
// again both in expanded and grouped form, i.e. between individual nodes of a group and
// nodes of another group or other group node or between two group nodes.
//
// The latter was done to keep the single-link groups ('blue', rose, ...) close.
// console.log(n1.size, n1.group, n2.group, Math.min((n1.size || (n1.group != n2.group ? n1.group_data.size : 0))))
// return 90 +
// Math.min(20 * Math.min((n1.size || (n1.group != n2.group ? n1.group_data.size : 0)),
// (n2.size || (n1.group != n2.group ? n2.group_data.size : 0))),
// -30 +
// 30 * Math.min((n1.link_count || (n1.group != n2.group ? n1.group_data.link_count : 0)),
// (n2.link_count || (n1.group != n2.group ? n2.group_data.link_count : 0))),
// 100);
return 90 +
Math.min(20 * Math.min((n1.size || (n1.group != n2.group ? n1.group_data.size*5 : 0)),
(n2.size || (n1.group != n2.group ? n2.group_data.size*5 : 0))),
-30 +
30 * Math.min((n1.link_count || (n1.group != n2.group ? n1.group_data.link_count*5 : 0)),
(n2.link_count || (n1.group != n2.group ? n2.group_data.link_count*5 : 0))),
100);
// //return 150;
})
.linkStrength(function(l, i) {
return 1;
})
.gravity(0.5) // gravity+charge tweaked to ensure good 'grouped' view (e.g. green group not smack between blue&orange, ...
//original 0.05 but higher value keeps the not linked nodes closer (0.5)
//.charge(-600) // ... charge is important to turn single-linked groups to the outside -600
.charge(function(d,i) {
// var n1 = l.source, n2 = l.target;
console.log("d")
console.log(d.size ? -100: 0)
return -500 + d.size ? -400: 0
})
.friction(0.5) // friction adjusted to get dampened display: less bouncy bouncy ball [Swedish Chef, anyone?]
force.start();
var force2 = d3.layout.force()
.nodes(labelAnchors)
.links(labelAnchorLinks)
.gravity(0)
.linkDistance(0)
.linkStrength(8)
.charge(-100)
// .size([w, h]);
force2.start();
hullg.selectAll("path.hull").remove();
hull = hullg.selectAll("path.hull")
.data(convexHulls(net.nodes, getGroup, off))
.enter().append("path")
.attr("class", "hull")
.attr("d", drawCluster)
.style("fill", function(d) { return fill(d.group); })
.style('fill-opacity', 0.3)
.on("click", function(d) {
//console.log("hull click", d, arguments, this, expand[d.group]);
expand[d.group] = false; init();
});
//------------from double graph code -----------------
var link = vis.selectAll("line.link").data(net.links)
link.exit().remove();
link.enter().append("svg:line")
.attr("class", "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; })
// .style("stroke-width", function(d) { return d.size || 1; })
.style("stroke-width", 1)
.style("stroke", "#333")
.style("stroke-opacity", 0.5)
.style("pointer-event", 'none');
var node = vis.selectAll("g.node").data(net.nodes)
node.exit().remove();
node.enter().append("svg:g")
.attr("class", "node");
node.append("svg:circle")
// if (d.size) -- d.size > 0 when d is a group node.
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; })
.style("fill", function(d) { return fill(d.group); })
.style('stroke-width', '2px')
.style('stroke',' #555')
.on("mouseover", function(d){
// d3.select(this).style("fill", function(d) { return fill(d.group).darker(2); })
d3.select(this).style("cursor", "pointer");
// console.log(this)
// console.log(d)
//d3.select(this).style("fill", 'darkblue')
})
.on("mouseout", function(d){
d3.select(this).style("cursor", "pointer");
//console.log(this)
//d3.select(this).style("fill", function(d) { return fill(d.group); })
})
.on("click", function(d) {
console.log("node click", d, arguments, this, expand[d.group]);
expand[d.group] = !expand[d.group];
init();
});
node.call(force.drag);
var anchorLink = vis.selectAll("line.anchorLink")
.data(labelAnchorLinks)
anchorLink.exit().remove();
anchorLink.enter().append("svg:line")
.attr("class", "anchorLink")
//.enter().append("svg:line").attr("class", "anchorLink").style("stroke", "#999");
//console.log(anchorLink)
console.log("anchor nodes select all")
console.log(vis.selectAll("g.anchorNode"))
var anchorNode = vis.selectAll("g.anchorNode")
.data(labelAnchors)
anchorNode.exit().remove();
console.log("anchor nodes select all after remove")
console.log(vis.selectAll("g.anchorNode"))
anchorNode.enter().append("svg:g")
.attr("class", "anchorNode");
anchorNode.append("svg:circle")
.attr("r", 0).style("fill", "#FFF");
anchorNode.append("svg:text")
.attr('x', 4)
.attr('y', 4)
.attr("class", "anchorText")
.text(function(d, i) {
if(i % 2 == 0){
return ""
} else {
//console.log(d.node.size);
if (d.node.size){
//console.log(d.node.group);
return d.node.group
} else {
//console.log(d.node.name);
return d.node.name
}
}
// d.size?d.group:d.name
// return i % 2 == 0 ? "" : d.node.group
}).style("fill", "#555").style("font-family", "Roboto").style("font-size", 12);
var updateLink = function() {
this.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;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
//console.log(d.x, d.y);
return "translate(" + d.x + "," + d.y + ")";
});
}
////
///// double force graph
force.on("tick", function() {
force2.start();
if (!hull.empty()) {
hull.data(convexHulls(net.nodes, getGroup, off))
.attr("d", drawCluster);
}
node.call(updateNode);
anchorNode.each(function(d, i) {
// console.log(d.node.size)
if(i % 2 == 0) {
// console.log(d);
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.childNodes[1].getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY =5;
this.childNodes[1].setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
anchorNode.call(updateNode);
link.call(updateLink);
anchorLink.call(updateLink);
});
}
return vis.node();
}