diff --git a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js index 58a59ca..40f8603 100644 --- a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js +++ b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js @@ -9,7 +9,7 @@ function startNeighbourSamplingSimulation() { simulation.stop(); p1 = performance.now(); - let force = d3.forceNeighbourSamplingDistance() + let force = d3.forceNeighbourSampling() .neighbourSize(NEIGHBOUR_SIZE) .sampleSize(SAMPLE_SIZE) .distance(function (s, t) { diff --git a/index.js b/index.js index 9636fd4..2a5b330 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,4 @@ -/* export {default as forceNeighbourSampling} - from "./src/neighbourSampling"; -export {default as forceNeighbourSamplingPre} - from "./src/neighbourSamplingPre"; -*/ -export {default as forceNeighbourSamplingDistance} from "./src/neighbourSamplingDistance"; /* export { default as forceBarnesHut} diff --git a/src/neighbourSampling.js b/src/neighbourSampling.js deleted file mode 100644 index 0b8f049..0000000 --- a/src/neighbourSampling.js +++ /dev/null @@ -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; -} diff --git a/src/neighbourSamplingPre.js b/src/neighbourSamplingPre.js deleted file mode 100644 index 82c68ae..0000000 --- a/src/neighbourSamplingPre.js +++ /dev/null @@ -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; -}