From 89bc9474095367680abe63064cc79f7a2dae89e6 Mon Sep 17 00:00:00 2001 From: Pitchaya Boonsarngsuk <2285135b@student.gla.ac.uk> Date: Wed, 24 Jan 2018 21:23:25 +0000 Subject: [PATCH] Separate a Link Force with optimization --- .../js/src/neighbourSampling-papaparsing.js | 18 +++- .../linkForce.js | 51 +++++----- index.js | 36 +++---- src/link.js | 99 +++++-------------- 4 files changed, 80 insertions(+), 124 deletions(-) diff --git a/examples/js/src/neighbourSampling-papaparsing.js b/examples/js/src/neighbourSampling-papaparsing.js index 7965d7d..5af706c 100644 --- a/examples/js/src/neighbourSampling-papaparsing.js +++ b/examples/js/src/neighbourSampling-papaparsing.js @@ -41,7 +41,8 @@ var nodes, // as in Data points fileName = "", selectedData, clickedIndex = -1, - paused = false; + paused = false, + alreadyRanIterations; // Default parameters var MULTIPLIER = 50, @@ -119,10 +120,16 @@ function processData(data, error) { props.pop(); //Hide Iris index / last column from distance function //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) { d.x = (Math.random()-0.5) * 100000; d.y = (Math.random()-0.5) * 100000; - }); + });*/ addNodesToDOM(nodes); @@ -180,6 +187,8 @@ function addNodesToDOM(data) { } function ticked() { + console.log("ticked"); + alreadyRanIterations++; // If rendering is selected, then draw at every iteration. if (rendering === true) { node // Each sub-circle in the SVG, update cx and cy @@ -194,9 +203,14 @@ function ticked() { if (springForce) { intercom.emit("passedData", simulation.force(forceName).distributionData()); } + if(alreadyRanIterations == ITERATIONS) { + simulation.stop(); + ended(); + } } function ended() { + console.log("ended"); if (rendering !== true) { // Never drawn anything before? Now it's time. node .attr("cx", function (d) { diff --git a/examples/js/src/neighbourSampling-papaparsing/linkForce.js b/examples/js/src/neighbourSampling-papaparsing/linkForce.js index 163e45e..b60bb57 100644 --- a/examples/js/src/neighbourSampling-papaparsing/linkForce.js +++ b/examples/js/src/neighbourSampling-papaparsing/linkForce.js @@ -1,40 +1,45 @@ - /** * Initialize the link force algorithm and start simulation. */ function startLinkSimulation() { console.log("startLinkSimulation") springForce = false; + alreadyRanIterations = 0; simulation.stop(); p1 = performance.now(); let links = []; // Initialize link array. nodes = simulation.nodes(); - for (i = 0; i < nodes.length; i++) { - for (j = 0; j < i; j++) { - if (i !== j) { - links.push({ - source: nodes[i], - target: nodes[j], - }); - } + for (i = nodes.length-1; i >= 1; i--) { + for (j = i-1; j >= 0; j--) { + links.push({ + source: nodes[i], + target: nodes[j], + }); } } - // Add the links to the simulation. - simulation.force(forceName, d3.forceLink().links(links)); - - simulation - .alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS)) + /* Add force + * Please add the distance function before feeding the force + * it to the simulation or adding links. + * + * 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) - .force(forceName) - // The distance function that will be used to calculate distances - // 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(); + .alpha(1) + .restart(); } diff --git a/index.js b/index.js index 19935c5..03fe85c 100644 --- a/index.js +++ b/index.js @@ -1,28 +1,20 @@ -/*export { - default as forceNeighbourSampling -} +/* +export {default as forceNeighbourSampling} from "./src/neighbourSampling"; -export { - default as forceNeighbourSamplingPre -} - from "./src/neighbourSamplingPre";*/ -export { - default as forceNeighbourSamplingDistance -} +export {default as forceNeighbourSamplingPre} + from "./src/neighbourSamplingPre"; +*/ +export {default as forceNeighbourSamplingDistance} from "./src/neighbourSamplingDistance"; -export { - default as forceBarnesHut -} + +export { default as forceBarnesHut} from "./src/barnesHut"; -export { - default as tSNE -} + +export { default as tSNE} from "./src/t-sne"; -export { - default as forceLink -} + +export { default as forceLinkOptimized} from "./src/link"; -export { - default as hybridSimulation -} + +export { default as hybridSimulation} from "./src/hybridSimulation"; diff --git a/src/link.js b/src/link.js index ad9be61..7be7219 100644 --- a/src/link.js +++ b/src/link.js @@ -1,100 +1,53 @@ import constant from "./constant"; import jiggle from "./jiggle"; -import {map} from "d3-collection"; -import {getStress} from "./stress"; /** - * Extended link force algorithm to include the stress metric for - * comparisons between the different algorithms. - * Everything else is the same as in D3 force module. + * Modified link force algorithm + * - ignore alpha + * - 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) { - var id = index, - strength = defaultStrength, - strengths, + var dataSizeFactor, distance = constant(30), - distances, + distances = [], nodes, - count, - bias, iterations = 1; if (links == null) links = []; - function defaultStrength(link) { - return 1 / Math.min(count[link.source.index], count[link.target.index]); - } - - function force(alpha) { + function force() { 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]; + // jiggle so it wont divide / multiply by zero after this source = link.source; target = link.target; - x = target.x + target.vx - source.x - source.vx || jiggle(); - y = target.y + target.vy - source.y - source.vy || jiggle(); + x = target.x - source.x || jiggle(); + y = target.y - source.y || jiggle(); 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; - target.vx -= x * (b = bias[i]); - target.vy -= y * b; - source.vx += x * (b = 1 - b); - source.vy += y * b; + target.vx -= x; + target.vy -= y; + source.vx += x; + source.vy += y; } } } function initialize() { if (!nodes) return; - - var i, - n = nodes.length, - 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); - } + console.log("CUSTOM LINK FORCE"); + dataSizeFactor = 0.5/(nodes.length-1); + initializeDistance(); } function initializeDistance() { + console.log("INIT DISTANCE", links.length); if (!nodes) return; for (var i = 0, n = links.length; i < n; ++i) { @@ -119,17 +72,9 @@ export default function(links) { return arguments.length ? (iterations = +_, force) : iterations; }; - force.strength = function(_) { - return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength; - }; - force.distance = function(_) { 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; }