Skip to content
Snippets Groups Projects

Main

Closed Stahl, Merle requested to merge bax9187/projekt-cis-biochemie-2021-22:main into main
5 files
+ 1520
3
Compare changes
  • Side-by-side
  • Inline
Files
5
+ 548
0
 
/**
 
* creates a new zoom behavior
 
*/
 
var zoom = d3.zoom().on("zoom", handle_zoom);
 
 
/**
 
* creates svg object and associated attributes
 
* applies the zoom behavior to svg
 
*/
 
var svg = d3.select("svg.graph")
 
.call(zoom),
 
width = svg.attr("width"),
 
height = svg.attr("height"),
 
perc;
 
 
/**
 
* scale functions that return y coordinate/color of node depending on group
 
*/
 
var color = d3.scaleOrdinal()
 
.domain(["height", "input", "depth"])
 
.range([' #01d7c0', ' #8b90fe ', ' #a15eb2 ']),
 
y_scale = d3.scaleOrdinal()
 
.domain(["height", "input", "depth"])
 
.range([0, 200, 400]),
 
to_remove;
 
 
/**
 
* creates node object and (default) radius
 
*/
 
var node,
 
r = 10;
 
 
/**
 
* creates link object
 
*/
 
var link;
 
 
/**
 
* creates a background with a click functionality
 
*/
 
var rect = svg.append("rect")
 
.attr("x", 0)
 
.attr("y", 0)
 
.attr("height", height)
 
.attr("width", width)
 
.style("fill", 'white')
 
.on('click', click_rect);
 
 
/**
 
* creates svg object (legend) and associated attributes
 
*/
 
var svg_legend = d3.select("svg.legendsvg"),
 
legend_position = [65,95,125],
 
arrow_legend_position = [0,25],
 
arrow_group_names = ["citation","self-citation"],
 
group_names = ["cited by","input","reference"],
 
line_type = d3.scaleOrdinal()
 
.domain(["line","dotted"])
 
.range([("8,0"),("8,8")]),
 
text_info = '',
 
text_abstract = '';
 
 
var legend = svg_legend.selectAll(".legend")
 
.data(legend_position)
 
.enter()
 
.append("g")
 
.attr("class","legend")
 
.attr("transform", function(d,i) {return "translate(0," + d + ")"; });
 
 
legend.append("text")
 
.attr("x", 80)
 
.attr("y", 0)
 
.attr("dy", ".35em")
 
.style("text-anchor", "start")
 
.text(function(d,i) {return group_names[i]});
 
 
legend.append("circle")
 
.attr("r", r)
 
.attr("cx",30-r)
 
.style("fill", color);
 
 
var legend_arrow = svg_legend.selectAll(".legendarr")
 
.data(arrow_legend_position)
 
.enter()
 
.append("g")
 
.attr("class","legendarr")
 
.attr("transform", function(d) { return "translate(0," + d + ")"; });
 
 
legend_arrow.append("line")
 
.attr("x1", 10)
 
.attr("x2", 50)
 
.attr("y1", 10)
 
.attr("y2", 10)
 
.style("stroke-dasharray",line_type)
 
.style("stroke", '#999')
 
.style("stroke-width", "1px")
 
.style('pointer-events', 'none')
 
.attr('marker-end',update_marker('#999',this));
 
 
legend_arrow.append("text")
 
.attr("x", 80)
 
.attr("y", 10)
 
.attr("dy", ".35em")
 
.style("text-anchor", "start")
 
.text(function(d,i){return arrow_group_names[i]});
 
 
/**
 
* creates a new simulation
 
* updates the positions of the links and nodes when the
 
state of the layout has changed (simulation has advanced by a tick)
 
*/
 
var simulation = d3.forceSimulation()
 
.force("link", d3.forceLink().id(function(d) {return d.doi;}).distance(50).strength(function(d) {
 
if (d.group == "input") {return 0;}
 
else {return 5;}
 
}))
 
.force("collide", d3.forceCollide(function(d) {
 
if (d.group == "input") {return 100;}
 
else {return 65;}
 
}).strength(0.5))
 
.force("charge", d3.forceManyBody().strength(0.001))
 
.force("center", d3.forceCenter(width/2, height/2+20))
 
.force("yscale", d3.forceY().strength(function(d) {
 
if (d.group == "input") {return 1000;}
 
else {return 50;}
 
}).y(function(d) {return y_scale(d.group)}))
 
.alpha(0.005)
 
.on("end", zoom_to);
 
 
/**
 
* creates group element
 
*/
 
var g = svg.append("g")
 
.attr("class", "everything")
 
 
/**
 
* loads JSON data and calls the update function
 
*/
 
d3.json("json_text.json").then(function(graph) {
 
update(graph.links, graph.nodes);
 
})
 
 
/**
 
* calls update functions for links and nodes
 
* adds the nodes, links and tick functionality to the simulation
 
* @param {object} nodes - nodes
 
* @param {object} links - links
 
*/
 
function update(links, nodes) {
 
update_links(links);
 
update_nodes(nodes);
 
 
simulation
 
.nodes(nodes)
 
.on("tick", handle_tick);
 
simulation.force("link")
 
.links(links);
 
 
link.attr('marker-end', function(d) {return update_marker("#999", d.target);})
 
.style("stroke-dasharray",function(d){return self_citation(d.source,d.target)? ("8,8"): ("1,0")});
 
}
 
 
/**
 
* initializes and shows links
 
* @param {object} links - links
 
*/
 
function update_links(links) {
 
link = g.append("g")
 
.selectAll(".link")
 
.data(links)
 
.enter()
 
.append("line")
 
.style("stroke-width", "1px")
 
.style("stroke", "#999")
 
.attr("class", "link");
 
}
 
 
/**
 
* initializes and shows nodes with circles, texts and a click functionality
 
* creates a new drag behavior and applies it to the circles
 
* @param {object} nodes - nodes
 
*/
 
function update_nodes(nodes) {
 
node = g.selectAll(".node")
 
.data(nodes)
 
.enter()
 
.append("g")
 
.attr("class", "node")
 
.call(d3.drag()
 
.on("start", start_drag)
 
.on("drag", dragged)
 
);
 
 
node.append("circle")
 
.attr("class", "circle")
 
.attr("r", function(d) {return 1.5*r+d.citations*0.05})
 
.style("fill", function(d){ return color(d.group)})
 
.on('click', click_node);
 
 
node.append("text")
 
.attr("class", "text")
 
.style("font-size", "15px")
 
.style('pointer-events', 'auto')
 
.text(function (d) {const first_author=d.author[0].split(" ")
 
return first_author[first_author.length-1];})
 
.on('click', click_node);
 
}
 
 
/**
 
* creates arrowhead and returns its url
 
* @param {string} color - color of arrowhead
 
* @param {string} target - target node
 
*/
 
function update_marker(color, target) {
 
var radius = 1.5*r+target.citations*0.05;
 
svg.append('defs').append('marker')
 
.attr('id',color.replace("#", "")+radius)
 
.attr('viewBox','-0 -5 10 10')
 
.attr('refX',radius+9.5)
 
.attr('refY',0)
 
.attr('orient','auto')
 
.attr('markerWidth',10)
 
.attr('markerHeight',15)
 
.attr('xoverflow','visible')
 
.append('svg:path')
 
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
 
.attr('fill', color)
 
.style('stroke','none');
 
return "url(" + color + radius + ")";
 
};
 
 
/**
 
* 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
 
*/
 
function click_node(node) {
 
d3.select(this.parentNode).raise();
 
fix_nodes(node);
 
if(to_remove){
 
d3.select(to_remove).selectAll(".circle").style("stroke","none")
 
}
 
to_remove = this.parentNode;
 
d3.select(this.parentNode).selectAll(".circle").style("stroke","black")
 
mark_link(node)
 
textbox_content(node)
 
reset_button_highlight()
 
highlight_button("overview")
 
}
 
 
/**
 
* removes the highlights of the circles and their links
 
*/
 
function click_rect() {
 
fix_nodes(node);
 
d3.selectAll(".circle").style("stroke", "none")
 
d3.selectAll(".link")
 
.style("stroke", "#999")
 
.attr('marker-end', function(d) {return update_marker('#999', d.target);})
 
text_abstract='';
 
text_info='';
 
reset_button_highlight()
 
document.getElementById('textbox').innerHTML = "Click node";
 
}
 
 
/**
 
* returns true if journals have a common author (self-citation)
 
* @param {object} source - node
 
* @param {object} target - node
 
*/
 
function self_citation(source,target) {
 
return source.author.some(item=>target.author.includes(item))
 
}
 
 
/**
 
* sets color of link (line and arrowhead) to black if it is directly connected to node
 
* and to grey otherwise
 
* @param {object} node - node
 
*/
 
function mark_link(node) {
 
d3.selectAll(".link")
 
.style("stroke", function(o) {
 
return is_link_for_node(node, o) ? "black" : "#999";})
 
.attr('marker-end', function(o) {
 
return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#999', o.target);})
 
}
 
 
/**
 
* returns true if link is directly connected to node and false if it is not
 
* @param {object} node - node
 
* @param {object} link - link
 
*/
 
function is_link_for_node(node, link) {
 
return link.source.index == node.index || link.target.index == node.index;
 
}
 
 
/**
 
* saves text for overview and abstract of node
 
* outputs node info to textbox
 
* @param {object} node - node
 
*/
 
function textbox_content(node) {
 
text_info = "Title:" + '</br>' + node.name +
 
'</br>' +'</br>'+"Author:"+ '</br>' +node.author+'</br>'+'</br>'+"Date:"+'</br>'
 
+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;
 
text_abstract = node.abstract;
 
document.getElementById('textbox').innerHTML = text_info;
 
}
 
 
/**
 
* sets color of btn to dark gray
 
* @param {object} btn - button
 
*/
 
function highlight_button(btn) {
 
reset_button_highlight();
 
document.getElementById(btn).style.background = "#CACACA";
 
}
 
 
/**
 
* sets color of all buttons to default light gray
 
*/
 
function reset_button_highlight() {
 
document.getElementById("overview").style.background = '';
 
document.getElementById("abstract").style.background = '';
 
}
 
 
/**
 
* 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";
 
}
 
else {
 
if (a == true) {
 
document.getElementById('textbox').innerHTML = text_abstract;
 
}
 
else {
 
document.getElementById('textbox').innerHTML = text_info;
 
}
 
}
 
}
 
 
/**
 
* updates the positions of the links and nodes
 
*/
 
function handle_tick() {
 
link.attr("x1", function (d) {return d.source.x;})
 
.attr("y1", function (d) {return d.source.y;})
 
.attr("x2", function (d) {return d.target.x;})
 
.attr("y2", function (d) {return d.target.y;});
 
node.attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
 
}
 
 
/**
 
* initializes the dragging of the node
 
* @param {object} node - node
 
*/
 
function start_drag(node) {
 
d3.select(this).raise();
 
if (!d3.event.active)
 
simulation.alphaTarget(0.3).restart()
 
node.fx = node.x;
 
node.fy = node.y;
 
fix_nodes(node);
 
}
 
 
/**
 
* applies the dragging to the node
 
* @param {object} node - node
 
*/
 
function dragged(node) {
 
node.fx = d3.event.x;
 
node.fy = d3.event.y;
 
fix_nodes(node);
 
}
 
 
/**
 
* fix positions of all nodes except for the current node
 
* @param {object} this_node - node
 
*/
 
function fix_nodes(this_node) {
 
node.each(function(d) {
 
if (this_node != d) {
 
d.fx = d.x;
 
d.fy = d.y;
 
}
 
});
 
}
 
 
/**
 
* applies the transformation (zooming or dragging) to the g element
 
*/
 
function handle_zoom() {
 
d3.select('g').attr("transform", d3.event.transform);
 
}
 
 
/**
 
* transforms svg so that the zoom is adapted to the size of the graph
 
*/
 
function zoom_to() {
 
node_bounds = d3.selectAll("svg.graph").node().getBBox();
 
svg_bounds = d3.select("rect").node().getBBox();
 
 
perc_x = width/(node_bounds.width+100);
 
perc_y = height/(node_bounds.height+100);
 
perc = d3.min([perc_x, perc_y])
 
 
d3.select('svg')
 
.call(zoom.scaleBy, perc);
 
}
 
 
/**
 
* transforms svg so that the zoom and drag is reset
 
*/
 
function reset_view() {
 
d3.select('svg')
 
.call(zoom.scaleTo, 1)
 
d3.select('svg')
 
.call(zoom.translateTo, 0.5 * width, 0.5 * height);
 
d3.select('svg')
 
.call(zoom.scaleBy, perc);
 
}
 
 
/**
 
* save svg as png
 
*/
 
function save_svg(){
 
var svgString = get_svg_string(svg.node());
 
svg_string_to_image(svgString, 2*width, 2*height, 'png', save); // passes Blob and filesize String to the callback
 
 
function save( dataBlob, filesize ){
 
saveAs(dataBlob, 'D3 vis exported to PNG.png'); // FileSaver.js function
 
}
 
};
 
 
/**
 
* generate svgString
 
* @param {object} svgNode - node
 
*/
 
function get_svg_string(svgNode) {
 
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
 
var cssStyleText = get_css_styles(svgNode);
 
append_css(cssStyleText, svgNode);
 
 
var serializer = new XMLSerializer();
 
var svgString = serializer.serializeToString(svgNode);
 
svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
 
svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
 
 
return svgString;
 
 
function get_css_styles(parentElement) {
 
var selectorTextArr = [];
 
 
// Add Parent element Id and Classes to the list
 
selectorTextArr.push('#' + parentElement.id);
 
for (var c = 0; c < parentElement.classList.length; c++)
 
if (!contains('.'+parentElement.classList[c], selectorTextArr))
 
selectorTextArr.push('.'+parentElement.classList[c]);
 
 
// Add Children element Ids and Classes to the list
 
var nodes = parentElement.getElementsByTagName("*");
 
for (var i = 0; i < nodes.length; i++) {
 
var id = nodes[i].id;
 
if (!contains('#'+id, selectorTextArr))
 
selectorTextArr.push('#' + id);
 
 
var classes = nodes[i].classList;
 
for (var c = 0; c < classes.length; c++)
 
if (!contains('.'+classes[c], selectorTextArr))
 
selectorTextArr.push('.'+classes[c]);
 
}
 
 
// Extract CSS Rules
 
var extractedCSSText = "";
 
for (var i = 0; i < document.styleSheets.length; i++) {
 
var s = document.styleSheets[i];
 
 
try {
 
if(!s.cssRules) continue;
 
} catch(e) {
 
if(e.name !== 'SecurityError') throw e; // for Firefox
 
continue;
 
}
 
 
var cssRules = s.cssRules;
 
for (var r = 0; r < cssRules.length; r++) {
 
if (contains(cssRules[r].selectorText, selectorTextArr))
 
extractedCSSText += cssRules[r].cssText;
 
}
 
}
 
 
 
return extractedCSSText;
 
 
function contains(str,arr) {
 
return arr.indexOf(str) === -1 ? false : true;
 
}
 
 
}
 
 
function append_css(cssText, element) {
 
var styleElement = document.createElement("style");
 
styleElement.setAttribute("type","text/css");
 
styleElement.innerHTML = cssText;
 
var refNode = element.hasChildNodes() ? element.children[0] : null;
 
element.insertBefore(styleElement, refNode);
 
}
 
}
 
 
/**
 
* 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 imgsrc = 'data:image/svg+xml;base64,'+ btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL
 
 
var canvas = document.createElement("canvas");
 
var context = canvas.getContext("2d");
 
 
canvas.width = width;
 
canvas.height = height;
 
 
var image = new Image();
 
image.onload = function() {
 
context.clearRect(0, 0, width, height);
 
context.drawImage(image, 0, 0, width, height);
 
 
canvas.toBlob(function(blob) {
 
var filesize = Math.round(blob.length/1024) + ' KB';
 
if (callback) callback(blob, filesize);
 
});
 
 
};
 
 
image.src = imgsrc;
 
}
 
Loading