Refactor: Extract functions
This commit is contained in:
@@ -129,7 +129,7 @@
|
|||||||
<div class="left">
|
<div class="left">
|
||||||
<p>Select algorithm:</p>
|
<p>Select algorithm:</p>
|
||||||
<div id="algorithms">
|
<div id="algorithms">
|
||||||
<input id="HLButton" type="radio" name="algorithm" onclick="d3.select('#startSimulation').on('click', startHybridSimulation)">Hybrid
|
<input id="HLButton" type="radio" name="algorithm" checked onclick="d3.select('#startSimulation').on('click', startHybridSimulation)">Hybrid
|
||||||
layout
|
layout
|
||||||
<br>
|
<br>
|
||||||
<div id="HLParameters" class="parameters" style="display:none">
|
<div id="HLParameters" class="parameters" style="display:none">
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
<div class="left">
|
<div class="left">
|
||||||
<p>Select distance function:</p>
|
<p>Select distance function:</p>
|
||||||
<div id="distance">
|
<div id="distance">
|
||||||
<input type="radio" name="distance" onclick="distanceFunction=calculateDistance"> General<br>
|
<input type="radio" name="distance" checked onclick="distanceFunction=calculateDistance"> General<br>
|
||||||
<input type="radio" name="distance" onclick="distanceFunction=calculateEuclideanDistance"> Euclidean<br>
|
<input type="radio" name="distance" onclick="distanceFunction=calculateEuclideanDistance"> Euclidean<br>
|
||||||
<input type="radio" name="distance" onclick="distanceFunction=calculateManhattanDistance"> Manhattan<br>
|
<input type="radio" name="distance" onclick="distanceFunction=calculateManhattanDistance"> Manhattan<br>
|
||||||
<input type="radio" name="distance" onclick="distanceFunction=calculateJaccardDissimilarity"> Jaccard<br>
|
<input type="radio" name="distance" onclick="distanceFunction=calculateJaccardDissimilarity"> Jaccard<br>
|
||||||
@@ -252,6 +252,9 @@
|
|||||||
<script src="js/lib/intercom.js"></script>
|
<script src="js/lib/intercom.js"></script>
|
||||||
<script src="../build/d3-neighbour-sampling.js"></script>
|
<script src="../build/d3-neighbour-sampling.js"></script>
|
||||||
<script src="js/src/neighbourSampling-papaparsing.js"></script>
|
<script src="js/src/neighbourSampling-papaparsing.js"></script>
|
||||||
|
<script src="js/src/neighbourSampling-papaparsing/hybrid.js"></script>
|
||||||
|
<script src="js/src/neighbourSampling-papaparsing/linkForce.js"></script>
|
||||||
|
<script src="js/src/neighbourSampling-papaparsing/neighbourSampling.js"></script>
|
||||||
<script src="js/distances/distancePokerHands.js"></script>
|
<script src="js/distances/distancePokerHands.js"></script>
|
||||||
<script src="js/distances/distance.js"></script>
|
<script src="js/distances/distance.js"></script>
|
||||||
<script src="js/distances/euclideanDistance.js"></script>
|
<script src="js/distances/euclideanDistance.js"></script>
|
||||||
|
|||||||
43
examples/js/src/neighbourSampling-papaparsing/hybrid.js
Normal file
43
examples/js/src/neighbourSampling-papaparsing/hybrid.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
examples/js/src/neighbourSampling-papaparsing/linkForce.js
Normal file
40
examples/js/src/neighbourSampling-papaparsing/linkForce.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
43
examples/js/src/neighbourSampling-papaparsing/otherAlgo.js
Normal file
43
examples/js/src/neighbourSampling-papaparsing/otherAlgo.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { dispatch } from "d3-dispatch";
|
import { dispatch } from "d3-dispatch";
|
||||||
import constant from "./constant";
|
import constant from "./constant";
|
||||||
import interpolation from "./interpolation";
|
import interpBruteForce from "./interpolation/interpBruteForce";
|
||||||
import interpolationPivots from "./interpolationPivots";
|
import interpolationPivots from "./interpolation/interpolationPivots";
|
||||||
import neighbourSamplingDistance from "./neighbourSamplingDistance";
|
import neighbourSamplingDistance from "./neighbourSamplingDistance";
|
||||||
|
import { takeSampleFrom } from "./interpolation/helpers";
|
||||||
|
|
||||||
export default function (nodes, config) {
|
export default function (nodes, config) {
|
||||||
|
|
||||||
@@ -21,10 +22,9 @@ export default function (nodes, config) {
|
|||||||
NUMPIVOTS = config.hasOwnProperty("numPivots") ? config.numPivots : 3,
|
NUMPIVOTS = config.hasOwnProperty("numPivots") ? config.numPivots : 3,
|
||||||
event = d3.dispatch("sampleTick", "fullTick", "startFull", "end");
|
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 sample = sets.sample;
|
||||||
var remainder = sets.remainder;
|
var remainder = sets.remainder;
|
||||||
var sampleSubset = sampleFromNodes(sample, Math.sqrt(sample.length)).sample;
|
|
||||||
|
|
||||||
var sampleSimulation = d3.forceSimulation(sample)
|
var sampleSimulation = d3.forceSimulation(sample)
|
||||||
.stop()
|
.stop()
|
||||||
@@ -46,15 +46,19 @@ export default function (nodes, config) {
|
|||||||
.alpha(1).restart();
|
.alpha(1).restart();
|
||||||
|
|
||||||
function ended() {
|
function ended() {
|
||||||
|
sample.forEach(function (d) {
|
||||||
|
d.vx = 0;
|
||||||
|
d.vy = 0;
|
||||||
|
});
|
||||||
event.call("startFull");
|
event.call("startFull");
|
||||||
console.log("Ended sample simulation");
|
console.log("Ended sample simulation");
|
||||||
alert('About to interpolate');
|
alert('About to interpolate');
|
||||||
interpolation(sample, remainder, sampleSubset, distanceFn);
|
interpBruteForce(sample, remainder, distanceFn);
|
||||||
/*
|
/*
|
||||||
if (PIVOTS) {
|
if (PIVOTS) {
|
||||||
interpolationPivots(sample, remainder, sampleSubSet, NUMPIVOTS, distance);
|
interpolationPivots(sample, remainder, sampleSubSet, NUMPIVOTS, distance);
|
||||||
} else {
|
} else {
|
||||||
interpolation(sample, remainder, sampleSubSet, distance);
|
interpBruteForce(sample, remainder, sampleSubSet, distance);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
event.call("fullTick");
|
event.call("fullTick");
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
48
src/interpolation/helpers.js
Normal file
48
src/interpolation/helpers.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
27
src/interpolation/interpBruteForce.js
Normal file
27
src/interpolation/interpBruteForce.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/interpolation/interpCommon.js
Normal file
102
src/interpolation/interpCommon.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -56,8 +56,10 @@ export default function () {
|
|||||||
// for each node.
|
// for each node.
|
||||||
findNewNeighbours(i);
|
findNewNeighbours(i);
|
||||||
}
|
}
|
||||||
velocity /= nodes.length*alpha;
|
velocity /= nodes.length*(neighbours.length+samples.length)*alpha;
|
||||||
// Now total velocity changes per node, alpha not considered
|
// Now total velocity changes per link, alpha not considered
|
||||||
|
// TODO per property too
|
||||||
|
stableVelocity = 0;
|
||||||
if(Math.abs(velocity)<stableVelocity && stableVeloHandler!== null){
|
if(Math.abs(velocity)<stableVelocity && stableVeloHandler!== null){
|
||||||
console.log("Neighbour sampling is now", velocity, ", calling stableVeloHandler().")
|
console.log("Neighbour sampling is now", velocity, ", calling stableVeloHandler().")
|
||||||
stableVeloHandler();
|
stableVeloHandler();
|
||||||
|
|||||||
Reference in New Issue
Block a user