Remove other neighbour sampling
This commit is contained in:
@@ -1,257 +0,0 @@
|
||||
import constant from "./constant";
|
||||
import jiggle from "./jiggle";
|
||||
|
||||
/**
|
||||
* Set the node id accessor to the specified i.
|
||||
* @param {node} d - node.
|
||||
* @param {accessor} i - id accessor.
|
||||
* @return {accessor} - node id accessor.
|
||||
*/
|
||||
function index(d, i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of Chalmers' 1996 Neighbour and Sampling algorithm.
|
||||
* It uses random sampling to find the most suited neighbours from the
|
||||
* data set.
|
||||
* @return {force} calculated forces.
|
||||
*/
|
||||
export default function () {
|
||||
var id = index,
|
||||
neighbours = [],
|
||||
samples = new Array(),
|
||||
distance = constant(300),
|
||||
nodes,
|
||||
neighbourSize = 6,
|
||||
sampleSize = 3,
|
||||
freeness = 0.85,
|
||||
springForce = 0.7,
|
||||
dampingFactor = 0.3,
|
||||
velocity,
|
||||
multiplier = 50;
|
||||
|
||||
/**
|
||||
* Calculates the forces at each iteration between the node and the
|
||||
* objects in neighbour and sample sets.
|
||||
* @param {number} alpha - controls the stopping of the
|
||||
* particle simulations.
|
||||
*/
|
||||
function force(alpha) {
|
||||
velocity = 0;
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
// Randomize the samples for every node.
|
||||
samples[i] = randomizeSample(i);
|
||||
// Calculate the forces between node and its neighbours.
|
||||
for (var [keyN, valueN] of neighbours[i]) {
|
||||
setVelocity(i, keyN, valueN, alpha);
|
||||
}
|
||||
// Calculate the forces between node and its sample set.
|
||||
for (var [keyS, valueS] of samples[i]) {
|
||||
setVelocity(i, keyS, valueS, alpha);
|
||||
}
|
||||
// Check if there are a better neighbours in a sample array
|
||||
// for each node.
|
||||
findNewNeighbours(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the velocities of the source and target nodes.
|
||||
* @param {number} sourceId - source node id.
|
||||
* @param {number} targetId - target node id.
|
||||
* @param {number} dist - high dimensional distance between
|
||||
* the two nodes.
|
||||
* @param {number} alpha - controls the speed of simulation.
|
||||
*/
|
||||
function setVelocity(sourceId, targetId, dist, alpha) {
|
||||
var source, target, x, y, l;
|
||||
source = nodes[sourceId], target = nodes[targetId];
|
||||
// If x or y coordinates not defined, add some randomness.
|
||||
x = target.x + target.vx - source.x - source.vx || jiggle();
|
||||
y = target.y + target.vy - source.y - source.vy || jiggle();
|
||||
l = Math.sqrt(x * x + y * y);
|
||||
l = (l - dist * multiplier) / l * alpha;
|
||||
x *= l, y *= l;
|
||||
velocity += x + y;
|
||||
// Set the calculated velocites for both nodes.
|
||||
target.vx -= x;
|
||||
target.vy -= y;
|
||||
source.vx += x;
|
||||
source.vy += y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the neighbour and sample set at the start.
|
||||
*/
|
||||
function initialize() {
|
||||
if (!nodes) return;
|
||||
|
||||
// Initialize for each node a neighbour and sample arrays
|
||||
// with random values.
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
var exclude = []; // Array that keeps the indices of nodes to ignore.
|
||||
exclude.push(i);
|
||||
|
||||
var neighbs = createRandomSample(i, exclude, n, neighbourSize);
|
||||
// Sort the neighbour set by the distances.
|
||||
neighbs = new Map([...neighbs.entries()].sort(sortDistances));
|
||||
neighbours[i] = neighbs;
|
||||
|
||||
exclude.concat(neighbs);
|
||||
samples[i] = createRandomSample(i, exclude, n, sampleSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that compares to map elements by its values.
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @return {number} - 0, if values are equal, positive number if b > a,
|
||||
* negative otherwise.
|
||||
*/
|
||||
function sortDistances(a, b) {
|
||||
return b[1] - a[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of random integers, all different, with maximum
|
||||
* value max and with size elements. No elements from exlucde should
|
||||
* be included.
|
||||
* @param {number} index - index of current node.
|
||||
* @param {array} exclude - indices of the nodes to ignore.
|
||||
* @param {number} max - maximum value.
|
||||
* @param {number} size - the number of elements in map to return.
|
||||
* @return {map} - a created map that contains random elements from
|
||||
* data set.
|
||||
*/
|
||||
function createRandomSample(index, exclude, max, size) {
|
||||
var randElements = new Map();
|
||||
|
||||
for (var i = 0; i < size; ++i) {
|
||||
// Stop when no new elements can be found.
|
||||
if (randElements.size + exclude.length >= nodes.length) {
|
||||
break;
|
||||
}
|
||||
var rand = 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.has(rand) || exclude.includes(rand)) {
|
||||
rand = Math.floor((Math.random() * max));
|
||||
}
|
||||
randElements.set(rand, +distance(nodes[index], nodes[rand]));
|
||||
}
|
||||
|
||||
return randElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new map of random numbers to be used by the samples list.
|
||||
* @param {number} index - index of current node.
|
||||
* @return {map} - map that contains random elements from data set.
|
||||
*/
|
||||
function randomizeSample(index) {
|
||||
// Ignore the current neighbours of the node and itself.
|
||||
var exclude = [index];
|
||||
exclude = exclude.concat(Array.from(neighbours[index].keys()));
|
||||
return createRandomSample(index, exclude, nodes.length, sampleSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the elements from sample set to the neighbour set and
|
||||
* replaces the elements from neighbour set if better neighbours are
|
||||
* found in sample set.
|
||||
* @param {number} index - index of current node.
|
||||
*/
|
||||
function findNewNeighbours(index) {
|
||||
var sample = samples[index];
|
||||
|
||||
for (var [key, value] of sample) {
|
||||
var neighbMax = neighbours[index].entries().next().value;
|
||||
|
||||
// Check if a value from sample could be a better neighbour
|
||||
// if so, replace it.
|
||||
if (value < neighbMax[1]) {
|
||||
neighbours[index].delete(neighbMax[0]);
|
||||
neighbours[index].set(key, value)
|
||||
neighbours[index] = new Map([...neighbours[index].entries()].sort(sortDistances));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the stress. Basically, it computes the difference between
|
||||
* high dimensional distance and real distance. The lower the stress is,
|
||||
* the better layout.
|
||||
* @return {number} - stress of the layout.
|
||||
*/
|
||||
function getStress() {
|
||||
var totalDiffSq = 0, totalHighDistSq = 0;
|
||||
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
if (i !== j) {
|
||||
source = nodes[i], target = nodes[j];
|
||||
realDist = Math.hypot(target.x - source.x, target.y - source.y);
|
||||
highDist = +distance(source, target);
|
||||
totalDiffSq += Math.pow(realDist - highDist, 2);
|
||||
totalHighDistSq += highDist * highDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.sqrt(totalDiffSq / totalHighDistSq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average velocity of the force calculation at the
|
||||
* current iteration.
|
||||
* @return {number} - average velocity.
|
||||
*/
|
||||
function getAvgVelocity() {
|
||||
return velocity / ((neighbourSize + sampleSize) * nodes.length);
|
||||
}
|
||||
|
||||
// API for initializing the algorithm, setting parameters and querying
|
||||
// metrics.
|
||||
force.initialize = function (_) {
|
||||
nodes = _;
|
||||
initialize();
|
||||
};
|
||||
|
||||
force.id = function (_) {
|
||||
return arguments.length ? (id = _, force) : id;
|
||||
};
|
||||
|
||||
force.neighbourSize = function (_) {
|
||||
return arguments.length ? (neighbourSize = +_, force) : neighbourSize;
|
||||
};
|
||||
|
||||
force.sampleSize = function (_) {
|
||||
return arguments.length ? (sampleSize = +_, force) : sampleSize;
|
||||
};
|
||||
|
||||
force.distance = function (_) {
|
||||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
||||
};
|
||||
|
||||
force.stress = function () {
|
||||
return getStress();
|
||||
};
|
||||
|
||||
force.velocity = function () {
|
||||
return getAvgVelocity();
|
||||
};
|
||||
|
||||
force.freeness = function (_) {
|
||||
return arguments.length ? (freeness = +_, force) : freeness;
|
||||
};
|
||||
|
||||
force.nodeNeighbours = function (_) {
|
||||
return arguments.length ? neighbours[+_] : [];
|
||||
};
|
||||
|
||||
force.multiplier = function (_) {
|
||||
return arguments.length ? (multiplier = +_, force) : multiplier;
|
||||
};
|
||||
|
||||
return force;
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
import constant from "./constant";
|
||||
import jiggle from "./jiggle";
|
||||
|
||||
/**
|
||||
* Set the node id accessor to the specified i.
|
||||
* @param {node} d - node.
|
||||
* @param {accessor} i - id accessor.
|
||||
* @return {accessor} - node id accessor.
|
||||
*/
|
||||
function index(d, i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of Chalmers' 1996 Neighbour and Sampling algorithm.
|
||||
* It uses random sampling to find the most suited neighbours from the
|
||||
* data set.
|
||||
* @return {force} calculated forces.
|
||||
*/
|
||||
export default function () {
|
||||
var id = index,
|
||||
neighbours = [],
|
||||
worst = [],
|
||||
samples = new Array(),
|
||||
distance = constant(300),
|
||||
nodes,
|
||||
neighbourSize = 6,
|
||||
sampleSize = 3,
|
||||
freeness = 0.85,
|
||||
springForce = 0.7,
|
||||
dampingFactor = 0.3;
|
||||
|
||||
/**
|
||||
* Calculates the forces at each iteration between the node and the
|
||||
* objects in neighbour and sample sets.
|
||||
* @param {number} alpha - controls the stopping of the
|
||||
* particle simulations.
|
||||
*/
|
||||
function force(alpha) {
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
// Randomize the samples for every node.
|
||||
// Calculate the forces between node and its neighbours.
|
||||
for (var [keyN, valueN] of neighbours[i]) {
|
||||
setVelocity(i, keyN, valueN, alpha);
|
||||
}
|
||||
// Calculate the forces between node and its sample set.
|
||||
// for (var [keyS, valueS] of worst[i]) {
|
||||
// setVelocity(i, keyS, valueS, alpha);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the velocities of the source and target nodes.
|
||||
* @param {number} sourceId - source node id.
|
||||
* @param {number} targetId - target node id.
|
||||
* @param {number} dist - high dimensional distance between
|
||||
* the two nodes.
|
||||
* @param {number} alpha - controls the speed of simulation.
|
||||
*/
|
||||
function setVelocity(sourceId, targetId, dist, alpha) {
|
||||
var source, target, x, y, l;
|
||||
source = nodes[sourceId], target = nodes[targetId];
|
||||
// If x or y coordinates not defined, add some randomness.
|
||||
x = target.x + target.vx - source.x - source.vx || jiggle();
|
||||
y = target.y + target.vy - source.y - source.vy || jiggle();
|
||||
l = Math.sqrt(x * x + y * y);
|
||||
l = (l - dist) / l * alpha;
|
||||
x *= l, y *= l;
|
||||
// Set the calculated velocites for both nodes.
|
||||
target.vx -= x;
|
||||
target.vy -= y;
|
||||
source.vx += x;
|
||||
source.vy += y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the neighbour and sample set at the start.
|
||||
*/
|
||||
function initialize() {
|
||||
if (!nodes) return;
|
||||
|
||||
findNeighbours();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that compares to map elements by its values.
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @return {number} - 0, if values are equal, positive number if b > a,
|
||||
* negative otherwise.
|
||||
*/
|
||||
function sortDistances(a, b) {
|
||||
return b[1] - a[1];
|
||||
}
|
||||
|
||||
|
||||
function findNeighbours() {
|
||||
// Initialize for each node a neighbour and sample arrays
|
||||
// with random values.
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
neighbours[i] = new Map();
|
||||
for (var j = 0; j < n; j++) {
|
||||
if (i !== j) {
|
||||
var dist = +distance(nodes[i], nodes[j]);
|
||||
|
||||
if (neighbours[i].size < neighbourSize) {
|
||||
neighbours[i].set(j, dist);
|
||||
neighbours[i] = new Map([...neighbours[i].entries()].sort(sortDistances));
|
||||
} else {
|
||||
var neighbMax = neighbours[i].entries().next().value;
|
||||
|
||||
if (dist < neighbMax[1]) {
|
||||
neighbours[i].delete(neighbMax[0]);
|
||||
neighbours[i].set(j, dist);
|
||||
neighbours[i] = new Map([...neighbours[i].entries()].sort(sortDistances));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the stress. Basically, it computes the difference between
|
||||
* high dimensional distance and real distance. The lower the stress is,
|
||||
* the better layout.
|
||||
* @return {number} - stress of the layout.
|
||||
*/
|
||||
function getStress() {
|
||||
var totalDiffSq = 0, totalHighDistSq = 0;
|
||||
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
if (i !== j) {
|
||||
source = nodes[i], target = nodes[j];
|
||||
realDist = Math.hypot(target.x - source.x, target.y - source.y);
|
||||
highDist = +distance(source, target);
|
||||
totalDiffSq += Math.pow(realDist - highDist, 2);
|
||||
totalHighDistSq += highDist * highDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.sqrt(totalDiffSq / totalHighDistSq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average velocity of the force calculation at the
|
||||
* current iteration.
|
||||
* @return {number} - average velocity.
|
||||
*/
|
||||
function getAvgVelocity() {
|
||||
return velocity / ((neighbourSize + sampleSize) * nodes.length);
|
||||
}
|
||||
|
||||
// API for initializing the algorithm, setting parameters and querying
|
||||
// metrics.
|
||||
force.initialize = function (_) {
|
||||
nodes = _;
|
||||
initialize();
|
||||
};
|
||||
|
||||
force.id = function (_) {
|
||||
return arguments.length ? (id = _, force) : id;
|
||||
};
|
||||
|
||||
force.neighbourSize = function (_) {
|
||||
return arguments.length ? (neighbourSize = +_, force) : neighbourSize;
|
||||
};
|
||||
|
||||
force.sampleSize = function (_) {
|
||||
return arguments.length ? (sampleSize = +_, force) : sampleSize;
|
||||
};
|
||||
|
||||
force.distance = function (_) {
|
||||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
||||
};
|
||||
|
||||
force.stress = function () {
|
||||
return getStress();
|
||||
};
|
||||
|
||||
force.velocity = function () {
|
||||
return getAvgVelocity();
|
||||
};
|
||||
|
||||
force.freeness = function (_) {
|
||||
return arguments.length ? (freeness = +_, force) : freeness;
|
||||
};
|
||||
|
||||
force.nodeNeighbours = function (_) {
|
||||
return arguments.length ? neighbours[+_] : [];
|
||||
};
|
||||
|
||||
return force;
|
||||
}
|
||||
Reference in New Issue
Block a user