This is an example of using GEXDFD3 to host an interactive version of a dynamic k-partite network. It’s still a very rough little library and feature requests and bug reports are welcome.
Simply replace cosit.gexf with your own gexf.
If it has a dynamic attribute (in this case the “startyr” column) then you can brush the network by modifying the .dynamicAttribute() setting in the index where gexfD3 is set up.
If you have a k-partite network, ensure that the types of nodes are in a “type” column and multimodal network projection buttons will automatically be created.
<html xmlns="//www.w3.org/1999/xhtml">
<head>
<title>GEXF D3 w/ Dynamic Brushing and Reprojecting and Canvas Edges</title>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="gexfd3.css" />
</head>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/colorbrewer.v1.min.js"></script>
<script src="parser.js" type="text/javascript">
</script>
<script src="gexfd3.js" type="text/javascript">
</script>
<body onload="loadGraph('cosit.gexf')">
<div id="vizcontainer" style="width:100%;height:100%">
<canvas style="background: white;1000px;width:1000px;position:absolute;z-index:-1;" height=1000 width=1000></canvas>
<svg id="graphSVG" style="border:1px lightgray solid;">
<g id="graphG" />
<div id="modal"><div id="content"></div><button id="modalClose" onclick="nodeFocus=false;nodeOut();d3.select('#modal').style('display','none');">X</button></div>
</div>
<div id="controls">
</div>
<div id="brushDiv"><svg style="width:100%;height:50px;"><g id="brushG" class="brush"></g></svg></div>
<footer>
<script>
nodeFocus = false;
currentBrush =[0,0];
docHash = {};
allLinks = [];
currentScale = 0;
function loadGraph(sourceGEXF) {
newGEXF = GexfParser.fetch(sourceGEXF);
gD3 = gexfD3().graph(newGEXF).size([1000,1000]).dynamicAttribute("centrality");
force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([1000, 1000])
.gravity(.1)
.on("tick", redrawGraph)
d3.select("#brushG").call(gD3.dynamicBrush);
gD3.dynamicBrush().on("brush", brushMove);
zoom = d3.behavior.zoom()
.scaleExtent([.1, 10])
.on("zoom", zoomed);
allLinks = gD3.links();
for (x in allLinks) {
allLinks[x]["highlighted"] = false;
}
brushMove();
d3.select("svg").call(zoom);
createControls();
zoomed();
}
function highlightNeighbors(d,i) {
var nodeNeighbors = findNeighbors(d,i);
d3.selectAll("g.node").each(function(p) {
var isNeighbor = nodeNeighbors.nodes.indexOf(p);
d3.select(this).select("circle")
.style("opacity", isNeighbor > -1 ? 1 : .25)
.style("stroke-width", isNeighbor > -1 ? 3 : 1)
.style("stroke", isNeighbor > -1 ? "blue" : "white")
})
nodeNeighbors.links.forEach(function (link){
link.highlighted = true;
})
redrawGraph();
}
function findNeighbors(d,i) {
neighborArray = [d];
var linkArray = [];
filteredLinks.filter(function(p) {return p.source == d || p.target == d}).forEach(function(p) {
neighborArray.indexOf(p.source) == -1 ? neighborArray.push(p.source) : null;
neighborArray.indexOf(p.target) == -1 ? neighborArray.push(p.target) : null;
linkArray.push(p);
})
return {nodes: neighborArray, links: linkArray};
}
function zoomed() {
force.stop();
var canvWidth = parseInt(d3.select("#vizcontainer").style("width"));
var canvHeight = parseInt(d3.select("#vizcontainer").style("height"));
var canvasTranslate = zoom.translate();
currentScale = zoom.scale();
var halfCanvas = canvHeight / 2;
var zoomLevel = halfCanvas * currentScale;
gD3.xScale().range([(halfCanvas - zoomLevel) + canvasTranslate[0], (halfCanvas + zoomLevel) + canvasTranslate[0]]);
gD3.yScale().range([(halfCanvas + zoomLevel) + canvasTranslate[1], (halfCanvas - zoomLevel) + canvasTranslate[1]]);
redrawGraph();
}
function createControls() {
d3.select("#controls").append("button").attr("class", "origButton topology").html("Force On").on("click", function() {
force.start();})
d3.select("#controls").append("button").attr("class", "origButton topology").html("Force Off").on("click", function() {
force.stop();})
d3.select("#controls").append("button").attr("class", "origButton topology").html("Reset Layout").on("click", function() {
force.stop();
gD3.nodes().forEach(function (el) {el.x = el.originalX;el.px = el.originalX;el.y = el.originalY;el.py = el.originalY;});
currentBrush = [0,0];
brushMove();
redrawGraph();
})
d3.select("#controls").append("button").attr("class", "origButton projection").html("N-Partite").on("click", function() {
collapseNetwork("base");
})
d3.select("#controls").selectAll("button.networkProjectionButton").data(
d3.set(gD3.nodes().map(function(el) {return el.properties.type})).values()
)
.enter()
.append("button")
.attr("class", "networkProjectionButton projection")
.on("click", function(d) {collapseNetwork(d)})
.html(function(d) {return d + " Only"});
d3.select("#controls").append("button").attr("class", "origButton node-appearance").html("Reset Colors").on("click", function() {
var sizeScale = gD3.nodeScale();
d3.selectAll("circle")
.attr("r", function (d) {return sizeScale(d.size)})
.style("fill", function(d) {return d.rgbColor})
.style("opacity", 1);
})
d3.select("#controls").selectAll("button.nodeButtons").data(gD3.nodeAttributes())
.enter()
.append("button")
.attr("class", "nodeButtons node-appearance")
.on("click", nodeButtonClick)
.html(function(d) {return d});
d3.select("#controls").selectAll("button.linkButtons").data(gD3.linkAttributes())
.enter()
.append("button")
.attr("class", "linkButtons node-appearance")
.on("click", linkButtonClick)
.html(function(d) {return d});
}
function nodeButtonClick(d,i) {
var nodeAttExtent = d3.extent(filteredNodes, function(p) {return parseFloat(p.properties[d])});
var colorScale = d3.scale.quantize().domain(nodeAttExtent).range(colorbrewer.YlGnBu[6]);
d3.selectAll("circle").style("fill", function(p) {return colorScale(p.properties[d])}).style("opacity", 1)
}
function linkButtonClick(d,i) {
var linkAttExtent = d3.extent(filteredLinks, function(p) {return parseFloat(p.properties[d])});
var colorScale = d3.scale.quantize().domain(linkAttExtent).range(colorbrewer.YlGnBu[6]);
d3.selectAll("line").style("stroke", function(p) {return colorScale(p.properties[d])}).style("opacity", 1)
}
function redrawGraph() {
var xScale = gD3.xScale();
var yScale = gD3.yScale();
var context = d3.select("canvas").node
var canvas = d3.select("canvas").node();
var context = canvas.getContext("2d");
context.clearRect (0,0,canvas.width,canvas.height);
context.lineWidth = 1;
context.strokeStyle = "rgba(0, 0, 0, 0.5)";
filteredLinks.forEach(function (link) {
context.beginPath();
context.moveTo(xScale(link.source.x),yScale(link.source.y))
context.lineTo(xScale(link.target.x),yScale(link.target.y))
context.stroke();
})
context.lineWidth = 2;
context.strokeStyle = "rgba(0, 255, 122, 0.75)";
filteredLinks.filter(function (d) {return d.highlighted}).forEach(function (link) {
context.beginPath();
context.moveTo(xScale(link.source.x),yScale(link.source.y))
context.lineTo(xScale(link.target.x),yScale(link.target.y))
context.stroke();
})
d3.selectAll("g.node")
.attr("transform", function(d) {return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")"});
}
function collapseNetwork(collapseVector) {
currentBrush = [0,0];
dAtt = gD3.dynamicAttribute();
if (collapseVector == "base") {
gD3.links(allLinks);
brushMove();
return;
}
newLinks = [];
for (x in allLinks) {
if (allLinks[x].source.properties.type == collapseVector) {
for (y in allLinks) {
if (allLinks[y].source.properties.type == collapseVector) {
if (allLinks[y].target == allLinks[x].target && (allLinks[y].properties[dAtt] || allLinks[y].source.properties[dAtt]) == (allLinks[x].properties[dAtt] || allLinks[x].source.properties[dAtt])) {
var newLink = {id: collapseVector + newLinks.length, source: allLinks[x].source, target: allLinks[y].source, properties: {}};
if (gD3.linkAttributes().indexOf(dAtt) > -1) {
newLink.properties[dAtt] = allLinks[x].properties[dAtt];
}
else if (gD3.nodeAttributes().indexOf(dAtt) > -1) {
newLink.properties[dAtt] = allLinks[y].target.properties[dAtt];
}
newLinks.push(newLink);
}
}
}
}
else if (allLinks[x].target.properties.type == collapseVector) {
for (y in allLinks) {
if (allLinks[y].target.properties.type == collapseVector) {
if (allLinks[y].source == allLinks[x].source && (allLinks[y].properties[dAtt] || allLinks[y].target.properties[dAtt]) == (allLinks[x].properties[dAtt] || allLinks[x].target.properties[dAtt])) {
var newLink = {id: collapseVector + newLinks.length, source: allLinks[x].target, target: allLinks[y].target, properties: {}};
if (gD3.linkAttributes().indexOf(dAtt) > -1) {
newLink.properties[dAtt] = allLinks[x].properties[dAtt];
}
else if (gD3.nodeAttributes().indexOf(dAtt) > -1) {
newLink.properties[dAtt] = allLinks[y].source.properties[dAtt];
}
newLinks.push(newLink);
}
}
}
}
}
console.log(newLinks)
gD3.links(newLinks);
brushMove();
redrawGraph();
}
function brushMove() {
var s = gD3.dynamicBrush().extent();
var dAtt = gD3.dynamicAttribute();
var xScale = gD3.xScale();
var yScale = gD3.yScale();
var sizeScale = gD3.nodeScale();
if (Math.ceil(s[0]) == currentBrush[0] && Math.floor(s[1]) == currentBrush[1]) {
return;
}
else {
currentBrush[0] = Math.floor(s[0]);
currentBrush[1] = Math.ceil(s[1]);
}
var forceRunning = false;
if (force.alpha() > 0) {
force.stop();
forceRunning = true;
}
if (typeof gD3.links()[0].properties["startyr"] != "undefined") {
filteredLinks = gD3.links().filter(function (d) {return d.properties[dAtt] == 0 || (d.properties[dAtt] >= currentBrush[0] && d.properties[dAtt] <= currentBrush[1])});
sourceNodes = filteredLinks.map(function (el) {return el.source});
targetNodes = filteredLinks.map(function (el) {return el.target});
filteredNodes = gD3.nodes().filter(function (d) {return sourceNodes.indexOf(d) > -1 || targetNodes.indexOf(d) > -1});
}
else {
filteredLinks = gD3.links();
filteredNodes = gD3.nodes();
}
if (gD3.nodeAttributes().indexOf(dAtt) > -1) {
filteredNodes = filteredNodes.filter(function (d) {return d.properties[dAtt] == 0 || (d.properties[dAtt] >= currentBrush[0] && d.properties[dAtt] <= currentBrush[1])});
nodeIDs = filteredNodes.map(function (el) {return el.id})
filteredLinks = filteredLinks.filter(function (d) {return nodeIDs.indexOf(d.source.id) > -1 && nodeIDs.indexOf(d.target.id) > -1})
}
d3.select("#graphG").selectAll("g.node").data(filteredNodes, function (d) {return d.id})
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")"})
.on("mouseover", nodeOver)
.on("mouseout", nodeOut)
.on("click", nodeClick)
.append("circle")
.attr("r", 5)
.style("fill", function(d) {return d.rgbColor})
.style("stroke", "black")
.style("stroke-width", "1px")
.style("stroke-opacity", 1);
d3.selectAll("g.node").data(filteredNodes, function (d) {return d.id})
.exit()
.remove();
force
.nodes(filteredNodes)
.links(filteredLinks);
force.start();
var maxWeight = d3.max(filteredNodes, function(d) {return d.weight || 0});
if (maxWeight > 0) {
var nodeScale = d3.scale.linear().domain([1,maxWeight]).range([2,10]).clamp(true);
d3.selectAll("g.node").select("circle").attr("r", function(d) {return nodeScale(d.weight) || 0});
}
if (!forceRunning) {
force.stop();
}
function nodeOver(d,i,e) {
var el = this;
if (!d3.event.fromElement) {
el = e;
}
if (nodeFocus) {
return;
}
//Only do the element stuff if this came from mouseover
el.parentNode.appendChild(el);
d3.select(el).append("text").attr("class", "hoverLabel").attr("stroke", "white").attr("stroke-width", "5px")
.style("opacity", .9)
.style("pointer-events", "none")
.text(d.label);
d3.select(el).append("text").attr("class", "hoverLabel")
.style("pointer-events", "none")
.text(d.label);
highlightNeighbors(d,i);
}
function nodeClick(d,i) {
nodeFocus = false;
nodeOut();
nodeOver(d,i,this);
nodeFocus = true;
var newContent = "<p>" + d.label + "</p>";
newContent += "<p>Attributes: </p><p><ul>";
for (x in gD3.nodeAttributes()) {
newContent += "<li>" + gD3.nodeAttributes()[x] + ": " + d.properties[gD3.nodeAttributes()[x]]+ "</li>";
}
newContent += "</ul></p><p>Connections:</p><ul>";
var neighbors = findNeighbors(d,i);
for (x in neighbors.nodes) {
if (neighbors.nodes[x] != d) {
newContent += "<li>" + neighbors.nodes[x].label + "</li>";
}
}
newContent += "</ul></p>";
d3.select("#modal").style("display", "block").select("#content").html(newContent);
}
redrawGraph();
}
function createAnonymousEdgeTable() {
var htmlTable = "source,target,year<br>";
for (x in gD3.links()) {
htmlTable+=gD3.links()[x].source.properties.type + "-" + gD3.nodes().indexOf(gD3.links()[x].source) + "," + gD3.links()[x].target.properties.type + "-" + gD3.nodes().indexOf(gD3.links()[x].target) + "," + gD3.links()[x].properties.year;
htmlTable+="<br>";
}
d3.select("#content").html(htmlTable)
}
function nodeOut() {
if (nodeFocus) {
return;
}
d3.selectAll(".hoverLabel").remove();
d3.selectAll("circle").style("opacity", 1).style("stroke", "black").style("stroke-width", "1px");
filteredLinks.forEach(function (link) {link.highlighted = false});
redrawGraph();
}
</script>
</footer>
</body>
</html>
body {
overflow: hidden;
}
#modal {
position:fixed;
left:150px;
top:20px;
z-index:1;
background: white;
border: 1px black solid;
box-shadow: 10px 10px 5px #888888;
display: none;
}
#content {
max-height: 400px;
overflow: auto;
}
#modalClose {
position: absolute;
top: -0px;
right: -0px;
z-index: 1;
}
tr {
border: 1px gray solid;
}
td {
font-size: 10px;
}
td.data {
font-weight: 900;
}
.tick line {
shape-rendering: crispEdges;
stroke: #000;
}
line.minor {
stroke: #777;
stroke-dasharray: 2,2;
}
path.domain {
fill: none;
stroke: black;
}
.inactive, .tentative {
stroke: darkgray;
stroke-width: 4px;
stroke-dasharray: 5 5;
}
.tentative {
opacity: .5;
}
.active {
stroke: black;
stroke-width: 4px;
stroke-dasharray: 0;
}
circle {
fill: red;
}
rect {
fill: darkgray;
}
#controls {
position: fixed;
bottom: 50px;
left: 20px;
}
#brushDiv {
position: fixed;
bottom: 100px;
left: 20px;
right: 20px;
height:50px;
background: white;
opacity: .75;
}
.brush .extent {
fill-opacity: .90;
shape-rendering: crispEdges;
}
svg {
width: 100%;
height:100%;
}
button.topology {
background: darkgray;
color: white;
cursor: pointer;
}
button.projection:hover {
background: black;
color: white;
}
button.projection {
background: #B8B894;
color: white;
cursor: pointer;
}
button.topology:hover {
background: black;
color: white;
}
button.node-appearance {
background: gray;
color: white;
cursor: pointer;
}
button.node-appearance:hover {
background: black;
color: white;
}
gexfD3 =
function () {
var nodes = [];
var links = [];
var linksFile = "";
var fileName = "";
var xExtent = [];
var yExtent = [];
var nodeScale = [1,10];
var layoutSize = [500,500];
var sizeExtent = [];
var dAtt = "";
var dynamicExtent = [];
var sizeScale, xScale, yScale, dynamicScale;
var gexfD3Brush = d3.svg.brush();
var linkAttributes = [];
var nodeAttributes = [];
var nodeHash = {};
this.graph = function(gexfParsed) {
if (!arguments.length) return true;
var gNodes = gexfParsed.nodes;
var gLinks = gexfParsed.edges;
nodes = [];
links = [];
nodeHash = {};
//Create JSON nodes array
var x = 0;
gNodes.forEach(function(gNode) {
var newNode = {id: x, properties: {}};
newNode.label = gNode.label || gNode.id;
newNode.rgbColor = gNode.viz.color || "rgb(122,122,122)";
newNode.x = gNode.viz.position.x;
newNode.y = gNode.viz.position.y;
newNode.z = gNode.viz.position.z;
newNode.originalX = newNode.x;
newNode.originalY = newNode.y;
newNode.size = gNode.viz.size;
nodeHash[gNode.id] = newNode;
for (y in gNode.attributes) {
if (!(typeof(gNode.attributes[y]) === "undefined") && !(gNode.attributes[y].toString() == "NaN" )) {
newNode.properties[y] = gNode.attributes[y];
}
}
nodes.push(newNode);
x++;
})
//get node attributes based on attributes in the first node
//this won't work for assymetrical node attributes
nodeAttributes = d3.keys(nodes[0].properties);
//Create JSON links array
var x = 0;
while (x < gLinks.length) {
var newLink = {id: x, properties: {}};
newLink.source = nodeHash[gLinks[x].source];
newLink.target = nodeHash[gLinks[x].target];
newLink.properties.weight = gLinks[x].weight;
//process attributes
for (y in gLinks[x].attributes) {
newLink.properties[y] = gLinks[x].attributes[y];
y++;
}
links.push(newLink)
x++;
}
linkAttributes = d3.keys(links[0].properties);
sizeExtent = d3.extent(nodes, function(d) {return parseFloat(d.size)})
sizeScale = d3.scale.linear().domain(sizeExtent).range(nodeScale);
return this;
}
this.nodes = function(incNodes) {
if (!arguments.length) return nodes;
nodes = incNodes;
return this;
}
this.links = function(incLinks) {
if (!arguments.length) return links;
links = incLinks
return this;
}
this.linkAttributes = function(incAtts) {
if (!arguments.length) return linkAttributes;
linkAttributes = incAtts;
return this;
}
this.nodeAttributes = function(incAtts) {
if (!arguments.length) return nodeAttributes;
nodeAttributes = incAtts;
return this;
}
this.nodeScale = function(incScale) {
if (!arguments.length) return sizeScale;
nodeScale = incScale;
sizeScale = d3.scale.linear().domain(sizeExtent).range(nodeScale);
return this;
}
this.overwriteLinks = function(data) {
if (!arguments.length) return nodes;
//OVERWRITE links for parallel links
links = [];
for (x in data) {
var newLink = {id: x, properties: {}};
newLink.source = nodeHash[data[x].source];
newLink.target = nodeHash[data[x].target];
newLink.id = x;
newLink.properties.type = "base";
newLink.properties.year = data[x].year;
//process attributes
if (newLink.source && newLink.target) {
links.push(newLink);
}
x++;
}
linkAttributes = d3.keys(links[0].properties);
return this;
}
this.size = function(incSize) {
if (!arguments.length) return layoutSize;
//Measure
layoutSize = incSize;
xExtent = d3.extent(nodes, function(d) {return parseFloat(d.x)})
yExtent = d3.extent(nodes, function(d) {return parseFloat(d.y)})
xScale = d3.scale.linear().domain(xExtent).range([0,layoutSize[0]]);
yScale = d3.scale.linear().domain(yExtent).range([layoutSize[1],0]);
return this;
}
this.dynamicAttribute = function(incAtt) {
if (!arguments.length) return dAtt;
dAtt = incAtt;
var nDE = [Infinity, -Infinity];
var lDE = [Infinity, -Infinity];
if (nodeAttributes.indexOf(dAtt) > -1) {
//currently filters out 0 entries
// nDE = d3.extent(nodes, function(d) {return parseInt(d.properties[dAtt])})
nDE = d3.extent(nodes.filter(function(p) {return p.properties[dAtt] != 0}), function(d) {return parseInt(d.properties[dAtt])})
}
if (linkAttributes.indexOf(dAtt) > -1) {
// lDE = d3.extent(links, function(d) {return parseInt(d.properties[dAtt])})
lDE = d3.extent(links.filter(function(p) {return p.properties[dAtt] != 0}), function(d) {return parseInt(d.properties[dAtt])})
}
dynamicExtent = [Math.min(nDE[0],lDE[0]), Math.max(nDE[1],lDE[1])]
dynamicScale = d3.scale.linear().domain(dynamicExtent).range([0,layoutSize[0]]);
return this;
}
this.dynamicBrush = function(incSelection) {
if (!arguments.length) return gexfD3Brush;
gexfD3Brush
.x(dynamicScale)
.extent(dynamicExtent)
var brushAxis = d3.svg.axis().scale(dynamicScale).orient("bottom").tickSize(-40).ticks(20).tickFormat(d3.format("d"));
incSelection.append("g").attr("id", "bgAxis").append("g").attr("transform", "translate(50,35)").call(brushAxis)
incSelection.append("g").attr("id", "fgBrush").attr("transform", "translate(50,0)")
.call(gexfD3Brush)
.selectAll("rect").attr("height", 35);
return this;
}
this.xScale = function(newScale) {
if (!arguments.length) return xScale;
xScale = newScale;
return this;
}
this.yScale = function(newScale) {
if (!arguments.length) return yScale;
yScale = newScale;
return this;
}
return this;
}
;(function(undefined) {
'use strict';
/**
* GEXF Parser
* ============
*
* Author: PLIQUE Guillaume (Yomguithereal)
* URL: https://github.com/Yomguithereal/gexf-parser
* Version: 1.0
*/
/**
* Helper Namespace
* -----------------
*
* A useful batch of function dealing with DOM operations and types.
*/
var _helpers = {
nodeListToArray: function(nodeList) {
// Return array
var children = [];
// Iterating
for (var i = 0, len = nodeList.length; i < len; ++i) {
if (nodeList[i].nodeName !== '#text')
children.push(nodeList[i]);
}
return children;
},
nodeListEach: function(nodeList, func) {
// Iterating
for (var i = 0, len = nodeList.length; i < len; ++i) {
if (nodeList[i].nodeName !== '#text')
func(nodeList[i]);
}
},
nodeListToHash: function(nodeList, filter) {
// Return object
var children = {};
// Iterating
for (var i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeName !== '#text') {
var prop = filter(nodeList[i]);
children[prop.key] = prop.value;
}
}
return children;
},
namedNodeMapToObject: function(nodeMap) {
// Return object
var attributes = {};
// Iterating
for (var i = 0; i < nodeMap.length; i++) {
attributes[nodeMap[i].name] = nodeMap[i].value;
}
return attributes;
},
getFirstElementByTagNS: function(node, ns, tag) {
var el = node.getElementsByTagName(ns + ':' + tag)[0];
if (!el)
el = node.getElementsByTagNameNS(ns, tag)[0];
if (!el)
el = node.getElementsByTagName(tag)[0];
return el;
},
getAttributeNS: function(node, ns, attribute) {
var attr_value = node.getAttribute(ns + ':' + attribute);
if (attr_value === undefined)
attr_value = node.getAttributeNS(ns, attribute);
if (attr_value === undefined)
attr_value = node.getAttribute(attribute);
return attr_value;
},
enforceType: function(type, value) {
switch (type) {
case 'boolean':
value = (value === 'true');
break;
case 'integer':
case 'long':
case 'float':
case 'double':
value = +value;
break;
}
return value;
},
getRGB: function(values) {
return (values[3]) ?
'rgba(' + values.join(',') + ')' :
'rgb(' + values.slice(0, -1).join(',') + ')';
}
};
/**
* Parser Core Functions
* ----------------------
*
* The XML parser's functions themselves.
*/
/**
* Node structure.
* A function returning an object guarded with default value.
*
* @param {object} properties The node properties.
* @return {object} The guarded node object.
*/
function Node(properties) {
// Possible Properties
return {
id: properties.id,
label: properties.label,
attributes: properties.attributes || {},
viz: properties.viz || {}
};
}
/**
* Edge structure.
* A function returning an object guarded with default value.
*
* @param {object} properties The edge properties.
* @return {object} The guarded edge object.
*/
function Edge(properties) {
// Possible Properties
return {
id: properties.id,
type: properties.type || 'undirected',
label: properties.label || '',
source: properties.source,
target: properties.target,
weight: +properties.weight || 1.0,
viz: properties.viz || {}
};
}
/**
* Graph parser.
* This structure parse a gexf string and return an object containing the
* parsed graph.
*
* @param {string} xml The xml string of the gexf file to parse.
* @return {object} The parsed graph.
*/
function Graph(xml) {
var _xml = {};
// Basic Properties
//------------------
_xml.els = {
root: xml.getElementsByTagName('gexf')[0],
graph: xml.getElementsByTagName('graph')[0],
meta: xml.getElementsByTagName('meta')[0],
model: xml.getElementsByTagName('attribute'),
nodes: xml.getElementsByTagName('node'),
edges: xml.getElementsByTagName('edge')
};
_xml.hasViz = !!_helpers.getAttributeNS(_xml.els.root, 'xmlns', 'viz');
_xml.version = _xml.els.root.getAttribute('version') || '1.0';
_xml.mode = _xml.els.graph.getAttribute('mode') || 'static';
var edgeType = _xml.els.graph.getAttribute('defaultedgetype');
_xml.defaultEdgetype = edgeType || 'undirected';
// Parser Functions
//------------------
// Meta Data
function _metaData() {
var metas = {};
if (!_xml.els.meta)
return metas;
// Last modified date
metas.lastmodifieddate = _xml.els.meta.getAttribute('lastmodifieddate');
// Other information
_helpers.nodeListEach(_xml.els.meta.childNodes, function(child) {
metas[child.tagName.toLowerCase()] = child.textContent;
});
return metas;
}
// Model
function _model() {
var attributes = [];
// Iterating through attributes
_helpers.nodeListEach(_xml.els.model, function(attr) {
// Properties
var properties = {
id: attr.getAttribute('id') || attr.getAttribute('for'),
type: attr.getAttribute('type') || 'string',
title: attr.getAttribute('title') || ''
};
// Defaults
var default_el = _helpers.nodeListToArray(attr.childNodes);
if (default_el.length > 0)
properties.defaultValue = default_el[0].textContent;
// Creating attribute
attributes.push(properties);
});
return attributes;
}
// Nodes
function _nodes(model) {
var nodes = [];
// Iteration through nodes
_helpers.nodeListEach(_xml.els.nodes, function(n) {
// Basic properties
var properties = {
id: n.getAttribute('id'),
label: n.getAttribute('label') || ''
};
// Retrieving data from nodes if any
if (model.length > 0)
properties.attributes = _nodeData(model, n);
// Retrieving viz information
if (_xml.hasViz)
properties.viz = _nodeViz(n);
// Pushing node
nodes.push(Node(properties));
});
return nodes;
}
// Data from nodes
function _nodeData(model, node) {
var data = {};
var attvalues_els = node.getElementsByTagName('attvalue');
// Getting Node Indicated Attributes
var ah = _helpers.nodeListToHash(attvalues_els, function(el) {
var attributes = _helpers.namedNodeMapToObject(el.attributes);
var key = attributes.id || attributes['for'];
// Returning object
return {key: key, value: attributes.value};
});
// Iterating through model
model.map(function(a) {
// Default value?
var att_title = a.title.toLowerCase();
data[att_title] = !(a.id in ah) && 'defaultValue' in a ?
_helpers.enforceType(a.type, a.defaultValue) :
_helpers.enforceType(a.type, ah[a.id]);
});
return data;
}
// Viz information from nodes
function _nodeViz(node) {
var viz = {};
// Color
var color_el = _helpers.getFirstElementByTagNS(node, 'viz', 'color');
if (color_el) {
var color = ['r', 'g', 'b', 'a'].map(function(c) {
return color_el.getAttribute(c);
});
viz.color = _helpers.getRGB(color);
}
// Position
var pos_el = _helpers.getFirstElementByTagNS(node, 'viz', 'position');
if (pos_el) {
viz.position = {};
['x', 'y', 'z'].map(function(p) {
viz.position[p] = +pos_el.getAttribute(p);
});
}
// Size
var size_el = _helpers.getFirstElementByTagNS(node, 'viz', 'size');
if (size_el)
viz.size = +size_el.getAttribute('value');
// Shape
var shape_el = _helpers.getFirstElementByTagNS(node, 'viz', 'shape');
if (shape_el)
viz.shape = shape_el.getAttribute('value');
return viz;
}
// Edges
function _edges(default_type) {
var edges = [];
// Iteration through edges
_helpers.nodeListEach(_xml.els.edges, function(e) {
// Creating the edge
var properties = _helpers.namedNodeMapToObject(e.attributes);
if (!('type' in properties)) {
properties.type = default_type;
}
// Retrieving viz information
if (_xml.hasViz)
properties.viz = _edgeViz(e);
edges.push(Edge(properties));
});
return edges;
}
// Viz information from edges
function _edgeViz(edge) {
var viz = {};
// Color
var color_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'color');
if (color_el) {
var color = ['r', 'g', 'b', 'a'].map(function(c) {
return color_el.getAttribute(c);
});
viz.color = _helpers.getRGB(color);
}
// Shape
var shape_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'shape');
if (shape_el)
viz.shape = shape_el.getAttribute('value');
// Thickness
var thick_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'thickness');
if (thick_el)
viz.thickness = +thick_el.getAttribute('value');
return viz;
}
// Returning the Graph
//---------------------
_xml.model = _model();
return {
version: _xml.version,
mode: _xml.mode,
defaultEdgeType: _xml.defaultEdgetype,
meta: _metaData(),
model: _xml.model,
nodes: _nodes(_xml.model),
edges: _edges(_xml.defaultEdgetype)
};
}
/**
* Public API
* -----------
*
* User-accessible functions.
*/
// Fetching GEXF with XHR
function fetch(gexf_url, callback) {
var xhr = (function() {
if (window.XMLHttpRequest)
return new XMLHttpRequest();
var names,
i;
if (window.ActiveXObject) {
names = [
'Msxml2.XMLHTTP.6.0',
'Msxml2.XMLHTTP.3.0',
'Msxml2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for (i in names)
try {
return new ActiveXObject(names[i]);
} catch (e) {}
}
return null;
})();
if (!xhr)
throw 'XMLHttpRequest not supported, cannot load the file.';
// Async?
var async = (typeof callback === 'function'),
getResult;
// If we can't override MIME type, we are on IE 9
// We'll be parsing the response string then.
if (xhr.overrideMimeType) {
xhr.overrideMimeType('text/xml');
getResult = function(r) {
return r.responseXML;
};
}
else {
getResult = function(r) {
var p = new DOMParser();
return p.parseFromString(r.responseText, 'application/xml');
};
}
xhr.open('GET', gexf_url, async);
if (async)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4)
callback(getResult(xhr));
};
xhr.send();
return (async) ? xhr : getResult(xhr);
}
// Parsing the GEXF File
function parse(gexf) {
return Graph(gexf);
}
// Fetch and parse the GEXF File
function fetchAndParse(gexf_url, callback) {
if (typeof callback === 'function') {
return fetch(gexf_url, function(gexf) {
callback(Graph(gexf));
});
} else
return Graph(fetch(gexf_url));
}
/**
* Exporting
* ----------
*/
this.GexfParser = {
// Functions
parse: parse,
fetch: fetchAndParse,
// Version
version: '0.1'
};
}).call(this);