|
//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}`]; |
|
} |