Skip to content
Snippets Groups Projects
Commit 059e8e0b authored by Katja's avatar Katja
Browse files

documentation stuff

parent 83bf01a5
No related branches found
No related tags found
No related merge requests found
/** /**
* creates a new zoom behavior * creates a new zoom behavior
*/ */
var zoom = d3.zoom().on("zoom", zoomHandler); var zoom = d3.zoom().on("zoom", handle_zoom);
/** /**
* creates svg object and associated attributes * creates svg object and associated attributes
...@@ -11,25 +11,26 @@ var svg = d3.select("svg.graph") ...@@ -11,25 +11,26 @@ var svg = d3.select("svg.graph")
.call(zoom), .call(zoom),
width = svg.attr("width"), width = svg.attr("width"),
height = svg.attr("height"); height = svg.attr("height");
var textinfo=''; var text_info='';
var textabstract=''; var text_abstract='';
var perc; var perc;
/** /**
* creates node object and associated attributes * creates node object and radius
*/ */
var node, var node,
r=10, r=10,
/**
* scale functions that return y coordinate/color of node depending on group
*/
color = d3.scaleOrdinal() color = d3.scaleOrdinal()
.domain(["height", "input", "depth"]) .domain(["height", "input", "depth"])
.range([' #01d7c0', ' #8b90fe ', ' #a15eb2 ']), .range([' #01d7c0', ' #8b90fe ', ' #a15eb2 ']),
yscale = d3.scaleOrdinal() y_scale = d3.scaleOrdinal()
.domain(["height", "input", "depth"]) .domain(["height", "input", "depth"])
.range([0, 200, 400]), .range([0, 200, 400]),
linetype=d3.scaleOrdinal() to_remove;
.domain(["line","dotted"])
.range([("8,0"),("8,8")]),
toRemove;
/** /**
* creates link object * creates link object
...@@ -46,19 +47,22 @@ var rect = svg.append("rect") ...@@ -46,19 +47,22 @@ var rect = svg.append("rect")
.attr("height", height) .attr("height", height)
.attr("width", width) .attr("width", width)
.style("fill", 'white') .style("fill", 'white')
.on('click', clickRect); .on('click', click_rect);
/** /**
* creates svg object (legend) and associated attributes * creates svg object (legend) and associated attributes
*/ */
var svglegend = d3.select("svg.legendsvg"), var svg_legend = d3.select("svg.legendsvg"),
legendposition = [65,95,125], legend_position = [65,95,125],
arrowlegendposition = [0,25], arrow_legend_position = [0,25],
arrowgroupnames = ["citation","self-citation"], arrow_group_names = ["citation","self-citation"],
groupnames = ["cited by","input","reference"]; group_names = ["cited by","input","reference"],
line_type=d3.scaleOrdinal()
.domain(["line","dotted"])
.range([("8,0"),("8,8")]);
var legend = svglegend.selectAll(".legend") var legend = svg_legend.selectAll(".legend")
.data(legendposition) .data(legend_position)
.enter() .enter()
.append("g") .append("g")
.attr("class","legend") .attr("class","legend")
...@@ -69,37 +73,37 @@ legend.append("text") ...@@ -69,37 +73,37 @@ legend.append("text")
.attr("y", 0) .attr("y", 0)
.attr("dy", ".35em") .attr("dy", ".35em")
.style("text-anchor", "start") .style("text-anchor", "start")
.text(function(d,i){return groupnames[i]}); .text(function(d,i){return group_names[i]});
legend.append("circle") legend.append("circle")
.attr("r", r) .attr("r", r)
.attr("cx",30-r) .attr("cx",30-r)
.style("fill", color); .style("fill", color);
var legendarrow = svglegend.selectAll(".legendarr") var legend_arrow = svg_legend.selectAll(".legendarr")
.data(arrowlegendposition) .data(arrow_legend_position)
.enter() .enter()
.append("g") .append("g")
.attr("class","legendarr") .attr("class","legendarr")
.attr("transform", function(d) { return "translate(0," + d + ")"; }); .attr("transform", function(d) { return "translate(0," + d + ")"; });
legendarrow.append("line") legend_arrow.append("line")
.attr("x1", 10) .attr("x1", 10)
.attr("x2", 50) .attr("x2", 50)
.attr("y1", 10) .attr("y1", 10)
.attr("y2", 10) .attr("y2", 10)
.style("stroke-dasharray",linetype) .style("stroke-dasharray",line_type)
.style("stroke", '#999') .style("stroke", '#999')
.style("stroke-width", "1px") .style("stroke-width", "1px")
.style('pointer-events', 'none') .style('pointer-events', 'none')
.attr('marker-end',updateMarker('#999',this)); .attr('marker-end',update_marker('#999',this));
legendarrow.append("text") legend_arrow.append("text")
.attr("x", 80) .attr("x", 80)
.attr("y", 10) .attr("y", 10)
.attr("dy", ".35em") .attr("dy", ".35em")
.style("text-anchor", "start") .style("text-anchor", "start")
.text(function(d,i){return arrowgroupnames[i]}); .text(function(d,i){return arrow_group_names[i]});
/** /**
* creates a new simulation * creates a new simulation
...@@ -120,9 +124,9 @@ var simulation = d3.forceSimulation() ...@@ -120,9 +124,9 @@ var simulation = d3.forceSimulation()
.force("yscale", d3.forceY().strength(function(d) { .force("yscale", d3.forceY().strength(function(d) {
if (d.group == "input") {return 1000;} if (d.group == "input") {return 1000;}
else {return 50;} else {return 50;}
}).y(function(d) {return yscale(d.group)})) }).y(function(d) {return y_scale(d.group)}))
.alpha(0.005) .alpha(0.005)
.on("end", zoomTo); .on("end", zoom_to);
/** /**
* creates group element * creates group element
...@@ -139,29 +143,29 @@ d3.json("json_text.json").then(function(graph) { ...@@ -139,29 +143,29 @@ d3.json("json_text.json").then(function(graph) {
/** /**
* calls update functions for links and nodes * calls update functions for links and nodes
* adds the nodes and links to the simulation * adds the nodes, links and tick functionailty to the simulation
* @param {object} nodes - nodes * @param {object} nodes - nodes
* @param {object} links - links * @param {object} links - links
*/ */
function update(links, nodes) { function update(links, nodes) {
updateLinks(links); update_links(links);
updateNodes(nodes); update_nodes(nodes);
simulation simulation
.nodes(nodes) .nodes(nodes)
.on("tick", tickHandler); .on("tick", handle_tick);
simulation.force("link") simulation.force("link")
.links(links); .links(links);
link.attr('marker-end', function(d) {return updateMarker("#999", d.target);}) link.attr('marker-end', function(d) {return update_marker("#999", d.target);})
.style("stroke-dasharray",function(d){return self_cit(d.source,d.target)? ("8,8"): ("1,0")}); .style("stroke-dasharray",function(d){return self_citation(d.source,d.target)? ("8,8"): ("1,0")});
} }
/** /**
* initializes and shows links * initializes and shows links
* @param {object} links - links * @param {object} links - links
*/ */
function updateLinks(links) { function update_links(links) {
link = g.append("g") link = g.append("g")
.selectAll(".link") .selectAll(".link")
.data(links) .data(links)
...@@ -178,14 +182,14 @@ function updateLinks(links) { ...@@ -178,14 +182,14 @@ function updateLinks(links) {
* creates a click functionality of the circles and texts * creates a click functionality of the circles and texts
* @param {object} nodes - nodes * @param {object} nodes - nodes
*/ */
function updateNodes(nodes) { function update_nodes(nodes) {
node = g.selectAll(".node") node = g.selectAll(".node")
.data(nodes) .data(nodes)
.enter() .enter()
.append("g") .append("g")
.attr("class", "node") .attr("class", "node")
.call(d3.drag() .call(d3.drag()
.on("start", dragstarted) .on("start", start_drag)
.on("drag", dragged) .on("drag", dragged)
); );
...@@ -193,25 +197,25 @@ function updateNodes(nodes) { ...@@ -193,25 +197,25 @@ function updateNodes(nodes) {
.attr("class", "circle") .attr("class", "circle")
.attr("r", function(d) {return 1.5*r+d.citations*0.05}) .attr("r", function(d) {return 1.5*r+d.citations*0.05})
.style("fill", function(d){ return color(d.group)}) .style("fill", function(d){ return color(d.group)})
.on('click', clickNode); .on('click', click_node);
node.append("text") node.append("text")
.attr("class", "text") .attr("class", "text")
.style("font-size", "15px") .style("font-size", "15px")
.style('pointer-events', 'auto') .style('pointer-events', 'auto')
.text(function (d) {const firstauthor=d.author[0].split(" ") .text(function (d) {const first_author=d.author[0].split(" ")
return firstauthor[firstauthor.length-1];}) return first_author[first_author.length-1];})
.on('click', clickNode); .on('click', click_node);
} }
/** /**
* creates arrowhead and returns its url * creates arrowhead and returns its url
* @param {string} color - color of arrowhead * @param {string} color - color of arrowhead
* @param {string} target - target-node * @param {string} target - target node
*/ */
function updateMarker(color, target) { function update_marker(color, target) {
var radius=1.5*r+target.citations*0.05 var radius=1.5*r+target.citations*0.05
svg.append('defs').append('marker')//arrowhead svg.append('defs').append('marker')
.attr('id',color.replace("#", "")+radius) .attr('id',color.replace("#", "")+radius)
.attr('viewBox','-0 -5 10 10') .attr('viewBox','-0 -5 10 10')
.attr('refX',radius+9.5) .attr('refX',radius+9.5)
...@@ -222,52 +226,51 @@ function updateMarker(color, target) { ...@@ -222,52 +226,51 @@ function updateMarker(color, target) {
.attr('xoverflow','visible') .attr('xoverflow','visible')
.append('svg:path') .append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5') .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', color)//arrowhead color .attr('fill', color)
.style('stroke','none'); .style('stroke','none');
return "url(" + color + radius + ")"; return "url(" + color + radius + ")";
}; };
/** /**
* colors the circle and its links black and removes the previous markings * sets color of circle and its links to black and removes the previous highlights
* displays overview info of node in textbox
* @param {object} node - node * @param {object} node - node
*/ */
function clickNode(node) { function click_node(node) {
d3.select(this.parentNode).raise(); d3.select(this.parentNode).raise();
fix_nodes(node); fix_nodes(node);
if(toRemove){ if(to_remove){
d3.select(toRemove).selectAll(".circle").style("stroke","none") d3.select(to_remove).selectAll(".circle").style("stroke","none")
} }
toRemove = this.parentNode; to_remove = this.parentNode;
d3.select(this.parentNode).selectAll(".circle").style("stroke","black") d3.select(this.parentNode).selectAll(".circle").style("stroke","black")
marklink(node) mark_link(node)
textfunc(node) textbox_content(node)
resetbuttonhighlight() reset_button_highlight()
highlightbutton("overview") highlight_button("overview")
} }
/** /**
* removes the markings of the circles and their links * removes the highlights of the circles and their links
*/ */
function clickRect() { function click_rect() {
fix_nodes(node); fix_nodes(node);
d3.selectAll(".circle").style("stroke", "none") d3.selectAll(".circle").style("stroke", "none")
d3.selectAll(".link") d3.selectAll(".link")
.style("stroke", "#999") .style("stroke", "#999")
.attr('marker-end', function(d) {return updateMarker('#999', d.target);}) .attr('marker-end', function(d) {return update_marker('#999', d.target);})
textabstract=''; text_abstract='';
textinfo=''; text_info='';
resetbuttonhighlight() reset_button_highlight()
document.getElementById('textbox').innerHTML = "Click node"; document.getElementById('textbox').innerHTML = "Click node";
} }
function create_author_array(authors){ /**
authorarray = authors.split(",") * returns true if journals have a common author (self-citation)
authorarray = authorarray.map(elem =>{return elem.trim();}) * @param {object} source - node
* @param {object} target - node
return authorarray */
} function self_citation(source,target){
function self_cit(source,target){
return source.author.some(item=>target.author.includes(item)) return source.author.some(item=>target.author.includes(item))
} }
...@@ -276,12 +279,12 @@ function self_cit(source,target){ ...@@ -276,12 +279,12 @@ function self_cit(source,target){
* and to grey otherwise * and to grey otherwise
* @param {object} node - node * @param {object} node - node
*/ */
function marklink(node){ function mark_link(node){
d3.selectAll(".link") d3.selectAll(".link")
.style("stroke", function(o) { .style("stroke", function(o) {
return isLinkForNode(node, o) ? "black" : "#999";}) return is_link_for_node(node, o) ? "black" : "#999";})
.attr('marker-end', function(o) { .attr('marker-end', function(o) {
return isLinkForNode(node, o) ? updateMarker('#000000', o.target) : updateMarker('#999', o.target);}) return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#999', o.target);})
} }
/** /**
...@@ -289,43 +292,55 @@ function marklink(node){ ...@@ -289,43 +292,55 @@ function marklink(node){
* @param {object} node - node * @param {object} node - node
* @param {object} link - link * @param {object} link - link
*/ */
function isLinkForNode(node, link){ function is_link_for_node(node, link){
return link.source.index == node.index || link.target.index == node.index; return link.source.index == node.index || link.target.index == node.index;
} }
/** /**
* saves text for overview and abstract of node
* outputs node info to textbox * outputs node info to textbox
* @param {object} node - data of current node * @param {object} node - node
*/ */
function textfunc(node) { function textbox_content(node) {
textinfo="Title:" + '</br>' + node.name + text_info="Title:" + '</br>' + node.name +
'</br>' +'</br>'+"Author:"+ '</br>' +node.author+'</br>'+'</br>'+"Date:"+'</br>' '</br>' +'</br>'+"Author:"+ '</br>' +node.author+'</br>'+'</br>'+"Date:"+'</br>'
+node.year+'</br>'+'</br>'+"doi:"+'</br>'+'<a href="'+node.doi+ '">'+node.doi +node.year+'</br>'+'</br>'+"Journal:"+'</br>'+node.journal+'</br>'+'</br>'+"doi:"+'</br>'+'<a href="'+node.doi+ '">'+node.doi
+'</a>'+'</br>'+'</br>'+"Citations:"+'</br>'+node.citations; +'</a>'+'</br>'+'</br>'+"Citations:"+'</br>'+node.citations;
textabstract=node.abstract; text_abstract=node.abstract;
document.getElementById('textbox').innerHTML = textinfo; document.getElementById('textbox').innerHTML = text_info;
} }
function highlightbutton(btn) { /**
resetbuttonhighlight(); * sets color of btn to dark gray
* @param {object} btn - button
*/
function highlight_button(btn) {
reset_button_highlight();
document.getElementById(btn).style.background="#CACACA"; document.getElementById(btn).style.background="#CACACA";
} }
function resetbuttonhighlight() { /**
* sets color of all buttons to default light gray
*/
function reset_button_highlight() {
document.getElementById("overview").style.background=''; document.getElementById("overview").style.background='';
document.getElementById("abstract").style.background=''; document.getElementById("abstract").style.background='';
} }
function displayabstract(a){ /**
if (textabstract=='' && textinfo=='') { * displays abstract in textbox if a is true, overview text otherwise
* @param {bool} a- bool
*/
function display_abstract(a){
if (text_abstract=='' && text_info=='') {
document.getElementById('textbox').innerHTML="Click node"; document.getElementById('textbox').innerHTML="Click node";
} }
else { else {
if (a==true) { if (a==true) {
document.getElementById('textbox').innerHTML =textabstract; document.getElementById('textbox').innerHTML =text_abstract;
} }
else { else {
document.getElementById('textbox').innerHTML =textinfo; document.getElementById('textbox').innerHTML =text_info;
} }
} }
} }
...@@ -333,7 +348,7 @@ function displayabstract(a){ ...@@ -333,7 +348,7 @@ function displayabstract(a){
/** /**
* updates the positions of the links and nodes * updates the positions of the links and nodes
*/ */
function tickHandler() { function handle_tick() {
link.attr("x1", function (d) {return d.source.x;}) link.attr("x1", function (d) {return d.source.x;})
.attr("y1", function (d) {return d.source.y;}) .attr("y1", function (d) {return d.source.y;})
.attr("x2", function (d) {return d.target.x;}) .attr("x2", function (d) {return d.target.x;})
...@@ -343,9 +358,9 @@ function tickHandler() { ...@@ -343,9 +358,9 @@ function tickHandler() {
/** /**
* initializes the dragging of the node * initializes the dragging of the node
* @param {object} node - data of current node * @param {object} node - node
*/ */
function dragstarted(node) { function start_drag(node) {
d3.select(this).raise(); d3.select(this).raise();
if (!d3.event.active) if (!d3.event.active)
simulation.alphaTarget(0.3).restart() simulation.alphaTarget(0.3).restart()
...@@ -356,7 +371,7 @@ function dragstarted(node) { ...@@ -356,7 +371,7 @@ function dragstarted(node) {
/** /**
* applies the dragging to the node * applies the dragging to the node
* @param {object} node - data of current node * @param {object} node - node
*/ */
function dragged(node) { function dragged(node) {
node.fx = d3.event.x; node.fx = d3.event.x;
...@@ -364,6 +379,10 @@ function dragged(node) { ...@@ -364,6 +379,10 @@ function dragged(node) {
fix_nodes(node); fix_nodes(node);
} }
/**
* fix positions of all nodes except for the current node
* @param {object} this_node - node
*/
function fix_nodes(this_node) { function fix_nodes(this_node) {
node.each(function(d) { node.each(function(d) {
if (this_node != d) { if (this_node != d) {
...@@ -376,20 +395,15 @@ function fix_nodes(this_node) { ...@@ -376,20 +395,15 @@ function fix_nodes(this_node) {
/** /**
* applies the transformation (zooming or dragging) to the g element * applies the transformation (zooming or dragging) to the g element
*/ */
function zoomHandler() { function handle_zoom() {
d3.select('g').attr("transform", d3.event.transform); d3.select('g').attr("transform", d3.event.transform);
} }
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
/** /**
* transforms svg so that the zoom is adapted to the size of the graph * transforms svg so that the zoom is adapted to the size of the graph
*/ */
function zoomTo() { function zoom_to() {
node_bounds = d3.selectAll("svg.graph").node().getBBox(); node_bounds = d3.selectAll("svg.graph").node().getBBox();
svg_bounds = d3.select("rect").node().getBBox(); svg_bounds = d3.select("rect").node().getBBox();
...@@ -403,9 +417,9 @@ function zoomTo() { ...@@ -403,9 +417,9 @@ function zoomTo() {
/** /**
* transforms svg so that the zoom is reset * transforms svg so that the zoom and drag is reset
*/ */
function resetView() { function reset_view() {
d3.select('svg') d3.select('svg')
.call(zoom.scaleTo, 1) .call(zoom.scaleTo, 1)
d3.select('svg') d3.select('svg')
...@@ -416,28 +430,26 @@ function resetView() { ...@@ -416,28 +430,26 @@ function resetView() {
} }
/** /**
* transforms svg so that it is centered * save svg as png
*/ */
function center() { function save_svg(){
d3.select('svg') var svgString = get_svg_string(svg.node());
.call(zoom.translateTo, 0.5 * width, 0.5 * height); svg_string_to_image( svgString, 2*width, 2*height, 'png', save ); // passes Blob and filesize String to the callback
}
function savesvg(){
var svgString = getSVGString(svg.node());
svgString2Image( svgString, 2*width, 2*height, 'png', save ); // passes Blob and filesize String to the callback
function save( dataBlob, filesize ){ function save( dataBlob, filesize ){
saveAs( dataBlob, 'D3 vis exported to PNG.png' ); // FileSaver.js function saveAs( dataBlob, 'D3 vis exported to PNG.png' ); // FileSaver.js function
} }
}; };
// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback ) /**
function getSVGString( svgNode ) { * generate svgString
* @param {object} svgNode - node
*/
function get_svg_string( svgNode ) {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink'); svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles( svgNode ); var cssStyleText = get_css_styles( svgNode );
appendCSS( cssStyleText, svgNode ); append_css( cssStyleText, svgNode );
var serializer = new XMLSerializer(); var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode); var svgString = serializer.serializeToString(svgNode);
...@@ -446,7 +458,7 @@ function getSVGString( svgNode ) { ...@@ -446,7 +458,7 @@ function getSVGString( svgNode ) {
return svgString; return svgString;
function getCSSStyles( parentElement ) { function get_css_styles( parentElement ) {
var selectorTextArr = []; var selectorTextArr = [];
// Add Parent element Id and Classes to the list // Add Parent element Id and Classes to the list
...@@ -496,7 +508,7 @@ function getSVGString( svgNode ) { ...@@ -496,7 +508,7 @@ function getSVGString( svgNode ) {
} }
function appendCSS( cssText, element ) { function append_css( cssText, element ) {
var styleElement = document.createElement("style"); var styleElement = document.createElement("style");
styleElement.setAttribute("type","text/css"); styleElement.setAttribute("type","text/css");
styleElement.innerHTML = cssText; styleElement.innerHTML = cssText;
...@@ -505,8 +517,15 @@ function getSVGString( svgNode ) { ...@@ -505,8 +517,15 @@ function getSVGString( svgNode ) {
} }
} }
/**
function svgString2Image( svgString, width, height, format, callback ) { * convert svgString to image and export it
* @param {object} svgString - svgString
* @param {object} width - width of image
* @param {object} height - height of image
* @param {object} format - format to save image in
* @param {object} callback - callback function
*/
function svg_string_to_image( svgString, width, height, format, callback ) {
var format = format ? format : 'png'; var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,'+ btoa( unescape( encodeURIComponent( svgString ) ) ); // Convert SVG string to data URL var imgsrc = 'data:image/svg+xml;base64,'+ btoa( unescape( encodeURIComponent( svgString ) ) ); // Convert SVG string to data URL
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<!-- style specifications for button and div elements -->
<style type="text/css"> <style type="text/css">
button { button {
width: 100px; width: 100px;
...@@ -85,20 +85,20 @@ ...@@ -85,20 +85,20 @@
<!-- textbox --> <!-- textbox -->
<div class="textbox" id = "textbox">Click node</div> <div class="textbox" id = "textbox">Click node</div>
<button id="overview" class="overview" onclick='displayabstract(false), highlightbutton("overview")'>Overview</button> <button id="overview" class="overview" onclick='display_abstract(false), highlight_button("overview")'>Overview</button>
<button id="abstract" class="abstract" onclick='displayabstract(true), highlightbutton("abstract")'>Abstract</button> <button id="abstract" class="abstract" onclick='display_abstract(true), highlight_button("abstract")'>Abstract</button>
<!-- buttons --> <!-- buttons -->
<button class="reloadGraph" onclick="location.reload()">Reload Graph</button> <button onclick="location.reload()">Reload Graph</button>
<button class="resetZoom" onclick="resetView()">Reset View</button> <button class="resetZoom" onclick="reset_view()">Reset View</button>
<button class="save" onclick="savesvg()">Save</button> <button class="save" onclick="save_svg()">Save</button>
<!-- link D3 (version 5) --> <!-- D3 (version 5) -->
<script src="https://d3js.org/d3.v5.min.js"></script> <script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.13.0/d3-legend.js"></script> <!-- scripts to save svg element as png -->
<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script> <script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script> <script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<!-- javascript for force-directed graph -->
<script type="text/javascript" id="cn" src="cn.js"></script> <script type="text/javascript" id="cn" src="cn.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment