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
2 merge requests!10Output,!9Main
/** /**
* 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