forceGraph = (data) => {
const w2 = width,
h2 = height,
nodeRadius = 5;
const ctx = DOM.context2d(width, height*2);
const canvas = ctx.canvas;
let lExtents = []
let rExtents = []
let linkExtents = []
let setExtents = []
let ordinal = ['louvain', 'region']
let continuous = ['pagerank', 'degree', 'between', "occurence"]
let log = ["pagerank"];
let radiusFxn;
let colors;
data.nodes.forEach((row) => {
rExtents.push(row.volume);
});
data.links.forEach((row) => {
linkExtents.push(row.volume);
});
let extentDomains = ({'occurence': rExtents})
const linkWidth = d3.scaleLinear().domain(d3.extent(linkExtents)).range([1, lWidth]);
colors = d3.scaleOrdinal().domain(lExtents).range(d3[colorScales[colorMetric]])
if (log.includes(radiusMetric)) {
radiusFxn = d3.scaleSequentialLog().domain(d3.extent(extentDomains[radiusMetric])).range(rNode)
} else {
radiusFxn = d3.scaleLinear().domain(d3.extent(extentDomains[radiusMetric])).range(rNode)
}
let linkDistanceFxn = d3.scaleLinear().domain(d3.extent(linkExtents)).range([2, 1000
])
function forceSimulation(width, height) {
return d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-150))
.force('collision', d3.forceCollide().radius(function(d) {
return d3.scalePow().domain(extentDomains[radiusMetric]).range(rNode)(d[radiusMetric]);
}))
.force("link", d3.forceLink().id(d => d.id).distance(d => {return linkDistanceFxn(d.volume)}))
.velocityDecay(0.3);
}
const simulationDurationInMs = 20000;
let startTime = Date.now();
let endTime = startTime + simulationDurationInMs;
const simulation = forceSimulation(width/2, height/2);
let transform = d3.zoomIdentity;
// The simulation will alter the input data objects so make
// copies to protect the originals.
let nodes = data.nodes.map(d => Object.assign({}, d));
let edges = data.links.map(d => Object.assign({}, d));
console.log(nodes.length, edges.length);
d3.select(canvas)
.call(d3.drag()
// Must set this in order to drag nodes. New in v5?
.container(canvas)
.subject(dragSubject)
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded))
.call(d3.zoom()
.scaleExtent([1/100000, 1])
.on("zoom", ({transform}) => zoomed(transform)));
simulation.nodes(nodes)
.on("tick",simulationUpdate);
simulation.force("link")
.links(edges);
function zoomed(newTransform) {
transform = newTransform;
simulationUpdate();
}
zoomed(d3.zoomIdentity);
/** Find the node that was clicked, if any, and return it. */
function dragSubject() {
const x = transform.invertX(d3.event.x),
y = transform.invertY(d3.event.y);
const node = findNode(nodes, x, y, nodeRadius);
if (node) {
node.x = transform.applyX(node.x);
node.y = transform.applyY(node.y);
}
// else: No node selected, drag container
return node;
}
function dragStarted() {
if (!d3.event.active) {
simulation.alphaTarget(0.3).restart();
}
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}
function dragged() {
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}
function dragEnded() {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}
function simulationUpdate() {
ctx.save();
ctx.clearRect(0, 0, width, height*2);
ctx.translate(transform.x, transform.y);
ctx.scale(transform.k, transform.k);
let linkColor = aircraftColors[flightMetric];
// Draw edges
edges.forEach(function(d) {
if (curves) {
const l = Math.sqrt(Math.pow(d.target.x - d.source.x, 2) + Math.pow(d.target.y - d.source.y, 2)); // line length
const a = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x); // line angle
const e = l * 0.5; // control point distance
const cp = { // control point
x: (d.source.x + d.target.x) / 2 + e * Math.cos(a - Math.PI / 2),
y: (d.source.y + d.target.y) / 2 + e * Math.sin(a - Math.PI / 2)
};
ctx.beginPath();
ctx.moveTo(d.source.x, d.source.y);
ctx.quadraticCurveTo(cp.x, cp.y, d.target.x, d.target.y);
} else {
ctx.beginPath();
ctx.moveTo(d.source.x, d.source.y);
ctx.lineTo(d.target.x, d.target.y);
}
ctx.lineWidth = linkWidth(d.occurence);
ctx.strokeStyle = `rgba(${linkColor[0]}, ${linkColor[1]}, ${linkColor[2]}, ${opacity})`;
ctx.stroke();
});
// Draw nodes
nodes.forEach(function(d, i) {
ctx.beginPath();
// Node fill
console.log('+++', radiusFxn(d['volume']));
ctx.moveTo(d.x + radiusFxn(d['volume']), d.y);
ctx.arc(d.x, d.y, radiusFxn(d['volume']), 0, 2 * Math.PI);
console.log('+++', d[colorMetric]);
ctx.fillStyle = colors(d[colorMetric])
//ctx.fillStyle = 'green'
ctx.fill();
// Node outline
ctx.strokeStyle = 'white'
ctx.lineWidth = '3'
ctx.stroke();
});
// Draw nodes
nodes.forEach(function(d, i) {
if (d.volume > threshold) {
ctx.fillStyle = 'black';
ctx.font = 12 + 'px sans-serif';
ctx.fillText(d.id, d.x, d.y);
ctx.strokeStyle = 'white'
}
});
ctx.restore();
}
return canvas;
}