var nodesOld = []; //contains the original graph nodes
var linksOld = []; //contains the original graph links
var nodes_communitys; //contains all nodes with its community
var amountMetaN; //contains the overall number of communities
//contains the new meta-nodes
var newMetaNodes = [];
//contains the new meta-links
var newLinks = [];
var linksD = []; //contains the meta-links in a drawable form
var nodesD = []; //contains the meta-nodes in a drawable form
var dataset = []; //contains data for statistic
/**
* This function creates metanodes out of nodes according to their community attribute.
* This is done by creating for every community a node and the size of the node is determined by the number of associated
* nodes. The strength of the links between these new nodes, which represent the metanodes, is determined by the number
* of links between the original nodes. The more connections between a pair of communities exists, the bigger the link is
* drawn.
* @param {Array} nodesV contains all nodes of the original graph
* @param {Array} linksV contains all links of the original graph
* @param {Array} result contains the id's of all nodes with its community number
* @param {number} communityCount contains the overall number of communities
*/
function createMetanodes(nodesV,linksV,result,communityCount)
{
//initalize empty
nodesOld = []; //contains the original graph nodes
linksOld = []; //contains the original graph links
nodes_communitys; //contains all nodes with its community
amountMetaN; //contains the overall number of communities
newMetaNodes = [];
newLinks = [];
linksD = []; //contains the meta-links in a drawable form
nodesD = []; //contains the meta-nodes in a drawable form
dataset = []; //contains data for statistic
nodesOld = nodesV;
linksOld = linksV;
nodes_communitys = result;
//need as many nodes as communities
amountMetaN = communityCount;
//for every community create metanode
for(var i = 0; i < amountMetaN; i++)
{
var metaN = createMetaNode(i);
newMetaNodes[i] = metaN;
}
//will contain the new links
newLinks = createInterANDIntraNodes();
//create dataset for the statistic
var metaNodeNames = [];
for (var key in newMetaNodes)
{
//name
var name = "" + key;
metaNodeNames.push(name);
var dataP =
{
label: name,
"Nodes": newMetaNodes[key].amountN,
"IntraEdges": newLinks[key].intraEdges.length,
"InterEdges": newLinks[key].interEdges.length
}
dataset.push(dataP);
}
//create nodes and links for drawing the meta-node graph
var i = 0;
for (var m = 0; m <newLinks.length; m++ )
{
for(var key in newLinks[m])
{
for(var n = 0; n <newLinks[m][key].length; n++ )
{
link = newLinks[m][key][n];
linksD[i] = {source: link.source,
target: link.target
};
i = i+1;
}
}
}
// compute nodes from links data
linksD.forEach(function(link) {
link.source = nodesD[link.source] ||
(nodesD[link.source] = {id: link.source});
link.target = nodesD[link.target] ||
(nodesD[link.target] = {id: link.target});
});
}
/**
* This function creates one metanode to the given community number.
* For every metanode the amount of belonging nodes is calculated and their id's are stored.
* @param {number} communityNr current community number which forms a metanode
* @param {Array} nodes_communitys contains all nodes with its community
* @return {Object} result which contains as properties the overall amount of belonging nodes and an array of ids
* the belonging nodes
*/
function createMetaNode(communityNr)
{
amountNode = 0;
belongingNodes = [];
var i = 0;
//for all nodes which have the recent communityNr
for(var nodeId in nodes_communitys) {
var comm = nodes_communitys[nodeId];
if(comm == communityNr)
{
//metanode
belongingNodes.push(nodeId);
amountNode = amountNode +1;
}
i++;
}
var result =
{ amountN: amountNode,
belongingN: belongingNodes,
id: communityNr
};
return result;
}
/**
* This function creates inter- and intra-edges according to the current metanode.
* The intra-edges are calculated for every metanode by using the links in the original node and looking through every stored
* node of the metanode, if there is a connection to another node with the same community number. For inter-edges the same
* is done, with the difference that connections to nodes with other communitiy numbers are sought.
* @return {Array} nL is an array of objects which contains the inter- and intra-links of all metanodes.
*/
function createInterANDIntraNodes()
{ //find according links
var nL = [];
for(var metaNodeId = 0; metaNodeId < newMetaNodes.length; metaNodeId++)
{
var intraEdges = []; //id of edges inside the same community
var interEdges = []; //id of edges to other communities
for(var i = 0; i < newMetaNodes[metaNodeId].belongingN.length; i++)
{
var currentN = newMetaNodes[metaNodeId].belongingN[i];
for(var key in linksOld)
{
if (linksOld[key].source.id == currentN)
{
var target = linksOld[key].target.id;
if(nodesOld[target].module == metaNodeId.toString() )
{ //target node belongs to the same community
var edge = {
source: metaNodeId,
target: nodesOld[target].module
};
intraEdges.push(edge);
}else
{ //target node belongs to another community
var edge = {
source: metaNodeId,
target: nodesOld[target].module
};
interEdges.push(edge);
}
}
}
}
nL[metaNodeId] = {
intraEdges: intraEdges,
interEdges: interEdges
};
}
return nL;
}
/**
* This functions draws the metanode graph and the statistics. For the metanode graph a d3 force layout is created on a
* svg. The statistic is drawn as grouped bar chart, which is also located on an svg. Also a close button is created to
* delete these two svgs.
*/
function drawMetaNodeGraphAndStatistic()
{
var svg = d3.select("svg");
//metaNodeSVG is appended on original svg
var metaNodeSVG = svg.append("svg")
.attr("id", "metaNodeSVG")
.attr("width", svg.attr("width")*0.6) //69%
.attr("height", svg.attr("height"))
.attr("x", svg.attr("width")*0.4)
.classed("metaNodeContainer",true);
//metaNodeSVG is appended on original svg
var statisticSVG = svg.append("svg")
.attr("id", "statisticSVG")
.attr("width", svg.attr("width")*0.4)
.attr("height", svg.attr("height"))
.classed("statisticContainer",true);
drawMetaNodes(metaNodeSVG);
calculateStatistic(statisticSVG);
// Close Button
createMetaNodeCloseButton();
}
/**
* This function uses a given svg to draw a grouped bar chart on it. Here for every metanode the amount of belonging nodes
* is shown, as well as the amount of intra- and inter-edges.
* @param {SVG}statisticSVG contains the SVG in which the grouped bar chart is drawn
*/
function calculateStatistic(statisticSVG)
{
//draw black border
statisticSVG.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white")
.attr("stroke", "#616161")
.attr("stroke-width", 1)
.attr("fill-opacity", 1)
.attr("stroke-opacity",1);
var attributeNames = d3.keys(dataset[0]).filter(function(key) { return key !== "label"; });
dataset.forEach(function(d) {
d.amounts = attributeNames.map(function(name) { return {name: name, value: +d[name]}; });
});
// drawing
var widthP = statisticSVG.attr("width")/100;
var heightP = statisticSVG.attr("height")/100;
var margin = {top: 10*heightP, right: 10*widthP, bottom: 25*heightP, left: 10*widthP},
width = statisticSVG.attr("width")- margin.left - margin.right,
height = statisticSVG.attr("height") - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([margin.left*0.7, width], 0.1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, margin.bottom]);
var xAxis = d3.svg.axis()
.scale(x0)
.tickSize(0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("right");
var color = d3.scale.ordinal()
.range(["#e41a1c","#377eb8","#4daf4a"]);
//give names to chart
x0.domain(dataset.map(function(d) { return d.label; }));
x1.domain(attributeNames).rangeRoundBands([0, x0.rangeBand()])
//give data to chart
y.domain([0, d3.max(dataset, function(d) { return d3.max(d.amounts, function(d) { return d.value; }); })]);
statisticSVG.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+margin.left+"," + height + ")") //change here for bar width and location
.call(xAxis);
//create the y-axis and a y-axis text label
statisticSVG.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left*0.5 + ", 0)") //change black y-axis here
.call(yAxis)
.append("text")
.attr("x",-13*heightP)
.attr("y", margin.left*0.5)
.attr("transform", "rotate(-90)")
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Amount");
var bar = statisticSVG.selectAll(".bar")
.data(dataset)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) { return "translate(" +x0(d.label) + ",0)"; });
bar.selectAll("rect")
.data(function(d) { return d.amounts; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); })
.attr("transform", function(d) { return "translate(" +margin.left + ",0)"; });;
//right legend
var legend = statisticSVG.selectAll(".legend")
.data(attributeNames.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i*20 + ")"; })
legend.append("rect")
.attr("x", width - 5*widthP)
.attr("y", 6.5*heightP)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 8*widthP)
.attr("y", 8*heightP)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
}
/**
* This functions draws the metaNode graph on a delivered svg. For every community number a meta-node is drawn with its
* related color. The size of the node is calculated according to the amount of nodes beloning to the certain community
* The strength of the links is dependent on the number of inter-edges to the respective other communities.
* @param {SVG} metaNodeSVG contains the SVG on which the graph will be drawn
*/
function drawMetaNodes(metaNodeSVG)
{
var widthP = metaNodeSVG.attr("width")/100;
var heightP = metaNodeSVG.attr("height")/100;
var margin = {top: 0*heightP, right: 0*widthP, bottom: 0*heightP, left: 0*widthP};
var widthM = metaNodeSVG.attr("width")- margin.left - margin.right;
var heightM = metaNodeSVG.attr("height") - margin.top - margin.bottom;
//set background color of metaNodeSVG
metaNodeSVG.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
//define layout for metaNode-graph
var force = d3.layout.forceInABox()
.groupBy("function (d) {return d.id}")
.gravityOverall(0.5)
.size([widthM, heightM])
.linkStrengthInterCluster(0.5)
.charge(-3000);
force
.nodes(d3.values(nodesD))
.links(linksD)
.on("tick", tick);
// Draw the graph lines
var link = metaNodeSVG.selectAll(".link")
.data(linksD)
.enter().append("line")
.attr("class", "link")
.attr("id", function(d,i){ return "link" + i});
// Draw node as a circle.
var node = metaNodeSVG.selectAll(".node")
.data(force.nodes())
.enter().append("g")
var circle = node.append("circle")
.attr("class", "node")
.attr("id", function(d,i){ return "node" + i});
//calculate attributes of links
var norm = Math.sqrt(Math.pow(width,2) + Math.pow(height,2));
//force.linkDistance(norm/10);
force.linkDistance(norm/7);
//calculate links according to amount of links
link.style("stroke-width", function (d)
{
var percent = 100/linksD.length;
var interEdges = newLinks[d.source.id].interEdges;
var size = 0;
for(var i = 0; i < interEdges.length; i++)
{
if(interEdges[i].target == d.target.id)
{
size = size+1;
}
}
size = size*percent;
return 1+(2*size); //without additional factor
});
//calculate origin radius of nodes
norm = norm/300;
if(norm > 7) norm = 7;
var radius = norm;
//calculate nodes according to amount of nodes in metaNode
var color = d3.scale.category20();//color nodes according to their cluster
circle.attr("r", function(d)
{
var percent = 100/amountMetaN;
var size = (newMetaNodes[d.id].amountN*percent/100); //size is between [0,1]
//calculate radius according to amount of nodes
//return radius + (5*size) //with additional factor
var r = radius + (2*size);
newMetaNodes[d.id]["radius"] = r;
return r;
});
circle.style("fill", function(d){return color(d.id);})
var nodeName = node.append("svg:text")
.attr("class", "text")
.attr("dy", ".35em")
.text(function(d) {return d.id});
force.start();
function tick(e) {
circle.attr("cx", function(d) {
return d.x = Math.max(newMetaNodes[d.id].radius, Math.min(widthM - newMetaNodes[d.id].radius, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(newMetaNodes[d.id].radius, Math.min(heightM - newMetaNodes[d.id].radius, d.y)); });
nodeName.attr("x", function (d) {return d.x;})
.attr("y", function (d) {return d.y - 4*heightP;});
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; });
}
}
/**
* This function deletes the svgs which store the meta-graph and the statistic. Therefore all existing svgs are located and
* the svgs with the id's "metaNodeSVG" and "metaNodeSVG" are removed. Also the close button is removed.
*/
function deleteGraphANDStatistic()
{
var svgs = d3.selectAll("svg")[0];
var flagDelete = [];
for(var i = 0; i <svgs.length; i++)
{
if((svgs[i].id == "metaNodeSVG") || (svgs[i].id == "statisticSVG")) {
flagDelete[i] = 1;
} else
{
flagDelete[i] = 0;
}
}
for(var i = 0; i <svgs.length; i++)
{
if(flagDelete[i] == 1)
{
svgs[i].remove();
}
}
//remove close button
var con = document.getElementById("svgContainer");
var children = con.childNodes;
for(var c = 0; c < children.length; c++) {
if (children[c].id == "closeButton") {
con.removeChild(children[c]);
}
}
}
/**
* This function adds a close button to the container holding the svgs which contain the metanode graph and the statistic.
* This is done by searching this container and appending a button on it. When this button is clicked, the meta-node
* force layout and the grouped bar chart representing the statistic are deleted.
*/
function createMetaNodeCloseButton()
{
var MetaOverlay = document.getElementById("svgContainer");
// Button
var closeButton = document.createElement("SPAN");
closeButton.classList.add("w3-button");
closeButton.classList.add("w3-theme-l2");
closeButton.classList.add("w3-display-topright");
closeButton.setAttribute("id", "closeButton");
MetaOverlay.appendChild(closeButton);
// Icon
var closeIcon = document.createElement("SPAN");
closeIcon.classList.add("fa");
closeIcon.classList.add("fa-remove");
closeIcon.classList.add("fa-lg");
closeButton.appendChild(closeIcon);
// Close function
closeButton.addEventListener("click", function() {deleteGraphANDStatistic() });
}