1 /** 2 * creates a new zoom behavior 3 */ 4 var zoom = d3.zoom().on("zoom", zoomHandler); 5 6 /** 7 * creates svg object and associated attributes 8 * applies the zoom behavior to svg 9 */ 10 var svg = d3.select("svg") 11 .call(zoom), 12 width = svg.attr("width"), 13 height = svg.attr("height"); 14 15 /** 16 * creates node object and associated attributes 17 */ 18 var node, 19 r=12, 20 color = d3.scaleOrdinal() 21 .domain(["citing", "input", "cited"]) 22 .range([' #01d7c0', ' #8b90fe ', ' #a15eb2 ']), 23 yscale = d3.scaleOrdinal() 24 .domain(["citing", "input", "cited"]) 25 .range([0, 200, 400]), 26 toRemove; 27 28 /** 29 * creates link object 30 */ 31 var link; 32 33 /** 34 * creates a background 35 * creates a click functionality of the background 36 */ 37 var rect = svg.append("rect") 38 .attr("x", 0) 39 .attr("y", 0) 40 .attr("height", height) 41 .attr("width", width) 42 .style("fill", 'white') 43 .on('click', clickRect); 44 45 /** 46 * creates a new simulation 47 * updates the positions of the links and nodes when the 48 state of the layout has changed (simulation has advanced by a tick) 49 */ 50 var simulation = d3.forceSimulation() 51 .force("link", d3.forceLink().id(function(d) {return d.doi;}).distance(100).strength(1)) 52 .force("collide", d3.forceCollide(50)) 53 .force("charge", d3.forceManyBody().strength(-30)) 54 .force("center", d3.forceCenter(width/2, height/2)) 55 .force("yscale", d3.forceY().strength(1).y(function(d) {return yscale(d.group)})) 56 .on("tick", tickHandler); 57 58 /** 59 * creates group element 60 */ 61 var g = svg.append("g") 62 .attr("class", "everything") 63 64 /** 65 * loads JSON data and calls the update function 66 */ 67 d3.json("json_text.json").then(function(graph) { 68 update(graph.links, graph.nodes); 69 }) 70 71 /** 72 * calls update functions for links and nodes 73 * adds the nodes and links to the simulation 74 * @param {object} nodes - nodes 75 * @param {object} links - links 76 */ 77 function update(links, nodes) { 78 updateLinks(links); 79 updateNodes(nodes); 80 81 simulation 82 .nodes(nodes); 83 simulation.force("link") 84 .links(links); 85 } 86 87 /** 88 * initializes and shows links 89 * @param {object} links - links 90 */ 91 function updateLinks(links) { 92 link = g.append("g") 93 .selectAll(".link") 94 .data(links) 95 .enter() 96 .append("line") 97 .style("stroke-width", "1px") 98 .style("stroke", "#999") 99 .attr('marker-end',marker("#999")) 100 .attr("class", "link"); 101 } 102 103 /** 104 * initializes and shows nodes with circles and texts 105 * creates a new drag behavior and applies it to the circles 106 * creates a click functionality of the circles and texts 107 * @param {object} nodes - nodes 108 */ 109 function updateNodes(nodes) { 110 node = g.selectAll(".node") 111 .data(nodes) 112 .enter() 113 .append("g") 114 .attr("class", "node") 115 .attr("initial_x", function(d) {return d.dx;}) 116 .attr("initial_y", function(d) {return d.dy;}) 117 .call(d3.drag() 118 .on("start", dragstarted) 119 .on("drag", dragged) 120 ); 121 122 node.append("circle") 123 .attr("class", "circle") 124 .attr("r", r) 125 .style("fill", function(d){ return color(d.group)}) 126 .on('click', clickNode); 127 128 node.append("text") 129 .attr("class", "text") 130 .style("font-size", "15px") 131 .style('pointer-events', 'auto') 132 .text(function (d) {return firstauthor(d.author);}) 133 .on('click', clickNode); 134 } 135 136 /** 137 * colors the circle and its links black and removes the previous markings 138 * @param {object} node - node 139 */ 140 function clickNode(node) { 141 if(toRemove){ 142 d3.select(toRemove).selectAll(".circle").style("stroke","none") 143 } 144 toRemove = this.parentNode; 145 d3.select(this.parentNode).selectAll(".circle").style("stroke","black") 146 marklink(node) 147 textfunc(node) 148 } 149 150 /** 151 * removes the markings of the circles and their links 152 */ 153 function clickRect() { 154 d3.selectAll(".circle").style("stroke", "none") 155 d3.selectAll(".link") 156 .style("stroke", "#999") 157 .attr('marker-end',marker('#999')) 158 document.getElementById('textbox').innerHTML = "Click node"; 159 } 160 161 /** 162 * sets color of link (line and arrowhead) to black if it is directly connected to node 163 * and to grey otherwise 164 * @param {object} node - node 165 */ 166 function marklink(node){ 167 d3.selectAll(".link") 168 .style("stroke", function(o) { 169 return isLinkForNode(node, o) ? "black" : "#999";}) 170 .attr('marker-end', function(o) { 171 return isLinkForNode(node, o) ? marker('#000000') : marker('#999');}) 172 } 173 174 /** 175 * returns true if link is directly connected to node and false if it is not 176 * @param {object} node - node 177 * @param {object} link - link 178 */ 179 function isLinkForNode(node, link){ 180 return link.source.index == node.index || link.target.index == node.index; 181 } 182 183 /** 184 * creates arrowhead and returns its url 185 * @param {string} color - color of arrowhead 186 */ 187 function marker(color) { 188 svg.append('defs').append('marker')//arrowhead 189 .attr('id',color.replace("#", "")) 190 .attr('viewBox','-0 -5 10 10') 191 .attr('refX',r+10) 192 .attr('refY',0) 193 .attr('orient','auto') 194 .attr('markerWidth',10) 195 .attr('markerHeight',15) 196 .attr('xoverflow','visible') 197 .append('svg:path') 198 .attr('d', 'M 0,-5 L 10 ,0 L 0,5') 199 .attr('fill', color)//arrowhead color 200 .style('stroke','none'); 201 return "url(" + color + ")"; 202 }; 203 204 /** 205 * returns last name of first author 206 * @param {string} authors - the comma-separated string of authors 207 */ 208 function firstauthor(authors){ 209 if (/,/.test(authors)==false){ 210 var firstauthor=/^.*\s+([\w\-]+)[\.\s]*$/.exec(authors) 211 } 212 else { 213 var firstauthor=/^[\s\w\.\-]*\s([\w\-]+)[\.\s]*,.*$/.exec(authors) 214 } 215 return firstauthor[1] 216 } 217 218 /** 219 * outputs node info to textbox 220 * @param {object} node - data of current node 221 */ 222 function textfunc(node){ 223 document.getElementById('textbox').innerHTML = "Title:" + '</br>' + node.name + 224 '</br>' +'</br>'+"Author:"+ '</br>' +node.author+'</br>'+'</br>'+"Year:"+'</br>' 225 +node.year+'</br>'+'</br>'+"doi:"+'</br>'+node.doi; 226 } 227 228 /** 229 * updates the positions of the links and nodes 230 */ 231 function tickHandler() { 232 link.attr("x1", function (d) {return d.source.x;}) 233 .attr("y1", function (d) {return d.source.y;}) 234 .attr("x2", function (d) {return d.target.x;}) 235 .attr("y2", function (d) {return d.target.y;}); 236 node.attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";}); 237 } 238 239 /** 240 * initializes the dragging of the node 241 * @param {object} node - data of current node 242 */ 243 function dragstarted(node) { 244 if (!d3.event.active) 245 simulation.alphaTarget(0.3).restart() 246 node.fx = node.x; 247 node.fy = node.y; 248 } 249 250 /** 251 * applies the dragging to the node 252 * @param {object} node - data of current node 253 */ 254 function dragged(node) { 255 node.fx = d3.event.x; 256 node.fy = d3.event.y; 257 } 258 259 /** 260 * resets the positions of the nodes 261 */ 262 function resetGraph() { 263 d3.selectAll(".node").each(function(d) { 264 d.fx = d.initial_x; 265 d.fy = d.initial_y; 266 }) 267 } 268 269 /** 270 * applies the transformation (zooming or dragging) to the g element 271 */ 272 function zoomHandler() { 273 d3.select('g').attr("transform", d3.event.transform); 274 } 275 276 /** 277 * transforms svg so that that the zoom is reset 278 */ 279 function resetZoom() { 280 d3.select('svg') 281 .call(zoom.scaleTo, 1); 282 } 283 284 /** 285 * transforms svg so that it is centered 286 */ 287 function center() { 288 d3.select('svg') 289 .call(zoom.translateTo, 0.5 * width, 0.5 * height); 290 }