diff --git a/assets/cn.js b/assets/cn.js index 309678b42520cec11d77cf6a3b4be2925cb8bede..557eb82641271febb93d2c924bc7f4e5aa5665ee 100644 --- a/assets/cn.js +++ b/assets/cn.js @@ -1,4 +1,5 @@ /** + * Zeilen kürzen * creates a new zoom behavior */ var zoom = d3.zoom().on("zoom", handle_zoom); @@ -15,6 +16,7 @@ perc; /** * scale functions that return y coordinate/color of node depending on group +* genauer Erläutern! */ var color = d3.scaleOrdinal() .domain(["height", "input", "depth"]) @@ -48,6 +50,8 @@ var rect = svg.append("rect") /** * creates svg object (legend) and associated attributes +* transform +* mehr kommentare */ var svg_legend = d3.select("svg.legendsvg"), legend_position = [65,95,125], @@ -108,23 +112,24 @@ legend_arrow.append("text") * 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) + tertärer Operator */ 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;} + if (d.group == "Input") {return 0;} else {return 5;} })) .force("collide", d3.forceCollide(function(d) { - if (d.group == "input") {return 100;} - else {return 65;} + if (d.group == "Input") {return 70;} + else {return 70;} }).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;} + if (d.group == "Input") {return 300;} + else {return 200;} }).y(function(d) {return y_scale(d.group)})) - .alpha(0.005) + .alpha(0.004) .on("end", zoom_to); /** @@ -135,11 +140,24 @@ var g = svg.append("g") /** * loads JSON data and calls the update function +Variable */ d3.json("json_text.json").then(function(graph) { update(graph.links, graph.nodes); }) +var intervalId=window.setInterval(function(){ + d3.json("json_text.json").then(function(graph) { + newjson_string=JSON.stringify(graph) + var newjson = CryptoJS.MD5(newjson_string).toString(); + oldjson=localStorage.getItem("oldjson") + if(newjson !== oldjson){ + localStorage.setItem("oldjson", newjson); + window.location.reload() + } + }) +},5000); + /** * calls update functions for links and nodes * adds the nodes, links and tick functionality to the simulation @@ -161,7 +179,7 @@ function update(links, nodes) { } /** -* initializes and shows links +* initializes and shows links (edges) * @param {object} links - links */ function update_links(links) { @@ -250,6 +268,7 @@ function click_node(node) { /** * removes the highlights of the circles and their links +to_remove auch hier benutzen */ function click_rect() { fix_nodes(node); @@ -280,9 +299,9 @@ function self_citation(source,target) { function mark_link(node) { d3.selectAll(".link") .style("stroke", function(o) { - return is_link_for_node(node, o) ? "black" : "#999";}) + return is_link_for_node(node, o) ? "black" : "#DEDEDE";}) .attr('marker-end', function(o) { - return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#999', o.target);}) + return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#DEDEDE', o.target);}) } /** @@ -400,6 +419,7 @@ function handle_zoom() { /** * transforms svg so that the zoom is adapted to the size of the graph +zoom_start initial */ function zoom_to() { node_bounds = d3.selectAll("svg.graph").node().getBBox(); diff --git a/assets/cn2.js b/assets/cn2.js new file mode 100644 index 0000000000000000000000000000000000000000..7d5afe16643d33782f67ddf460971ecbef57651f --- /dev/null +++ b/assets/cn2.js @@ -0,0 +1,606 @@ +/** + * Zeilen kürzen +* 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 +* genauer Erläutern! +*/ +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 +* transform +* mehr kommentare +*/ +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) + tertärer Operator +*/ + +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 70;} + else {return 75;} + }).strength(1)) + .force("charge", d3.forceManyBody().strength(0.001)) + .force("center", d3.forceCenter(width/2, height/2+20)) + .alpha(0.004) + .on("end", zoom_to); + +/** +* creates group element +*/ +var g = svg.append("g") + .attr("class", "everything") + +/** +* creates xAxis element +*/ +var xAxis = d3.axisBottom() + .tickFormat(function(d) {return d;}) + .ticks(10);; + +/** +* draw xAxis +*/ +var gX = svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0,25)") + gX.append("text") + .attr("y", 0) + .attr("x", 80) + .attr("text-anchor", "end") + .attr("stroke", "black") + .text("year"); + +/** +* loads JSON data and calls the update function +Variable +*/ +d3.json("json_text.json").then(function(graph) { + update(graph.links, graph.nodes); +}) + +var intervalId=window.setInterval(function(){ + d3.json("json_text.json").then(function(graph) { + newjson_string=JSON.stringify(graph) + var newjson = CryptoJS.MD5(newjson_string).toString(); + oldjson=localStorage.getItem("oldjson") + if(newjson !== oldjson){ + localStorage.setItem("oldjson", newjson); + window.location.reload() + } + }) +},5000); + +/** +* 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) { + updateXAxis(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 xAxis THISS +* @param {object} nodes - nodes +*/ +function updateXAxis(nodes) { + years = []; + for (i = 0; i < nodes.length; i++) { + years.push(parseInt((nodes[i]["year"]).split(" ")[2])); + } + + xscale = d3.scaleLinear() + .domain([d3.min(years)-1, d3.max(years)+1]) + .range([50, width-50]) + + xAxis.scale(xscale); + gX.call(xAxis); +} + +/** +* initializes and shows links (edges) +* @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 +to_remove auch hier benutzen +*/ +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" : "#DEDEDE";}) + .attr('marker-end', function(o) { + return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker("#DEDEDE", 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 xscale(parseInt((d.source.year).split(" ")[2]));}) + .attr("y1", function (d) {return d.source.y;}) + .attr("x2", function (d) {return xscale(parseInt((d.target.year).split(" ")[2]));}) + .attr("y2", function (d) {return d.target.y;}); + node.attr("transform", function (d) {return "translate(" + xscale(parseInt((d.year).split(" ")[2])) + ", " + 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); + var new_xScale = d3.event.transform.rescaleX(xscale) + gX.call(xAxis.scale(new_xScale)); +} + +/** +* transforms svg so that the zoom is adapted to the size of the graph +zoom_start initial +*/ +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; +} + diff --git a/assets/index.html b/assets/index.html index 78560da48c01f39debc7a810b58a60672c0995dc..374abe283271574e67bd8d73e3691ef35b9e782d 100644 --- a/assets/index.html +++ b/assets/index.html @@ -18,6 +18,12 @@ border:1px solid #909090; } + button.display{ + width: 120px; + top: 0px; + margin-left: 100px; + } + .button:hover { background-color: #CACACA; } @@ -78,14 +84,17 @@ </head> <body> + <button id="change_graph" class="display" onclick="display()">display timeline</button> + <!-- graph --> <svg class="graph" width="960" height="560"></svg> + <p id="oldjson"></p> <!-- legend --> - <div class="legendbox"> <svg class="legendsvg"></svg></div> + <div class="legendbox"><svg class="legendsvg"></svg></div> <!-- textbox --> - <div class="textbox" id = "textbox">Click node</div> + <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> @@ -100,8 +109,64 @@ <!-- 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> + + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/core.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/md5.js"></script> - <!-- javascript for force-directed graph --> - <script type="text/javascript" id="cn" src="cn.js"></script> + <script type="text/javascript" > + if(window.location.hash=='#default'){ + document.getElementById("change_graph").innerHTML = 'display timeline'; + load_default(); + + } + else if(window.location.hash=='#time'){ + document.getElementById("change_graph").innerHTML = 'display default'; + load_time(); + + } + else if(window.location.hash==''){ + window.location.hash = 'default'; + document.getElementById("change_graph").innerHTML = 'display timeline'; + load_default(); + } + function display(){ + if(window.location.hash=='#time'){ + display_default(); + } + else if(window.location.hash=='#default'){ + display_time(); + } + } + function display_default(){ + window.location.hash = 'default'; + window.location.reload(); + } + function display_time(){ + window.location.hash = 'time'; + window.location.reload(); + + } + function load_default(){ + var htmlHeader = document.getElementsByTagName("head")[0]; + var myScript = document.createElement('script'); + myScript.type = 'text/javascript'; + myScript.src = 'cn.js'; + myScript.id='abc'; + htmlHeader.appendChild(myScript); + } + function load_time(){ + var htmlHeader = document.getElementsByTagName("head")[0]; + var myScript = document.createElement('script'); + myScript.type = 'text/javascript'; + myScript.src = 'cn2.js'; + myScript.id='abc'; + htmlHeader.appendChild(myScript); + + } + </script> + + <!-- javascript for force-directed graph + <script type="text/javascript" id="cn" src="cn.js"></script> --> + </body> </html> \ No newline at end of file diff --git a/output/graph/cn.js b/output/graph/cn.js index 309678b42520cec11d77cf6a3b4be2925cb8bede..557eb82641271febb93d2c924bc7f4e5aa5665ee 100644 --- a/output/graph/cn.js +++ b/output/graph/cn.js @@ -1,4 +1,5 @@ /** + * Zeilen kürzen * creates a new zoom behavior */ var zoom = d3.zoom().on("zoom", handle_zoom); @@ -15,6 +16,7 @@ perc; /** * scale functions that return y coordinate/color of node depending on group +* genauer Erläutern! */ var color = d3.scaleOrdinal() .domain(["height", "input", "depth"]) @@ -48,6 +50,8 @@ var rect = svg.append("rect") /** * creates svg object (legend) and associated attributes +* transform +* mehr kommentare */ var svg_legend = d3.select("svg.legendsvg"), legend_position = [65,95,125], @@ -108,23 +112,24 @@ legend_arrow.append("text") * 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) + tertärer Operator */ 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;} + if (d.group == "Input") {return 0;} else {return 5;} })) .force("collide", d3.forceCollide(function(d) { - if (d.group == "input") {return 100;} - else {return 65;} + if (d.group == "Input") {return 70;} + else {return 70;} }).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;} + if (d.group == "Input") {return 300;} + else {return 200;} }).y(function(d) {return y_scale(d.group)})) - .alpha(0.005) + .alpha(0.004) .on("end", zoom_to); /** @@ -135,11 +140,24 @@ var g = svg.append("g") /** * loads JSON data and calls the update function +Variable */ d3.json("json_text.json").then(function(graph) { update(graph.links, graph.nodes); }) +var intervalId=window.setInterval(function(){ + d3.json("json_text.json").then(function(graph) { + newjson_string=JSON.stringify(graph) + var newjson = CryptoJS.MD5(newjson_string).toString(); + oldjson=localStorage.getItem("oldjson") + if(newjson !== oldjson){ + localStorage.setItem("oldjson", newjson); + window.location.reload() + } + }) +},5000); + /** * calls update functions for links and nodes * adds the nodes, links and tick functionality to the simulation @@ -161,7 +179,7 @@ function update(links, nodes) { } /** -* initializes and shows links +* initializes and shows links (edges) * @param {object} links - links */ function update_links(links) { @@ -250,6 +268,7 @@ function click_node(node) { /** * removes the highlights of the circles and their links +to_remove auch hier benutzen */ function click_rect() { fix_nodes(node); @@ -280,9 +299,9 @@ function self_citation(source,target) { function mark_link(node) { d3.selectAll(".link") .style("stroke", function(o) { - return is_link_for_node(node, o) ? "black" : "#999";}) + return is_link_for_node(node, o) ? "black" : "#DEDEDE";}) .attr('marker-end', function(o) { - return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#999', o.target);}) + return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker('#DEDEDE', o.target);}) } /** @@ -400,6 +419,7 @@ function handle_zoom() { /** * transforms svg so that the zoom is adapted to the size of the graph +zoom_start initial */ function zoom_to() { node_bounds = d3.selectAll("svg.graph").node().getBBox(); diff --git a/output/graph/cn2.js b/output/graph/cn2.js new file mode 100644 index 0000000000000000000000000000000000000000..7d5afe16643d33782f67ddf460971ecbef57651f --- /dev/null +++ b/output/graph/cn2.js @@ -0,0 +1,606 @@ +/** + * Zeilen kürzen +* 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 +* genauer Erläutern! +*/ +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 +* transform +* mehr kommentare +*/ +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) + tertärer Operator +*/ + +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 70;} + else {return 75;} + }).strength(1)) + .force("charge", d3.forceManyBody().strength(0.001)) + .force("center", d3.forceCenter(width/2, height/2+20)) + .alpha(0.004) + .on("end", zoom_to); + +/** +* creates group element +*/ +var g = svg.append("g") + .attr("class", "everything") + +/** +* creates xAxis element +*/ +var xAxis = d3.axisBottom() + .tickFormat(function(d) {return d;}) + .ticks(10);; + +/** +* draw xAxis +*/ +var gX = svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0,25)") + gX.append("text") + .attr("y", 0) + .attr("x", 80) + .attr("text-anchor", "end") + .attr("stroke", "black") + .text("year"); + +/** +* loads JSON data and calls the update function +Variable +*/ +d3.json("json_text.json").then(function(graph) { + update(graph.links, graph.nodes); +}) + +var intervalId=window.setInterval(function(){ + d3.json("json_text.json").then(function(graph) { + newjson_string=JSON.stringify(graph) + var newjson = CryptoJS.MD5(newjson_string).toString(); + oldjson=localStorage.getItem("oldjson") + if(newjson !== oldjson){ + localStorage.setItem("oldjson", newjson); + window.location.reload() + } + }) +},5000); + +/** +* 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) { + updateXAxis(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 xAxis THISS +* @param {object} nodes - nodes +*/ +function updateXAxis(nodes) { + years = []; + for (i = 0; i < nodes.length; i++) { + years.push(parseInt((nodes[i]["year"]).split(" ")[2])); + } + + xscale = d3.scaleLinear() + .domain([d3.min(years)-1, d3.max(years)+1]) + .range([50, width-50]) + + xAxis.scale(xscale); + gX.call(xAxis); +} + +/** +* initializes and shows links (edges) +* @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 +to_remove auch hier benutzen +*/ +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" : "#DEDEDE";}) + .attr('marker-end', function(o) { + return is_link_for_node(node, o) ? update_marker('#000000', o.target) : update_marker("#DEDEDE", 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 xscale(parseInt((d.source.year).split(" ")[2]));}) + .attr("y1", function (d) {return d.source.y;}) + .attr("x2", function (d) {return xscale(parseInt((d.target.year).split(" ")[2]));}) + .attr("y2", function (d) {return d.target.y;}); + node.attr("transform", function (d) {return "translate(" + xscale(parseInt((d.year).split(" ")[2])) + ", " + 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); + var new_xScale = d3.event.transform.rescaleX(xscale) + gX.call(xAxis.scale(new_xScale)); +} + +/** +* transforms svg so that the zoom is adapted to the size of the graph +zoom_start initial +*/ +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; +} + diff --git a/output/graph/index.html b/output/graph/index.html index 78560da48c01f39debc7a810b58a60672c0995dc..374abe283271574e67bd8d73e3691ef35b9e782d 100644 --- a/output/graph/index.html +++ b/output/graph/index.html @@ -18,6 +18,12 @@ border:1px solid #909090; } + button.display{ + width: 120px; + top: 0px; + margin-left: 100px; + } + .button:hover { background-color: #CACACA; } @@ -78,14 +84,17 @@ </head> <body> + <button id="change_graph" class="display" onclick="display()">display timeline</button> + <!-- graph --> <svg class="graph" width="960" height="560"></svg> + <p id="oldjson"></p> <!-- legend --> - <div class="legendbox"> <svg class="legendsvg"></svg></div> + <div class="legendbox"><svg class="legendsvg"></svg></div> <!-- textbox --> - <div class="textbox" id = "textbox">Click node</div> + <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> @@ -100,8 +109,64 @@ <!-- 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> + + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/core.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/md5.js"></script> - <!-- javascript for force-directed graph --> - <script type="text/javascript" id="cn" src="cn.js"></script> + <script type="text/javascript" > + if(window.location.hash=='#default'){ + document.getElementById("change_graph").innerHTML = 'display timeline'; + load_default(); + + } + else if(window.location.hash=='#time'){ + document.getElementById("change_graph").innerHTML = 'display default'; + load_time(); + + } + else if(window.location.hash==''){ + window.location.hash = 'default'; + document.getElementById("change_graph").innerHTML = 'display timeline'; + load_default(); + } + function display(){ + if(window.location.hash=='#time'){ + display_default(); + } + else if(window.location.hash=='#default'){ + display_time(); + } + } + function display_default(){ + window.location.hash = 'default'; + window.location.reload(); + } + function display_time(){ + window.location.hash = 'time'; + window.location.reload(); + + } + function load_default(){ + var htmlHeader = document.getElementsByTagName("head")[0]; + var myScript = document.createElement('script'); + myScript.type = 'text/javascript'; + myScript.src = 'cn.js'; + myScript.id='abc'; + htmlHeader.appendChild(myScript); + } + function load_time(){ + var htmlHeader = document.getElementsByTagName("head")[0]; + var myScript = document.createElement('script'); + myScript.type = 'text/javascript'; + myScript.src = 'cn2.js'; + myScript.id='abc'; + htmlHeader.appendChild(myScript); + + } + </script> + + <!-- javascript for force-directed graph + <script type="text/javascript" id="cn" src="cn.js"></script> --> + </body> </html> \ No newline at end of file