Separate a Link Force with optimization

This commit is contained in:
Pitchaya Boonsarngsuk
2018-01-24 21:23:25 +00:00
parent 36ddbd13c4
commit 89bc947409
4 changed files with 80 additions and 124 deletions

View File

@@ -41,7 +41,8 @@ var nodes, // as in Data points
fileName = "", fileName = "",
selectedData, selectedData,
clickedIndex = -1, clickedIndex = -1,
paused = false; paused = false,
alreadyRanIterations;
// Default parameters // Default parameters
var MULTIPLIER = 50, var MULTIPLIER = 50,
@@ -119,10 +120,16 @@ function processData(data, error) {
props.pop(); //Hide Iris index / last column from distance function props.pop(); //Hide Iris index / last column from distance function
//Put the nodes in random starting positions //Put the nodes in random starting positions
//TODO Change this back
nodes.forEach(function (d) {
d.x = 0;
d.y = 0;
});
/*
nodes.forEach(function (d) { nodes.forEach(function (d) {
d.x = (Math.random()-0.5) * 100000; d.x = (Math.random()-0.5) * 100000;
d.y = (Math.random()-0.5) * 100000; d.y = (Math.random()-0.5) * 100000;
}); });*/
addNodesToDOM(nodes); addNodesToDOM(nodes);
@@ -180,6 +187,8 @@ function addNodesToDOM(data) {
} }
function ticked() { function ticked() {
console.log("ticked");
alreadyRanIterations++;
// If rendering is selected, then draw at every iteration. // If rendering is selected, then draw at every iteration.
if (rendering === true) { if (rendering === true) {
node // Each sub-circle in the SVG, update cx and cy node // Each sub-circle in the SVG, update cx and cy
@@ -194,9 +203,14 @@ function ticked() {
if (springForce) { if (springForce) {
intercom.emit("passedData", simulation.force(forceName).distributionData()); intercom.emit("passedData", simulation.force(forceName).distributionData());
} }
if(alreadyRanIterations == ITERATIONS) {
simulation.stop();
ended();
}
} }
function ended() { function ended() {
console.log("ended");
if (rendering !== true) { // Never drawn anything before? Now it's time. if (rendering !== true) { // Never drawn anything before? Now it's time.
node node
.attr("cx", function (d) { .attr("cx", function (d) {

View File

@@ -1,40 +1,45 @@
/** /**
* Initialize the link force algorithm and start simulation. * Initialize the link force algorithm and start simulation.
*/ */
function startLinkSimulation() { function startLinkSimulation() {
console.log("startLinkSimulation") console.log("startLinkSimulation")
springForce = false; springForce = false;
alreadyRanIterations = 0;
simulation.stop(); simulation.stop();
p1 = performance.now(); p1 = performance.now();
let links = []; let links = [];
// Initialize link array. // Initialize link array.
nodes = simulation.nodes(); nodes = simulation.nodes();
for (i = 0; i < nodes.length; i++) { for (i = nodes.length-1; i >= 1; i--) {
for (j = 0; j < i; j++) { for (j = i-1; j >= 0; j--) {
if (i !== j) { links.push({
links.push({ source: nodes[i],
source: nodes[i], target: nodes[j],
target: nodes[j], });
});
}
} }
} }
// Add the links to the simulation. /* Add force
simulation.force(forceName, d3.forceLink().links(links)); * Please add the distance function before feeding the force
* it to the simulation or adding links.
simulation *
.alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS)) * On setting the distance fn and being initialized by the simulation, the
* force will pre-calculate high-dimensional distances of every link and
* store that as cache.
* Adding distance fn before links means that the first pre-calculation will
* calculate noting as there was no link.
* The full pre-calculation will then occur once when the force is being
* initialized by the simulation.
*/
simulation.force(forceName,
d3.forceLinkOptimized()
.distance(function (n) {
return distanceFunction(n.source, n.target, props, norm);
})
.links(links)
).alphaDecay(0)
//.velocityDecay(0.8) //.velocityDecay(0.8)
.force(forceName) .alpha(1)
// The distance function that will be used to calculate distances .restart();
// between nodes.
.distance(function (n) {
return distanceFunction(n.source, n.target, props, norm);
})
// Set the parameter for the algorithm (optional).
// Restart the simulation.
simulation.alpha(1).restart();
} }

View File

@@ -1,28 +1,20 @@
/*export { /*
default as forceNeighbourSampling export {default as forceNeighbourSampling}
}
from "./src/neighbourSampling"; from "./src/neighbourSampling";
export { export {default as forceNeighbourSamplingPre}
default as forceNeighbourSamplingPre from "./src/neighbourSamplingPre";
} */
from "./src/neighbourSamplingPre";*/ export {default as forceNeighbourSamplingDistance}
export {
default as forceNeighbourSamplingDistance
}
from "./src/neighbourSamplingDistance"; from "./src/neighbourSamplingDistance";
export {
default as forceBarnesHut export { default as forceBarnesHut}
}
from "./src/barnesHut"; from "./src/barnesHut";
export {
default as tSNE export { default as tSNE}
}
from "./src/t-sne"; from "./src/t-sne";
export {
default as forceLink export { default as forceLinkOptimized}
}
from "./src/link"; from "./src/link";
export {
default as hybridSimulation export { default as hybridSimulation}
}
from "./src/hybridSimulation"; from "./src/hybridSimulation";

View File

@@ -1,100 +1,53 @@
import constant from "./constant"; import constant from "./constant";
import jiggle from "./jiggle"; import jiggle from "./jiggle";
import {map} from "d3-collection";
import {getStress} from "./stress";
/** /**
* Extended link force algorithm to include the stress metric for * Modified link force algorithm
* comparisons between the different algorithms. * - ignore alpha
* Everything else is the same as in D3 force module. * - removed location prediction, and bias
* - modified strength calculation
* - removed other unused functions
* Making it more suitable for the spring model.
*/ */
function index(d, i) {
return i;
}
function find(nodeById, nodeId) {
var node = nodeById.get(nodeId);
if (!node) throw new Error("missing: " + nodeId);
return node;
}
export default function(links) { export default function(links) {
var id = index, var dataSizeFactor,
strength = defaultStrength,
strengths,
distance = constant(30), distance = constant(30),
distances, distances = [],
nodes, nodes,
count,
bias,
iterations = 1; iterations = 1;
if (links == null) links = []; if (links == null) links = [];
function defaultStrength(link) { function force() {
return 1 / Math.min(count[link.source.index], count[link.target.index]);
}
function force(alpha) {
for (var k = 0, n = links.length; k < iterations; ++k) { // Each iteration in a tick for (var k = 0, n = links.length; k < iterations; ++k) { // Each iteration in a tick
for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { //For each link for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { // For each link
link = links[i]; link = links[i];
// jiggle so it wont divide / multiply by zero after this
source = link.source; source = link.source;
target = link.target; target = link.target;
x = target.x + target.vx - source.x - source.vx || jiggle(); x = target.x - source.x || jiggle();
y = target.y + target.vy - source.y - source.vy || jiggle(); y = target.y - source.y || jiggle();
l = Math.sqrt(x * x + y * y); l = Math.sqrt(x * x + y * y);
l = (l - distances[i]) / l * alpha * strengths[i]; l = (l - distances[i]) / l * dataSizeFactor;
x *= l, y *= l; x *= l, y *= l;
target.vx -= x * (b = bias[i]); target.vx -= x;
target.vy -= y * b; target.vy -= y;
source.vx += x * (b = 1 - b); source.vx += x;
source.vy += y * b; source.vy += y;
} }
} }
} }
function initialize() { function initialize() {
if (!nodes) return; if (!nodes) return;
console.log("CUSTOM LINK FORCE");
var i, dataSizeFactor = 0.5/(nodes.length-1);
n = nodes.length, initializeDistance();
m = links.length,
nodeById = map(nodes, id),
link;
for (i = 0, count = new Array(n); i < n; ++i) {
count[i] = 0;
}
for (i = 0; i < m; ++i) {
link = links[i];
link.index = i;
//if (typeof link.source !== "object") link.source = find(nodeById, link.source);
//if (typeof link.target !== "object") link.target = find(nodeById, link.target);
++count[link.source.index];
++count[link.target.index];
}
for (i = 0, bias = new Array(m); i < m; ++i) {
link = links[i];
bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]);
}
strengths = new Array(m), initializeStrength();
distances = new Array(m), initializeDistance();
}
function initializeStrength() {
if (!nodes) return;
for (var i = 0, n = links.length; i < n; ++i) {
strengths[i] = +strength(links[i], i, links);
}
} }
function initializeDistance() { function initializeDistance() {
console.log("INIT DISTANCE", links.length);
if (!nodes) return; if (!nodes) return;
for (var i = 0, n = links.length; i < n; ++i) { for (var i = 0, n = links.length; i < n; ++i) {
@@ -119,17 +72,9 @@ export default function(links) {
return arguments.length ? (iterations = +_, force) : iterations; return arguments.length ? (iterations = +_, force) : iterations;
}; };
force.strength = function(_) {
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength;
};
force.distance = function(_) { force.distance = function(_) {
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance; return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
}; };
force.stress = function() {
return getStress(nodes, function(s,t){return distance({source: s, target: t});});
}
return force; return force;
} }