Select distance function:
- General
+ General
Euclidean
Manhattan
Jaccard
@@ -252,6 +252,9 @@
+
+
+
diff --git a/examples/js/src/neighbourSampling-papaparsing/hybrid.js b/examples/js/src/neighbourSampling-papaparsing/hybrid.js
new file mode 100644
index 0000000..321c34f
--- /dev/null
+++ b/examples/js/src/neighbourSampling-papaparsing/hybrid.js
@@ -0,0 +1,43 @@
+/**
+ * Initialize the hybrid layout algorithm and start simulation.
+ */
+function startHybridSimulation() {
+ console.log("startHybridSimulation");
+ springForce = false;
+ d3.selectAll(".nodes").remove();
+ simulation.stop();
+ p1 = performance.now();
+
+ configuration = {
+ iteration: ITERATIONS,
+ neighbourSize: NEIGHBOUR_SIZE,
+ sampleSize: SAMPLE_SIZE,
+ distanceRange: SELECTED_DISTANCE * MULTIPLIER,
+ fullIterations: FULL_ITERATIONS,
+ fullNeighbourSize: FULL_NEIGHBOUR_SIZE,
+ fullSampleSize: FULL_SAMPLE_SIZE,
+ fullDistanceRange: FULL_SELECTED_DISTANCE * MULTIPLIER,
+ distanceFn: function (s, t) {return distanceFunction(s, t, props, norm) * MULTIPLIER;},
+ pivots: PIVOTS,
+ numPivots: NUM_PIVOTS
+ };
+ console.log(configuration);
+ hybridSimulation = d3.hybridSimulation(nodes, configuration);
+
+ let sample = hybridSimulation.sample();
+ let remainder = hybridSimulation.remainder();
+
+ addNodesToDOM(sample);
+
+ hybridSimulation
+ .on("sampleTick", ticked)
+ .on("fullTick", ticked)
+ .on("startFull", startedFull)
+ .on("end", ended);
+
+ function startedFull() {
+ console.log("startedFull");
+ d3.selectAll(".nodes").remove();
+ addNodesToDOM(nodes);
+ }
+}
diff --git a/examples/js/src/neighbourSampling-papaparsing/linkForce.js b/examples/js/src/neighbourSampling-papaparsing/linkForce.js
new file mode 100644
index 0000000..97f07b5
--- /dev/null
+++ b/examples/js/src/neighbourSampling-papaparsing/linkForce.js
@@ -0,0 +1,40 @@
+
+/**
+ * Initialize the link force algorithm and start simulation.
+ */
+function startLinkSimulation() {
+ console.log("startLinkSimulation")
+ springForce = false;
+ simulation.stop();
+ p1 = performance.now();
+ let links = [];
+
+ // Initialize link array.
+ nodes = simulation.nodes();
+ for (i = 0; i < nodes.length; i++) {
+ for (j = 0; j < nodes.length; j++) {
+ if (i !== 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))
+ .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) * MULTIPLIER;
+ })
+ // Set the parameter for the algorithm (optional).
+ .strength(1);
+ // Restart the simulation.
+ simulation.alpha(1).restart();
+}
diff --git a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js
new file mode 100644
index 0000000..1376492
--- /dev/null
+++ b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js
@@ -0,0 +1,29 @@
+/**
+ * Initialize the Chalmers' 1996 algorithm and start simulation.
+ */
+function startNeighbourSamplingSimulation() {
+ console.log("startNeighbourSamplingSimulation");
+ springForce = true;
+ simulation.stop();
+ p1 = performance.now();
+
+ simulation
+ .alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
+ .force(forceName, d3.forceNeighbourSamplingDistance()
+ // Set the parameters for the algorithm (optional).
+ .neighbourSize(NEIGHBOUR_SIZE)
+ .sampleSize(SAMPLE_SIZE)
+ // .freeness(0.5)
+ .distanceRange(SELECTED_DISTANCE * MULTIPLIER)
+ // The distance function that will be used to calculate distances
+ // between nodes.
+ .distance(function (s, t) {
+ return distanceFunction(s, t, props, norm) * MULTIPLIER;
+ })
+ .stableVelocity(1.2 * MULTIPLIER)
+ .stableVeloHandler( function(){simulation.stop(); ended();} )
+ );
+ // Restart the simulation.
+ console.log(simulation.force(forceName).neighbourSize(), simulation.force(forceName).sampleSize());
+ simulation.alpha(1).restart();
+}
diff --git a/examples/js/src/neighbourSampling-papaparsing/otherAlgo.js b/examples/js/src/neighbourSampling-papaparsing/otherAlgo.js
new file mode 100644
index 0000000..7cadba7
--- /dev/null
+++ b/examples/js/src/neighbourSampling-papaparsing/otherAlgo.js
@@ -0,0 +1,43 @@
+/**
+ * Initialize the t-SNE algorithm and start simulation.
+*/
+function starttSNE() {
+ springForce = false;
+ simulation.stop();
+ p1 = performance.now();
+
+ simulation
+ .alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
+ .force(forceName, d3.tSNE()
+ // Set the parameter for the algorithm (optional).
+ .perplexity(PERPLEXITY)
+ .learningRate(LEARNING_RATE)
+ // The distance function that will be used to calculate distances
+ // between nodes.
+ .distance(function (s, t) {
+ return distanceFunction(s, t, props, norm) * MULTIPLIER;
+ }));
+ // Restart the simulation.
+ console.log(simulation.force(forceName).perplexity(), simulation.force(forceName).learningRate());
+ simulation.alpha(1).restart();
+}
+
+/**
+ * Initialize the Barnes-Hut algorithm and start simulation.
+*/
+function startBarnesHutSimulation() {
+ springForce = false;
+ simulation.stop();
+ p1 = performance.now();
+
+ simulation
+ .alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
+ .force(forceName, d3.forceBarnesHut()
+ // The distance function that will be used to calculate distances
+ // between nodes.
+ .distance(function (s, t) {
+ return distanceFunction(s, t, props, norm) * MULTIPLIER;
+ }));
+ // Restart the simulation.
+ simulation.alpha(1).restart();
+}
diff --git a/src/hybridSimulation.js b/src/hybridSimulation.js
index 98c0c69..d620e25 100644
--- a/src/hybridSimulation.js
+++ b/src/hybridSimulation.js
@@ -1,8 +1,9 @@
import { dispatch } from "d3-dispatch";
import constant from "./constant";
-import interpolation from "./interpolation";
-import interpolationPivots from "./interpolationPivots";
+import interpBruteForce from "./interpolation/interpBruteForce";
+import interpolationPivots from "./interpolation/interpolationPivots";
import neighbourSamplingDistance from "./neighbourSamplingDistance";
+import { takeSampleFrom } from "./interpolation/helpers";
export default function (nodes, config) {
@@ -21,10 +22,9 @@ export default function (nodes, config) {
NUMPIVOTS = config.hasOwnProperty("numPivots") ? config.numPivots : 3,
event = d3.dispatch("sampleTick", "fullTick", "startFull", "end");
- var sets = sampleFromNodes(nodes, Math.sqrt(nodes.length));
+ var sets = takeSampleFrom(nodes, Math.sqrt(nodes.length));
var sample = sets.sample;
var remainder = sets.remainder;
- var sampleSubset = sampleFromNodes(sample, Math.sqrt(sample.length)).sample;
var sampleSimulation = d3.forceSimulation(sample)
.stop()
@@ -46,15 +46,19 @@ export default function (nodes, config) {
.alpha(1).restart();
function ended() {
+ sample.forEach(function (d) {
+ d.vx = 0;
+ d.vy = 0;
+ });
event.call("startFull");
console.log("Ended sample simulation");
alert('About to interpolate');
- interpolation(sample, remainder, sampleSubset, distanceFn);
+ interpBruteForce(sample, remainder, distanceFn);
/*
if (PIVOTS) {
interpolationPivots(sample, remainder, sampleSubSet, NUMPIVOTS, distance);
} else {
- interpolation(sample, remainder, sampleSubSet, distance);
+ interpBruteForce(sample, remainder, sampleSubSet, distance);
}
*/
event.call("fullTick");
diff --git a/src/interpolation.js b/src/interpolation.js
deleted file mode 100644
index 9b7146d..0000000
--- a/src/interpolation.js
+++ /dev/null
@@ -1,153 +0,0 @@
-export default function(sampleSet, remainderSet, sampleSubset, distanceFn) {
- // var distance = calculateEuclideanDistance;
- // console.log("Brute-force");
- for (let node of remainderSet) {
- let nearestSample = undefined,
- minDist = undefined,
- sampleSubsetDistanceCache = [];
-
- for (let sample of sampleSet) {
- let dist = distanceFn(node, sample);
- if (nearestSample === undefined || dist < minDist) {
- minDist = dist;
- nearestSample = sample;
- }
-
- let index = sampleSubset.indexOf(sample);
- if (index !== -1) {
- sampleSubsetDistanceCache[index] = dist;
- }
- }
-
- placeNearToNearestNeighbour(node, nearestSample, minDist, sampleSubset, sampleSubsetDistanceCache);
- }
-}
-
-
-function placeNearToNearestNeighbour(node, minNode, radius, sampleSubset, realDistances) {
- let
- dist0 = sumDistError(pointOnCircle(minNode.x, minNode.y, 0, radius), sampleSubset, realDistances),
- dist90 = sumDistError(pointOnCircle(minNode.x, minNode.y, 90, radius), sampleSubset, realDistances),
- dist180 = sumDistError(pointOnCircle(minNode.x, minNode.y, 180, radius), sampleSubset, realDistances),
- dist270 = sumDistError(pointOnCircle(minNode.x, minNode.y, 270, radius), sampleSubset, realDistances),
- lowBound = 0.0,
- highBound = 0.0;
-
- // Determine the closest quadrant
- if (dist0 == dist180) {
- if (dist90 > dist270)
- lowBound = highBound = 270;
- else
- lowBound = highBound = 90;
- } else if (dist90 == dist270) {
- if (dist0 > dist180)
- lowBound = highBound = 180;
- else
- lowBound = highBound = 0;
- } else if (dist0 > dist180) {
- if (dist90 > dist270) {
- lowBound = 180;
- highBound = 270;
- } else {
- lowBound = 90;
- highBound = 180;
- }
- } else {
- if (dist90 > dist270) {
- lowBound = 270;
- highBound = 360;
- } else {
- lowBound = 0;
- highBound = 90;
- }
- }
-
- let angle = binarySearchMin(lowBound, highBound,
- function(angle){
- return sumDistError(pointOnCircle(minNode.x, minNode.y, angle, radius), sampleSubset, realDistances);
- });
- let newPoint = pointOnCircle(minNode.x, minNode.y, angle, radius);
-
- // console.log(newPoint);
- node.x = newPoint.x;
- node.y = newPoint.y;
-
- for (let i = 0; i < 20; i++) {
- let forces = sumForcesToSample(node, sampleSubset, realDistances);
- // console.log(forces);
- node.x += forces.x*0.5;
- node.y += forces.y*0.5;
- }
-
-}
-
-// With a circle radius r, and center at (h,k),
-// Find the coordinate of a point at angle degree
-function pointOnCircle(h, k, angle, r) {
- let x = h + r*Math.cos(toRadians(angle));
- let y = k + r*Math.sin(toRadians(angle));
-
- return {
- x: x,
- y: y
- };
-}
-
-function toRadians(degrees) {
- return degrees * (Math.PI / 180);
-}
-
-function sumDistError(currentPos, samples, realDistances) {
- let total = 0.0;
- for (let i = 0; i < samples.length; i++) {
- let sample = samples[i];
- let lowDDistance = Math.hypot(sample.x - currentPos.x, sample.y - currentPos.y);
- total += Math.abs(lowDDistance - realDistances[i]);
- }
- return total;
-}
-
-
-function sumForcesToSample(node, samples, sampleCache) {
- let nodeVx = 0,
- nodeVy = 0;
-
- for (let i = 0; i < samples.length; i++) {
- var sample = samples[i];
- if(sample === node) continue;
-
- let x = node.x - sample.x,
- y = node.y - sample.y,
- l = Math.sqrt(x * x + y * y);
-
- l = (l - sampleCache[i]) / l;
- x *= l, y *= l;
- nodeVx -= x;
- nodeVy -= y;
- }
- return {x: nodeVx, y: nodeVy};
-}
-
-function binarySearchMin(lb, hb, fn) {
- while (lb <= hb) {
- if(lb === hb) return lb;
- if(hb-lb == 1) {
- if (fn(lb) >= fn(hb)) return hb;
- else return lb;
- }
-
- let range = hb-lb;
- let valLowerHalf = fn(lb + range/4);
- let valHigherHalf = fn(lb + range*3/4);
-
- if (valLowerHalf > valHigherHalf)
- lb = Math.floor((lb + hb) / 2);
- else if (valLowerHalf < valHigherHalf)
- hb = Math.ceil((lb + hb) / 2);
- else {
- lb += Math.floor(range/4);
- hb -= Math.ceil(range/4);
- }
- }
- return -1;
-}
diff --git a/src/interpolation/helpers.js b/src/interpolation/helpers.js
new file mode 100644
index 0000000..38d9930
--- /dev/null
+++ b/src/interpolation/helpers.js
@@ -0,0 +1,48 @@
+export function takeSampleFrom(nodes, amount) {
+ let randElements = [],
+ max = nodes.length;
+
+ for (var i = 0; i < amount; ++i) {
+ var rand = nodes[Math.floor((Math.random() * max))];
+ // If the rand is already in random list or in exclude list
+ // ignore it and get a new value.
+ while (randElements.includes(rand)) {
+ rand = nodes[Math.floor((Math.random() * max))];
+ }
+ randElements.push(rand);
+ }
+ var remainder = nodes.filter(function (node) {
+ return !randElements.includes(node);
+ });
+
+ return {
+ sample: randElements,
+ remainder: remainder
+ };
+}
+
+// With a circle radius r, and center at (h,k),
+// Find the coordinate of a point at angle degree
+export function pointOnCircle(h, k, angle, r) {
+ let x = h + r*Math.cos(toRadians(angle));
+ let y = k + r*Math.sin(toRadians(angle));
+
+ return {
+ x: x,
+ y: y
+ };
+}
+
+function toRadians(degrees) {
+ return degrees * (Math.PI / 180);
+}
+
+export function sumDistError(node, samples, realDistances) {
+ let total = 0.0;
+ for (let i = 0; i < samples.length; i++) {
+ let sample = samples[i];
+ let lowDDistance = Math.hypot(sample.x - node.x, sample.y - node.y);
+ total += Math.abs(lowDDistance - realDistances[i]);
+ }
+ return total;
+}
diff --git a/src/interpolation/interpBruteForce.js b/src/interpolation/interpBruteForce.js
new file mode 100644
index 0000000..27748e7
--- /dev/null
+++ b/src/interpolation/interpBruteForce.js
@@ -0,0 +1,27 @@
+import { pointOnCircle, takeSampleFrom } from "./helpers";
+import { placeNearToNearestNeighbour } from "./interpCommon";
+
+export default function(sampleSet, remainderSet, distanceFn) {
+ // var distance = calculateEuclideanDistance;
+ // console.log("Brute-force");
+ let sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample;
+
+ for (let node of remainderSet) {
+ let nearestSample = undefined,
+ minDist = undefined,
+ sampleSubsetDistanceCache = [];
+
+ for (let sample of sampleSet) {
+ let dist = distanceFn(node, sample);
+ if (nearestSample === undefined || dist < minDist) {
+ minDist = dist;
+ nearestSample = sample;
+ }
+
+ let index = sampleSubset.indexOf(sample);
+ if (index !== -1)
+ sampleSubsetDistanceCache[index] = dist;
+ }
+ placeNearToNearestNeighbour(node, nearestSample, minDist, sampleSubset, sampleSubsetDistanceCache);
+ }
+}
diff --git a/src/interpolation/interpCommon.js b/src/interpolation/interpCommon.js
new file mode 100644
index 0000000..4966c4c
--- /dev/null
+++ b/src/interpolation/interpCommon.js
@@ -0,0 +1,102 @@
+import { pointOnCircle, sumDistError } from "./helpers";
+
+export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleSubset, realDistances) {
+ let
+ dist0 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 0, radius), sampleSubset, realDistances),
+ dist90 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 90, radius), sampleSubset, realDistances),
+ dist180 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 180, radius), sampleSubset, realDistances),
+ dist270 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 270, radius), sampleSubset, realDistances),
+ lowBound = 0.0,
+ highBound = 0.0;
+
+ // Determine the closest quadrant
+ if (dist0 == dist180) {
+ if (dist90 > dist270)
+ lowBound = highBound = 270;
+ else
+ lowBound = highBound = 90;
+ } else if (dist90 == dist270) {
+ if (dist0 > dist180)
+ lowBound = highBound = 180;
+ else
+ lowBound = highBound = 0;
+ } else if (dist0 > dist180) {
+ if (dist90 > dist270) {
+ lowBound = 180;
+ highBound = 270;
+ } else {
+ lowBound = 90;
+ highBound = 180;
+ }
+ } else {
+ if (dist90 > dist270) {
+ lowBound = 270;
+ highBound = 360;
+ } else {
+ lowBound = 0;
+ highBound = 90;
+ }
+ }
+
+ let angle = binarySearchMin(lowBound, highBound,
+ function(angle){
+ return sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, angle, radius), sampleSubset, realDistances);
+ });
+ let newPoint = pointOnCircle(nearNeighbour.x, nearNeighbour.y, angle, radius);
+
+ // console.log(newPoint);
+ node.x = newPoint.x;
+ node.y = newPoint.y;
+
+ for (let i = 0; i < 20; i++) {
+ let forces = sumForcesToSample(node, sampleSubset, realDistances);
+ //console.log(forces);
+ node.x += forces.x*0.5;
+ node.y += forces.y*0.5;
+ }
+}
+
+function sumForcesToSample(node, samples, sampleCache) {
+ let nodeVx = 0,
+ nodeVy = 0;
+
+ for (let i = 0; i < samples.length; i++) {
+ var sample = samples[i];
+ if(sample === node) continue;
+
+ let x = node.x - sample.x,
+ y = node.y - sample.y,
+ l = Math.sqrt(x * x + y * y);
+
+ l = (l - sampleCache[i]) / l;
+ x *= l, y *= l;
+ nodeVx -= x;
+ nodeVy -= y;
+ }
+ return {x: nodeVx, y: nodeVy};
+}
+
+function binarySearchMin(lb, hb, fn) {
+ while (lb <= hb) {
+ if(lb === hb) return lb;
+
+ if(hb-lb == 1) {
+ if (fn(lb) >= fn(hb)) return hb;
+ else return lb;
+ }
+
+ let range = hb-lb;
+ let valLowerHalf = fn(lb + range/4);
+ let valHigherHalf = fn(lb + range*3/4);
+
+ if (valLowerHalf > valHigherHalf)
+ lb = Math.floor((lb + hb) / 2);
+ else if (valLowerHalf < valHigherHalf)
+ hb = Math.ceil((lb + hb) / 2);
+ else {
+ lb += Math.floor(range/4);
+ hb -= Math.ceil(range/4);
+ }
+ }
+ return -1;
+}
diff --git a/src/interpolationPivots.js b/src/interpolation/interpolationPivots.js
similarity index 100%
rename from src/interpolationPivots.js
rename to src/interpolation/interpolationPivots.js
diff --git a/src/neighbourSamplingDistance.js b/src/neighbourSamplingDistance.js
index 4c20b56..9a61001 100644
--- a/src/neighbourSamplingDistance.js
+++ b/src/neighbourSamplingDistance.js
@@ -56,8 +56,10 @@ export default function () {
// for each node.
findNewNeighbours(i);
}
- velocity /= nodes.length*alpha;
- // Now total velocity changes per node, alpha not considered
+ velocity /= nodes.length*(neighbours.length+samples.length)*alpha;
+ // Now total velocity changes per link, alpha not considered
+ // TODO per property too
+ stableVelocity = 0;
if(Math.abs(velocity)