block by emeeks 6038a697b1d80ff628c9

GEXFD3 with Canvas

Full Screen

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.

index.html

<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>

gexfd3.css

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.js

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;
    }

parser.js

;(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);