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 }