| //new data | |
| var dataStored = []; | |
| var nodeIDList = []; | |
| var linkIDList = []; | |
| var sitePrefix = 'https://beta.eol.org';//"https://beta.eol.org"; | |
| var pageId; | |
| //graph | |
| var graph, | |
| node, | |
| link, | |
| new_node, | |
| existing_node, | |
| existing_link, | |
| new_link; | |
| //for animation purpose | |
| var source_nodes=[], | |
| existing_nodes=[], | |
| new_nodes=[], | |
| hiding_nodes=[], | |
| existing_links = [], | |
| new_links = [], | |
| transition = false; | |
| //node positions | |
| var curSource, | |
| predPos = [], | |
| preyPos = [], | |
| compPos = [], | |
| sourcePos= []; | |
| var compList = []; | |
| //Node number limit | |
| var nLimit = 7; | |
| //network graph window #networkSvg | |
| var width= 1000, | |
| height= 800, | |
| radius = 6, | |
| source_radius = 30; | |
| //node colors | |
| var color = d3.scaleOrdinal(d3.schemeSet3); | |
| color(1); | |
| color(2); | |
| color(3); | |
| color(4); | |
| color(5); | |
| //svg selection and sizing | |
| var s = d3.select("#networkSvg") | |
| .attr("width", width) | |
| .attr("height", height); | |
| var svg = s.append("g") | |
| .attr("width", width) | |
| .attr("height", height); | |
| var tooltip = d3.select("#tooltipDiv"); | |
| var tooltipSvg = d3.select("#tooltipSvg"); | |
| var zoom = d3.zoom().scaleExtent([0, 3]) | |
| .on("zoom", function() { | |
| svg.attr("transform", d3.event.transform);}); | |
| s.call(zoom); | |
| d3.select("#reset").on("click", function() { | |
| s.transition().duration(100).call(zoom.transform, d3.zoomIdentity); | |
| toggleVisibilityOfNodesAndLinks(graph, graph.nodes[0]); | |
| updateGraph(); | |
| }); | |
| d3.select("#zoom_in").on("click", function() { | |
| zoom.scaleBy(s.transition().duration(100), 1.1); | |
| }); | |
| d3.select("#zoom_out").on("click", function() { | |
| zoom.scaleBy(s.transition().duration(100), 0.9); | |
| }); | |
| //legend label HTML | |
| var sequentialScale = tooltipSvg.append("g") | |
| .attr("class", "legendarray") | |
| .attr("transform", "translate(0,80)") | |
| .append("g") | |
| .attr("class", "legendCells") | |
| .attr("transform", "translate(0, 12.015625)"); | |
| var predLegend = sequentialScale.append("g") | |
| .attr("class", "cell") | |
| .attr("transform", "translate(0,0)"); | |
| predLegend | |
| .append("rect").attr("class", "watch") | |
| .attr("height", 15).attr("width", 30) | |
| .attr("style", "fill: rgb(141, 211, 199);"); | |
| predLegend | |
| .append("text") | |
| .attr("class", "label") | |
| .attr("transform", "translate(40, 12.5)") | |
| .text("Predator"); | |
| var preyLegend = sequentialScale.append("g") | |
| .attr("class", "cell") | |
| .attr("transform", "translate(0,20)"); | |
| preyLegend | |
| .append("rect") | |
| .attr("class", "watch") | |
| .attr("height", 15) | |
| .attr("width", 30) | |
| .attr("style", "fill: rgb(255, 255, 179);"); | |
| preyLegend | |
| .append("text") | |
| .attr("class", "label") | |
| .attr("transform", "translate(40, 12.5)") | |
| .text("Prey"); | |
| var compLegend = sequentialScale.append("g") | |
| .attr("class", "cell").attr("transform", "translate(0,40)"); | |
| compLegend | |
| .append("rect") | |
| .attr("class", "watch") | |
| .attr("height", 15).attr("width", 30) | |
| .attr("style", "fill: rgb(128, 177, 211);"); | |
| compLegend | |
| .append("text") | |
| .attr("class", "label") | |
| .attr("transform", "translate(40, 12.5)") | |
| .text("Competitor"); | |
| var pattern = svg.selectAll('.pattern'); | |
| var marker = svg.selectAll('.marker') | |
| .data(["arrow", "longer"]) | |
| .enter().append('marker') | |
| .attr("id", function(d) {return d;}) | |
| .attr("viewBox", "0 -5 10 10") | |
| .attr("refX", function(d) { | |
| if(d == "arrow") { | |
| return 20; | |
| } else { | |
| return 60; | |
| } | |
| }) | |
| .attr("refY", 0) | |
| .attr("markerWidth", 6) | |
| .attr("markerHeight", 6) | |
| .attr("orient", "auto") | |
| .attr("fill", "#9b9b9b") | |
| .append("path") | |
| .attr("d", "M0,-5L10,0L0,5") | |
| .style("stroke", "#9b9b9b"); | |
| node = svg.selectAll('.node'); | |
| new_node = svg.selectAll('.new_node'); | |
| existing_node =svg.selectAll('.existing_node'); | |
| new_link = svg.selectAll('.new_link'); | |
| existing_link =svg.selectAll('.existing_link'); | |
| link = svg.selectAll('.line'); | |
| marker = svg.selectAll('marker'); | |
| // force simulation initialization | |
| var simulation = d3.forceSimulation() | |
| .force("link", d3.forceLink() | |
| .id(function(d) { return d.id; })) | |
| .force("charge", d3.forceManyBody() | |
| .strength(function(d) { return -500;})) | |
| .force("center", d3.forceCenter(width / 2, height / 2)); | |
| //eol page id | |
| var eol_id = "328447"; | |
| //initialize first graph | |
| initializeGraph(eol_id); | |
| zoom.scaleBy(s.transition().duration(100), 0.81); | |
| function initializeGraph(eol_id){ | |
| //calculate prey and predator positions (according to the source node coordinates) | |
| calculatePositions((width-100)/2,(height-100)/2); | |
| //query prey_predator json | |
| d3.json(dataUrl(eol_id), function(err, g) { | |
| if (err) throw err; | |
| graph = g; | |
| dataStored.push(eol_id); | |
| graph.nodes[0].x = (width-100)/2; | |
| graph.nodes[0].y = (height-100)/2; | |
| //initialize the first source node | |
| source_nodes.push(graph.nodes[0]); | |
| //display tooltip | |
| tooltip.style("display", "inline-block") | |
| .style("opacity", .9) | |
| tooltip.html("<p style=\"font-size: 15px; color:"+ color(0)+"; font-style: italic;\"><a href=\"https://eol.org/pages/"+graph.nodes[0].id+"\" style=\"color: black; font-weight: bold; font-size: 15px\" target=\"_blank\">"+graph.nodes[0].label+ "</a><br /><p><strong>source</strong> of "+graph.nodes[0].label+"</p><img src=\""+ graph.nodes[0].icon+ "\" width=\"140\"><p>"); | |
| graph.nodes.forEach(n=>{ | |
| n.px = n.x; | |
| n.py = n.y; | |
| existing_nodes.push(n); | |
| if(!(nodeIDList.includes(n.id.toString()))){ | |
| nodeIDList.push(n.id.toString());} | |
| }); | |
| graph.links.forEach(l=>{ | |
| existing_links.push(l); | |
| if(!(linkIDList.includes([l.source.toString()+l.target.toString()]))) { | |
| linkIDList.push([l.source.toString()+l.target.toString()]); | |
| } | |
| }); | |
| simulation | |
| .nodes(graph.nodes) | |
| simulation.force("link") | |
| .links(graph.links); | |
| toggleVisibilityOfNodesAndLinks(graph, graph.nodes[0]); | |
| updateCoordinates(); | |
| updateGraph(); | |
| transition=false; | |
| }); | |
| } | |
| function calculatePositions (sourceX, sourceY) { | |
| sourcePos.length = 0; | |
| preyPos.length = 0; | |
| predPos.length = 0; | |
| compPos.length = 0; | |
| var add, preyAngle, predAngle; | |
| //set another dimension for padding | |
| var r_width = width-100; | |
| var r_height = height- 100; | |
| //alternative heights (display purpose) | |
| var radius= [height/4, height/4+20]; | |
| sourcePos.push(sourceX); | |
| sourcePos.push(sourceY); | |
| for (var i= 0; i< nLimit ; i++) { | |
| if(nLimit == 1){ | |
| add = 1/8; | |
| predAngle = (7/6 + add) * Math.PI; | |
| } else { | |
| //add = 1/((nLimit-1)*2); | |
| add = 2/(3*(nLimit-1)); | |
| //predAngle = (7/6) * Math.PI; | |
| predAngle = (7/6 + (i)*add) * Math.PI; | |
| } | |
| preyAngle = (1/6 + ((i)*add)) * Math.PI; | |
| preyPos.push([((radius[i%2] * Math.cos(preyAngle)) + sourceX), | |
| ((radius[i%2] * Math.sin(preyAngle)) + sourceY)]); | |
| predPos.push ([((radius[i%2] * Math.cos(predAngle)) + sourceX), | |
| ((radius[i%2] * Math.sin(predAngle)) + sourceY)]); | |
| } | |
| } | |
| function updateGraph() { | |
| transition = true; | |
| var gColor = ["source", "predator", "prey", "", "", "competitor"]; | |
| //copy nodes | |
| var tmp_eNodes = existing_nodes.slice(); | |
| var tmp_nNodes = new_nodes.slice(); | |
| var tmp_hNodes = hiding_nodes.slice(); | |
| var currentNodes = tmp_eNodes.concat(tmp_nNodes); | |
| var tmp_eLinks = existing_links.slice(); | |
| var tmp_nLinks = new_links.slice(); | |
| //clear previous items | |
| existing_nodes=[]; | |
| new_nodes = []; | |
| hiding_nodes = []; | |
| existing_links = []; | |
| new_links = []; | |
| currentNodes.forEach(node => { | |
| if(node.show) { | |
| existing_nodes.push(node); | |
| } else { | |
| hiding_nodes.push(node); | |
| } | |
| }); | |
| tmp_hNodes.forEach(node=> { | |
| if(node.show) { | |
| new_nodes.push(node); | |
| } else { | |
| hiding_nodes.push(node); | |
| } | |
| }); | |
| graph.links.filter(n => n.show).forEach(l =>{ | |
| if(existing_nodes.includes(l.source) && existing_nodes.includes(l.target)){ | |
| existing_links.push(l); | |
| } else { | |
| new_links.push(l); | |
| } | |
| }); | |
| console.log("existing_nodes", existing_nodes); | |
| console.log("new_nodes", new_nodes); | |
| console.log("existing_links", existing_links); | |
| console.log("(new_links)", new_links); | |
| //EXIT-Remove previous nodes/links | |
| svg.selectAll('line').data(graph.links.filter(n=>{n.show})).exit().remove(); | |
| svg.selectAll('.node').data(new_node).exit().remove(); | |
| svg.selectAll('.new_node').data(new_node).exit().remove(); | |
| svg.selectAll('.existing_node').data(existing_node).exit().remove(); | |
| existing_link = svg.selectAll('.line') | |
| .data(existing_links, function(d) { return d.id;}) | |
| .enter().append('line') | |
| .attr('class', 'link') | |
| .attr("marker-end", function(d) { | |
| if(source_nodes.includes(d.target)){ | |
| return "url(#longer)"; | |
| } else { | |
| return "url(#arrow)"; | |
| } | |
| }) | |
| .attr("x1", function(d) {return d.source.px;}) | |
| .attr("y1", function(d) {return d.source.py;}) | |
| .attr("x2", function(d) {return d.target.px;}) | |
| .attr("y2", function(d) {return d.target.py;}); | |
| new_link = svg.selectAll('.new_link') | |
| .data(new_links, function(d) { return d.id;}) | |
| .enter().append('line') | |
| .attr('class', 'new_link') | |
| .attr('opacity', 0) | |
| .attr("marker-end", function(d) { | |
| if(source_nodes.includes(d.target)){ | |
| return "url(#longer)"; | |
| } else { | |
| return "url(#arrow)"; | |
| } | |
| }) | |
| .attr("x1", function(d) {return d.source.nx;}) | |
| .attr("y1", function(d) {return d.source.ny;}) | |
| .attr("x2", function(d) {return d.target.nx;}) | |
| .attr("y2", function(d) {return d.target.ny;}); | |
| console.log("new_nodes", new_nodes); | |
| new_node = svg.selectAll('.new_node') | |
| //UPDATE | |
| .data(new_nodes) | |
| .enter().append('g') | |
| .attr('class', 'new_node') | |
| .attr("id", function(d) {return d.label.replace(/\s/g,'');}) | |
| .attr("x", function(d) {return d.fx;}) | |
| .attr("y", function(d) {return d.fy;}) | |
| .attr("transform", d => `translate(${d.nx},${d.ny})`) | |
| .attr('opacity', 0) | |
| .call(d3.drag() | |
| .subject(function() { | |
| var t = d3.select(this); | |
| var tr = getTranslation(t.attr("transform")); | |
| return {x: t.attr("x") + tr[0], | |
| y: t.attr("y") + tr[1]}; | |
| }) | |
| .on("drag", function(d,i) { | |
| d3.select(this).attr("transform", function(d,i) { | |
| d.x = d3.event.x; | |
| d.y = d3.event.y; | |
| return "translate(" + [ d3.event.x, d3.event.y ] + ")";}); | |
| svg.selectAll('.new_link').data(new_links).filter(l => (l.source === d)) | |
| .transition().duration(1).attr("x1", d3.event.x).attr("y1", d3.event.y); | |
| svg.selectAll('.link').data(existing_links).filter(l => (l.source === d)) | |
| .transition().duration(1).attr("x1", d3.event.x).attr("y1", d3.event.y); | |
| svg.selectAll('.new_link').data(new_links).filter(l => (l.target === d)) | |
| .transition().duration(1).attr("x2", d3.event.x).attr("y2", d3.event.y); | |
| svg.selectAll('.link').data(existing_links).filter(l => (l.target === d)) | |
| .transition().duration(1).attr("x2", d3.event.x).attr("y2", d3.event.y); | |
| })); | |
| //APPEND IMAGE | |
| new_node.append("svg:pattern") | |
| .attr("id", function(d) {return d.id.toString();}) | |
| .attr("width", "100%") | |
| .attr("height", "100%") | |
| .attr("patternContentUnits", "objectBoundingBox") | |
| .attr("preserveAspectRatio", "xMidYMid slice") | |
| .attr("viewBox", "0 0 1 1") | |
| .append("svg:image") | |
| .attr("xlink:href", function(d) {return d.icon;}) | |
| .attr("width", "1") | |
| .attr("height", "1") | |
| .attr("preserveAspectRatio", "xMidYMid slice"); | |
| //APPEND CIRCLE | |
| new_node.append('circle') | |
| .attr("r", function(d) { | |
| if(source_nodes.includes (d)){ | |
| return source_radius; | |
| } else { | |
| return radius; | |
| } | |
| }) | |
| .attr("fill", function(d) { | |
| if (source_nodes.includes (d)) { | |
| return 'url(#'+d.id.toString()+')'; | |
| } | |
| else if (d.type == "predator" | d.type =="prey" | d.type =="competitor") { | |
| return color(gColor.indexOf(d.type)); | |
| } | |
| else if (d.group%2==0) { return color(1);} | |
| else {return color(2);} | |
| }) ; | |
| new_node.on("click", d => { | |
| appendJSON(d); | |
| }) | |
| .on('mouseover.fade', fade(0.1)) | |
| .on('mouseout.fade', fade(1)) | |
| .on('mouseover.tooltip', function(d) { | |
| tooltip.style("display", "inline-block") | |
| .style("opacity", .9) | |
| tooltip.html("<p style=\"font-size: 15px; color:"+ color(gColor.indexOf(d.type))+"; font-style: italic;\"><a href=\"https://eol.org/pages/"+d.id+"\" style=\"color: black; font-weight: bold; font-size: 15px\" target=\"_blank\">"+d.label+ "</a><br /><p><strong>"+d.type+"</strong> of "+curSource.label+"</p><img src=\""+ d.icon+ "\" width=\"140\"><p>"); | |
| }); | |
| new_node.append('text') | |
| .attr('x', function(d) { | |
| if (source_nodes.includes(d)){ | |
| return 32; | |
| } else { | |
| return 0; | |
| } | |
| }) | |
| .attr('y', function(d) { | |
| if(source_nodes.includes(d)){ | |
| return 0; | |
| }else { | |
| return 15; | |
| } | |
| }) | |
| .attr('dy', '.35em') | |
| .attr("fill", 'black') | |
| .attr("font-family", "verdana") | |
| .attr("font-size", "10px") | |
| .attr("text-anchor",function(d) { | |
| if(source_nodes.includes(d)) { | |
| return "left"; | |
| } else { | |
| return "middle"; | |
| } | |
| }) | |
| .text(function(d) {return d.label;}); | |
| existing_node = svg.selectAll('.existing_node') | |
| //UPDATE | |
| .data(existing_nodes) | |
| .enter().append('g') | |
| .attr('class', 'existing_node') | |
| .attr("transform", d => `translate(${d.px},${d.py})`) | |
| .call(d3.drag() | |
| .subject(function() { | |
| var t = d3.select(this); | |
| var tr = getTranslation(t.attr("transform")); | |
| return {x: t.attr("x") + tr[0], | |
| y: t.attr("y") + tr[1]}; | |
| }) | |
| .on("drag", function(d,i) { | |
| d3.select(this).attr("transform", function(d,i) { | |
| d.x = d3.event.x; | |
| d.y = d3.event.y; | |
| return "translate(" + [ d3.event.x, d3.event.y ] + ")";}); | |
| svg.selectAll('.new_link').data(new_links).filter(l => (l.source === d)) | |
| .transition().duration(1).attr("x1", d3.event.x).attr("y1", d3.event.y); | |
| svg.selectAll('.link').data(existing_links).filter(l => (l.source === d)) | |
| .transition().duration(1).attr("x1", d3.event.x).attr("y1", d3.event.y); | |
| svg.selectAll('.new_link').data(new_links).filter(l => (l.target === d)) | |
| .transition().duration(1).attr("x2", d3.event.x).attr("y2", d3.event.y); | |
| svg.selectAll('.link').data(existing_links).filter(l => (l.target === d)) | |
| .transition().duration(1).attr("x2", d3.event.x).attr("y2", d3.event.y); | |
| })); | |
| //.on("drag", dragged)); | |
| //APPEND IMAGE | |
| existing_node.append("svg:pattern") | |
| .attr("id", function(d) {return d.id.toString();}) | |
| .attr("width", "100%") | |
| .attr("height", "100%") | |
| .attr("patternContentUnits", "objectBoundingBox") | |
| .attr("preserveAspectRatio", "xMidYMid slice") | |
| .attr("viewBox", "0 0 1 1") | |
| .append("svg:image") | |
| .attr("xlink:href", function(d) {return d.icon;}) | |
| .attr("width", "1") | |
| .attr("height", "1") | |
| .attr("preserveAspectRatio", "xMidYMid slice"); | |
| existing_node.append('circle') | |
| .attr("r", function(d) { | |
| if(source_nodes.includes (d)){ | |
| return source_radius; | |
| } else { | |
| return radius; | |
| } | |
| }) | |
| .attr("fill", function(d) { | |
| if (source_nodes.includes (d)) { | |
| return 'url(#'+d.id.toString()+')'; | |
| } | |
| else if (d.type == "predator" | d.type =="prey" | d.type =="competitor") { | |
| return color(gColor.indexOf(d.type)); | |
| } | |
| else if (d.group%2==0) { return color(1);} | |
| else {return color(2);} | |
| }) | |
| .on('mouseover.fade', fade(0.1)) | |
| .on('mouseout.fade', fade(1)) | |
| .on('mouseover.tooltip', function(d) { | |
| tooltip.style("display", "inline-block") | |
| .style("opacity", .9) | |
| tooltip.html("<p style=\"font-size: 15px; color:"+ color(gColor.indexOf(d.type))+"; font-style: italic;\"><a href=\"https://eol.org/pages/"+d.id+"\" style=\"color: black; font-weight: bold; font-size: 15px\" target=\"_blank\">"+d.label+ "</a><br /><p><strong>"+d.type+"</strong> of "+curSource.label+"</p><img src=\""+ d.icon+ "\" width=\"140\"><p>"); | |
| }); | |
| existing_node.append('text') | |
| .attr('x', function(d) { | |
| if (source_nodes.includes(d)){ | |
| return 32; | |
| } else { | |
| return 0; | |
| } | |
| }) | |
| .attr('y', function(d) { | |
| if(source_nodes.includes(d)){ | |
| return 0; | |
| }else { | |
| return 15; | |
| } | |
| }) | |
| .attr('dy', '.35em') | |
| .attr("fill", 'black') | |
| .attr("font-family", "verdana") | |
| .attr("font-size", "10px") | |
| .attr("text-anchor",function(d) { | |
| if(source_nodes.includes(d)) { | |
| return "left"; | |
| } else { | |
| return "middle"; | |
| } | |
| }) | |
| .text(function(d) {return d.label;}); | |
| existing_node.on("click", d => {appendJSON(d);}); | |
| new_node.on("click", d => {appendJSON(d);}) | |
| //ANIMATION | |
| //existing nodes stay same & link follows the nodes | |
| svg.selectAll('.existing_node').data(existing_nodes) | |
| .transition().duration(5000).attr("transform", d => `translate(${d.nx},${d.ny})`); | |
| svg.selectAll('.link').data(existing_links) | |
| .transition().duration(5000).attr("x1", function(d) { return d.source.nx; }).attr("y1", function(d) { return d.source.ny; }).attr("x2", function(d) { return d.target.nx; }).attr("y2", function(d) { return d.target.ny; }) | |
| //new nodes and links appear after transition | |
| svg.selectAll('.new_node') | |
| .transition().duration(5000).delay(1000).attr("opacity", 1); | |
| svg.selectAll('.new_link').transition().duration(3000).delay(3000).attr("opacity", 1).on('end', function () {transition = false}); | |
| simulation | |
| .nodes(graph.nodes) | |
| simulation.force("link") | |
| .links(graph.links); | |
| simulation.alpha(1).alphaTarget(0).restart(); | |
| //new coordinate (n.x, n.y) -> past coordinate (p.x, p.y) | |
| updateCoordinates(); | |
| } | |
| function getTranslation(transform) { | |
| // Create a dummy g for calculation purposes only. This will never | |
| // be appended to the DOM and will be discarded once this function | |
| // returns. | |
| var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| // Set the transform attribute to the provided string value. | |
| g.setAttributeNS(null, "transform", transform); | |
| // consolidate the SVGTransformList containing all transformations | |
| // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get | |
| // its SVGMatrix. | |
| var matrix = g.transform.baseVal.consolidate().matrix; | |
| // As per definition values e and f are the ones for the translation. | |
| return [matrix.e, matrix.f]; | |
| } | |
| function dragged(d, i) { | |
| d3.select(this).attr("transform", function(d,i) { | |
| return "translate(" + [ d3.event.x,d3.event.y ] + ")"}); | |
| } | |
| /* | |
| function dragged(d) { | |
| if (!d3.event.active) simulation.alphaTarget(0); | |
| console.log("event", d3.event.x, d3.event.y); | |
| d.x = d3.event.x, d.y = d3.event.y; | |
| console.log("filtered links", existing_link.filter(function(l) { return l.source === d; })); | |
| console.log("filtered links", existing_link.filter(function(l) { return l.target === d; })) | |
| //existing_node.attr("transform", d => `translate(${d3.event.x},${d3.event.y})`) | |
| d3.select(this).attr("cx", d3.event.x).attr("cy", d3.event.y); | |
| //d3.select(this).attr("transform", d => `translate(${d3.event.x},${d3.event.y})`); | |
| existing_link.filter(function(l) { return l.source === d; }).attr("x1", d.x).attr("y1", d.y); | |
| existing_link.filter(function(l) { return l.target === d; }).attr("x2", d.x).attr("y2", d.y); | |
| } | |
| */ | |
| function updateCoordinates() { | |
| graph.nodes.forEach(n=> { | |
| n.px = n.nx; | |
| n.py = n.ny; | |
| }); | |
| } | |
| //new data | |
| function appendJSON(d) { | |
| var eol_id = d.id.toString(); | |
| //http request to JSON data | |
| if(!(dataStored.includes(eol_id))) { | |
| d3.json(dataUrl(eol_id), function(err, g) { | |
| if (err) {alert("No data found!"); throw err;} | |
| g.nodes.forEach(n => { | |
| if(!(nodeIDList.includes(n.id.toString()))) { | |
| //adding new nodes | |
| graph.nodes.push(n); | |
| n.x = 0; | |
| n.y = 0; | |
| n.px = 0; | |
| n.py = 0; | |
| n.nx = 0; | |
| n.ny = 0; | |
| n.show=false; | |
| nodeIDList.push(n.id.toString()); | |
| hiding_nodes.push(n); | |
| } | |
| }); | |
| g.links.forEach(l=> { | |
| if(!(linkIDList.includes(l.source.toString()+l.target.toString()))) { | |
| graph.links.push(l); | |
| l.show=false; | |
| linkIDList.push(l.source.toString()+l.target.toString()); | |
| } | |
| }); | |
| simulation | |
| .nodes(graph.nodes) | |
| simulation.force("link") | |
| .links(graph.links); | |
| toggleVisibilityOfNodesAndLinks(graph, d); | |
| updateGraph(); | |
| dataStored.push(eol_id); | |
| }); | |
| } else { | |
| //already stored data | |
| toggleVisibilityOfNodesAndLinks(graph, d); | |
| updateGraph(); | |
| } | |
| } | |
| function toggleVisibilityOfNodesAndLinks (graph,d) { | |
| var preyList = []; | |
| var predList = []; | |
| compList = []; | |
| curSource = addSourceNode(d); | |
| graph.nodes.forEach(node=> { | |
| if (node.id==d.id){ | |
| node.type ="source"; | |
| node.show = true; | |
| } else if (isConnectedOneWay(d, node) && d.id != node.id){ | |
| if (preyList.length < nLimit) { | |
| node.show = true; | |
| node.type = "prey"; | |
| preyList.push(node); | |
| } else{ | |
| node.show = false; | |
| node.type="none"; | |
| } | |
| } else if (isConnectedOneWay(node, d) && d.id != node.id) { | |
| if (predList.length < nLimit) { | |
| node.show=true; | |
| node.type = "predator"; | |
| predList.push(node); | |
| } else { | |
| node.show = false; | |
| node.type ="none"; | |
| } | |
| } | |
| else { | |
| node.show=false; | |
| node.type="none"; | |
| } | |
| }); | |
| //competitors | |
| graph.nodes.forEach(node=> { | |
| preyList.forEach(n=>{ | |
| if (isConnectedOneWay(node, n) && node.type == "none"){ | |
| if (compList.length < 10) { | |
| node.show = true; | |
| node.type = "competitor"; | |
| compList.push([node, n] ); | |
| } else{ | |
| node.show = false; | |
| node.type="none"; | |
| } | |
| } | |
| }); | |
| }); | |
| graph.links.forEach(link => { | |
| if(link.source.show && link.target.show){ | |
| link.show = true; | |
| } else { | |
| link.show = false; | |
| } | |
| }); | |
| updatePositions((width-100)/2, (height-100)/2); | |
| } | |
| function loadData(eolId, animate) { | |
| //query prey_predator json | |
| d3.json(dataUrl(eolId), function(err, g) { | |
| if (err) throw err; | |
| var prevGraph = graph; | |
| graph = g; | |
| pruneGraph(graph, prevGraph); | |
| updatePositions(); | |
| updateGraph(animate); | |
| $dimmer.removeClass('active'); | |
| }); | |
| } | |
| function dataUrl(pageId) { | |
| return sitePrefix + "/api/pages/" + pageId + "/pred_prey.json" | |
| } | |
| function updatePositions(sourceX, sourceY) { | |
| console.log("update positions") | |
| //make a copy of an array | |
| var tmpPreyPos, tmpPredPos, tmpCompPos; | |
| tmpPreyPos = preyPos.slice(); | |
| tmpPredPos = predPos.slice(); | |
| graph.nodes.filter(n => n.show).forEach(node => { | |
| if (node.type == "source") { | |
| console.log("source position", sourcePos[0],sourcePos[1]) | |
| node.nx = sourcePos[0]; | |
| node.ny = sourcePos[1]; | |
| } | |
| else if (node.type == "predator") { | |
| var middle = tmpPredPos[Math.floor(tmpPredPos.length/2)]; | |
| var index = tmpPredPos.indexOf(middle); | |
| node.nx = middle[0]; | |
| node.ny = middle[1]; | |
| if (index > -1) { | |
| tmpPredPos.splice(index, 1); | |
| } | |
| } else if (node.type == "prey") { | |
| if(tmpPreyPos.length != 0){ | |
| var middle = tmpPreyPos[Math.floor(tmpPreyPos.length/2)]; | |
| var index = tmpPreyPos.indexOf(middle); | |
| node.nx = middle[0]; | |
| node.ny = middle[1]; | |
| if (index > -1) { | |
| tmpPreyPos.splice(index, 1); | |
| } | |
| }} | |
| }); | |
| if(compList.length != 0){ | |
| var extra = 5; | |
| var gap = (width-100)/(compList.length+extra); | |
| compPos.length = 0; | |
| for(var i = 0; i<compList.length+extra; i++) { | |
| var value = 100 + (i*gap); | |
| compPos.push(value); | |
| } | |
| tmpCompPos = compPos.slice(); | |
| for (var i =0; i < extra; i++ ) { | |
| tmpCompPos.splice(Math.floor(tmpCompPos.length/2), 1); | |
| } | |
| var varHeight = -1 | |
| compList.forEach(c => { | |
| if(c[1].nx < width/2) { | |
| c[0].nx = tmpCompPos[0]; | |
| tmpCompPos.splice(0, 1); | |
| } else { | |
| var endIndex = tmpCompPos.length-1; | |
| c[0].nx = tmpCompPos[endIndex]; | |
| tmpCompPos.splice(endIndex, 1); | |
| } | |
| c[0].ny = sourceY+(15*varHeight); | |
| varHeight = varHeight*-1; | |
| }); | |
| } | |
| } | |
| function addSourceNode (d) { | |
| //most recent source | |
| var index = source_nodes.length-1; | |
| //the first source node | |
| if (d.id == source_nodes[0].id) { | |
| //remove everything | |
| source_nodes.splice(d); | |
| //put the first source node (reset effect) | |
| source_nodes.push(d); | |
| d.type = "source"; | |
| } | |
| //already the source node | |
| else if (source_nodes.includes(d)) { | |
| d.type = "source"; | |
| } | |
| else { | |
| source_nodes.push(d); | |
| d.type = "source"; | |
| } | |
| return d; | |
| } | |
| function fade(opacity) { | |
| return d => { | |
| if(!(transition)) { | |
| new_node.transition().duration(500).style('stroke-opacity', function (o) { | |
| const thisOpacity = isConnected(d, o) ? 1 : opacity; | |
| this.setAttribute('fill-opacity', thisOpacity); | |
| return thisOpacity;}); | |
| existing_node.transition().duration(500).style('stroke-opacity', function (o) { | |
| const thisOpacity = isConnected(d, o) ? 1 : opacity; | |
| this.setAttribute('fill-opacity', thisOpacity); | |
| return thisOpacity;}); | |
| new_link.style('opacity', o => (o.source === d || o.target === d ? 1 : opacity)); | |
| existing_link.style('opacity', o => (o.source === d || o.target === d ? 1 : opacity)); | |
| }}; | |
| } | |
| function isConnected(a, b) { | |
| const linkedByIndex = {}; | |
| graph.links.forEach(d => { | |
| linkedByIndex[`${d.source.index},${d.target.index}`] = 1; | |
| }); | |
| return linkedByIndex[`${a.index},${b.index}`] || linkedByIndex[`${b.index},${a.index}`] || a.index === b.index; | |
| } | |
| function isConnectedOneWay(a, b) { | |
| const linkedByIndex = {}; | |
| graph.links.forEach(d => { | |
| linkedByIndex[`${d.source.index},${d.target.index}`] = 1; | |
| }); | |
| return linkedByIndex[`${a.index},${b.index}`]; | |
| } |
US