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

Merge branch 'main' of...

parents 2e5dfde9 6dea7f3c
Branches
No related tags found
No related merge requests found
# Projekt CiS-Projekt 2021/22
Citation Network
## Usage
Input: Json-Datei ***json\_text.json*** in directory
```json
{
"nodes": [
{
"name": <title: string>,
"author": [<author1: string>, <author2: string>, ...],
"year": <date: tring>,
"journal": <journal: string>,
"doi": <doi: string>,
"group": <"input"/"height"/"depth">,
"citations": <citation: int>
}, ...
],
"links": [
{
"source": <doi: string>,
"target": <doi: string>
}, ...
]
}
```
Starting a Python-HTTP Server:
```sh
cd <path to file> &&python3 -m http.server <port>
```
Opening Server:
[http://0.0.0.0/:\<port\>](http://0.0.0.0/:<port>)
## Files in directory
- ***index.html***: webpage
- ***cn.js***: javascript code for force-directed graph, text elements and legend
- ***json_text.json***: example data
## Authors
- Katja Ehlers
- Merle Stahl
\ No newline at end of file
/**
* 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");
var text_info='';
var text_abstract='';
var perc;
/**
* creates node object and radius
*/
var node,
r=10,
/**
* scale functions that return y coordinate/color of node depending on group
*/
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 link object
*/
var link;
/**
* creates a background
* creates a click functionality of the background
*/
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")]);
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 functionailty 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 and texts
* creates a new drag behavior and applies it to the circles
* creates a click functionality of the circles and texts
* @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;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- style specifications for button and div elements -->
<style type="text/css">
button {
width: 100px;
height:20px;
display: flex;
justify-content: center;
position: absolute;
left: 455px;
top: 575px;
transition-duration: 0.4s;
border-radius:3px;
border:1px solid #909090;
}
.button:hover {
background-color: #CACACA;
}
button.resetZoom {
margin-left: 110px;
}
button.save {
margin-left: 220px;
}
button.abstract {
width:146px;
position:absolute;
top: 181px;
left: 1114px;
border-radius:0;
border:1px solid #909090;
}
button.overview {
width:147px;
position:absolute;
display:inline-block;
top: 181px;
left: 968px;
border-radius:0;
border:1px solid #909090;
}
div.legendbox {
width:270px;
height:170px;
padding: 10px;
/*border: 1px solid #999;*/
position: absolute;
top: 10px;
left: 968px;
display: inline-block;
margin: 0;
}
div.textbox {
width:270px;
min-height:200px;
max-height:370px;
padding: 10px;
border: 1px solid #999;
position: absolute;
top: 200px;
left: 968px;
display: inline-block;
overflow-y: scroll;
margin: 0;
}
</style>
</head>
<body>
<!-- graph -->
<svg class="graph" width="960" height="560"></svg>
<!-- legend -->
<div class="legendbox"> <svg class="legendsvg"></svg></div>
<!-- textbox -->
<div class="textbox" id = "textbox">Click node</div>
<button id="overview" class="overview" onclick='display_abstract(false), highlight_button("overview")'>Overview</button>
<button id="abstract" class="abstract" onclick='display_abstract(true), highlight_button("abstract")'>Abstract</button>
<!-- buttons -->
<button onclick="location.reload()">Reload Graph</button>
<button class="resetZoom" onclick="reset_view()">Reset View</button>
<button class="save" onclick="save_svg()">Save</button>
<!-- D3 (version 5) -->
<script src="https://d3js.org/d3.v5.min.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/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<!-- javascript for force-directed graph -->
<script type="text/javascript" id="cn" src="cn.js"></script>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment