แก้ coding style
This commit is contained in:
310
src/barnesHut.js
310
src/barnesHut.js
@@ -1,158 +1,152 @@
|
|||||||
import constant from "./constant";
|
import constant from "./constant";
|
||||||
import jiggle from "./jiggle";
|
import jiggle from "./jiggle";
|
||||||
import {x, y} from "./xy";
|
import {x, y} from "./xy";
|
||||||
import {quadtree} from "d3-quadtree";
|
import {quadtree} from "d3-quadtree";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The refinement of the existing Barnes-Hut implementation in D3
|
* The refinement of the existing Barnes-Hut implementation in D3
|
||||||
* to fit the use case of the project. Previously the algorithm stored
|
* to fit the use case of the project. Previously the algorithm stored
|
||||||
* strength as internal node, now the random child is stored as internal
|
* strength as internal node, now the random child is stored as internal
|
||||||
* node and the force calculations are done between the node and that internal
|
* node and the force calculations are done between the node and that internal
|
||||||
* object if they are sufficiently far away.
|
* object if they are sufficiently far away.
|
||||||
* The check to see if the nodes are far away was also changed to the one described in original Barnes-Hut paper.
|
* The check to see if the nodes are far away was also changed to the one described in original Barnes-Hut paper.
|
||||||
* @return {force} calculated forces.
|
* @return {force} calculated forces.
|
||||||
*/
|
*/
|
||||||
export default function() {
|
export default function() {
|
||||||
var nodes,
|
var nodes,
|
||||||
node,
|
node,
|
||||||
alpha,
|
alpha,
|
||||||
distance = constant(300),
|
distance = constant(300),
|
||||||
theta = 0.5;
|
theta = 0.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a quadtree at every iteration and apply the forces by visiting
|
* Constructs a quadtree at every iteration and apply the forces by visiting
|
||||||
* each node in a tree.
|
* each node in a tree.
|
||||||
* @param {number} _ - controls the stopping of the
|
* @param {number} _ - controls the stopping of the
|
||||||
* particle simulations.
|
* particle simulations.
|
||||||
*/
|
*/
|
||||||
function force(_) {
|
function force(_) {
|
||||||
var i, n = nodes.length, tree = quadtree(nodes, x, y).visitAfter(accumulate);
|
var i, n = nodes.length, tree = quadtree(nodes, x, y).visitAfter(accumulate);
|
||||||
for (alpha = _, i = 0; i < n; ++i) {
|
for (alpha = _, i = 0; i < n; ++i) {
|
||||||
node = nodes[i], tree.visit(apply);
|
node = nodes[i], tree.visit(apply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function used during the tree construction to fill out the nodes with
|
* Function used during the tree construction to fill out the nodes with
|
||||||
* correct data. Internal nodes acquire the random child while the leaf
|
* correct data. Internal nodes acquire the random child while the leaf
|
||||||
* nodes accumulate forces from coincident quadrants.
|
* nodes accumulate forces from coincident quadrants.
|
||||||
* @param {quadrant} quad - node representing the quadrant in quadtree.
|
* @param {quadrant} quad - node representing the quadrant in quadtree.
|
||||||
*/
|
*/
|
||||||
function accumulate(quad) {
|
function accumulate(quad) {
|
||||||
var q, d, children = [];
|
var q, d, children = [];
|
||||||
|
|
||||||
// For internal nodes, accumulate forces from child quadrants.
|
// For internal nodes, accumulate forces from child quadrants.
|
||||||
if (quad.length) {
|
if (quad.length) {
|
||||||
for (var i = 0; i < 4; ++i) {
|
for (var i = 0; i < 4; ++i) {
|
||||||
if ((q = quad[i]) && (d = q.data)) {
|
if ((q = quad[i]) && (d = q.data)) {
|
||||||
children.push(d);
|
children.push(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Choose a random child.
|
// Choose a random child.
|
||||||
quad.data = children[Math.floor(Math.random() * children.length)];
|
quad.data = children[Math.floor(Math.random() * children.length)];
|
||||||
quad.x = quad.data.x;
|
quad.x = quad.data.x;
|
||||||
quad.y = quad.data.y;
|
quad.y = quad.data.y;
|
||||||
}
|
} else { // For leaf nodes, accumulate forces from coincident quadrants.
|
||||||
|
q = quad;
|
||||||
// For leaf nodes, accumulate forces from coincident quadrants.
|
q.x = q.data.x;
|
||||||
else {
|
q.y = q.data.y;
|
||||||
q = quad;
|
}
|
||||||
q.x = q.data.x;
|
}
|
||||||
q.y = q.data.y;
|
|
||||||
}
|
/**
|
||||||
}
|
* Function that applies the forces for each node. If the objects are
|
||||||
|
* far away, the approximation is made. Otherwise, forces are calculated
|
||||||
/**
|
* directly between the nodes.
|
||||||
* Function that applies the forces for each node. If the objects are
|
* @param {quadrant} quad - node representing the quadrant in quadtree.
|
||||||
* far away, the approximation is made. Otherwise, forces are calculated
|
* @param {number} x1 - lower x bound of the node.
|
||||||
* directly between the nodes.
|
* @param {number} _ - lower y bound of the node.
|
||||||
* @param {quadrant} quad - node representing the quadrant in quadtree.
|
* @param {number} x2 - upper x bound of the node.
|
||||||
* @param {number} x1 - lower x bound of the node.
|
* @return {boolean} - true if the approximation was applied.
|
||||||
* @param {number} _ - lower y bound of the node.
|
*/
|
||||||
* @param {number} x2 - upper x bound of the node.
|
function apply(quad, x1, _, x2) {
|
||||||
* @return {boolean} - true if the approximation was applied.
|
|
||||||
*/
|
var x = quad.data.x + quad.data.vx - node.x - node.vx,
|
||||||
function apply(quad, x1, _, x2) {
|
y = quad.data.y + quad.data.vy - node.y - node.vy,
|
||||||
|
w = x2 - x1,
|
||||||
var x = quad.data.x + quad.data.vx - node.x - node.vx,
|
l = Math.sqrt(x * x + y * y);
|
||||||
y = quad.data.y + quad.data.vy - node.y - node.vy,
|
|
||||||
w = x2 - x1,
|
// Apply the Barnes-Hut approximation if possible.
|
||||||
l = Math.sqrt(x * x + y * y);
|
// Limit forces for very close nodes; randomize direction if coincident.
|
||||||
|
if (w / l < theta) {
|
||||||
// Apply the Barnes-Hut approximation if possible.
|
if (x === 0) x = jiggle(), l += x * x;
|
||||||
// Limit forces for very close nodes; randomize direction if coincident.
|
if (y === 0) y = jiggle(), l += y * y;
|
||||||
if (w / l < theta) {
|
if (quad.data) {
|
||||||
if (x === 0) x = jiggle(), l += x * x;
|
l = (l - +distance(node, quad.data)) / l * alpha;
|
||||||
if (y === 0) y = jiggle(), l += y * y;
|
x *= l, y *= l;
|
||||||
if (quad.data) {
|
quad.data.vx -= x;
|
||||||
l = (l - +distance(node, quad.data)) / l * alpha;
|
quad.data.vy -= y;
|
||||||
x *= l, y *= l;
|
node.vx += x;
|
||||||
quad.data.vx -= x;
|
node.vy += y;
|
||||||
quad.data.vy -= y;
|
}
|
||||||
node.vx += x;
|
return true;
|
||||||
node.vy += y;
|
} else if (quad.length) return; // Otherwise, process points directly.
|
||||||
}
|
|
||||||
return true;
|
// Limit forces for very close nodes; randomize direction if coincident.
|
||||||
}
|
if (quad.data !== node || quad.next) {
|
||||||
|
if (x === 0) x = jiggle(), l += x * x;
|
||||||
// Otherwise, process points directly.
|
if (y === 0) y = jiggle(), l += y * y;
|
||||||
else if (quad.length) return;
|
}
|
||||||
|
|
||||||
// Limit forces for very close nodes; randomize direction if coincident.
|
do if (quad.data !== node) {
|
||||||
if (quad.data !== node || quad.next) {
|
l = (l - +distance(node, quad.data)) / l * alpha;
|
||||||
if (x === 0) x = jiggle(), l += x * x;
|
x *= l, y *= l;
|
||||||
if (y === 0) y = jiggle(), l += y * y;
|
quad.data.vx -= x;
|
||||||
}
|
quad.data.vy -= y;
|
||||||
|
node.vx += x;
|
||||||
do if (quad.data !== node) {
|
node.vy += y;
|
||||||
l = (l - +distance(node, quad.data)) / l * alpha;
|
} while (quad = quad.next);
|
||||||
x *= l, y *= l;
|
}
|
||||||
quad.data.vx -= x;
|
|
||||||
quad.data.vy -= y;
|
/**
|
||||||
node.vx += x;
|
* Calculates the stress. Basically, it computes the difference between
|
||||||
node.vy += y;
|
* high dimensional distance and real distance. The lower the stress is,
|
||||||
} while (quad = quad.next);
|
* the better layout.
|
||||||
}
|
* @return {number} - stress of the layout.
|
||||||
|
*/
|
||||||
/**
|
function getStress() {
|
||||||
* Calculates the stress. Basically, it computes the difference between
|
var totalDiffSq = 0, totalHighDistSq = 0;
|
||||||
* high dimensional distance and real distance. The lower the stress is,
|
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
||||||
* the better layout.
|
for (var j = 0; j < nodes.length; j++) {
|
||||||
* @return {number} - stress of the layout.
|
if (i !== j) {
|
||||||
*/
|
source = nodes[i], target = nodes[j];
|
||||||
function getStress() {
|
realDist = Math.hypot(target.x-source.x, target.y-source.y);
|
||||||
var totalDiffSq = 0, totalHighDistSq = 0;
|
highDist = +distance(nodes[i], nodes[j]);
|
||||||
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
totalDiffSq += Math.pow(realDist-highDist, 2);
|
||||||
for (var j = 0; j < nodes.length; j++) {
|
totalHighDistSq += highDist * highDist;
|
||||||
if (i !== j) {
|
}
|
||||||
source = nodes[i], target = nodes[j];
|
}
|
||||||
realDist = Math.hypot(target.x-source.x, target.y-source.y);
|
}
|
||||||
highDist = +distance(nodes[i], nodes[j]);
|
return Math.sqrt(totalDiffSq/totalHighDistSq);
|
||||||
totalDiffSq += Math.pow(realDist-highDist, 2);
|
}
|
||||||
totalHighDistSq += highDist * highDist;
|
|
||||||
}
|
// API for initializing the algorithm, setting parameters and querying
|
||||||
}
|
// metrics.
|
||||||
}
|
force.initialize = function(_) {
|
||||||
return Math.sqrt(totalDiffSq/totalHighDistSq);
|
nodes = _;
|
||||||
}
|
};
|
||||||
|
|
||||||
// API for initializing the algorithm, setting parameters and querying
|
force.distance = function(_) {
|
||||||
// metrics.
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
||||||
force.initialize = function(_) {
|
};
|
||||||
nodes = _;
|
|
||||||
};
|
force.theta = function(_) {
|
||||||
|
return arguments.length ? (theta = _, force) : theta;
|
||||||
force.distance = function(_) {
|
};
|
||||||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
|
||||||
};
|
force.stress = function() {
|
||||||
|
return getStress();
|
||||||
force.theta = function(_) {
|
};
|
||||||
return arguments.length ? (theta = _, force) : theta;
|
|
||||||
};
|
return force;
|
||||||
|
}
|
||||||
force.stress = function() {
|
|
||||||
return getStress();
|
|
||||||
};
|
|
||||||
|
|
||||||
return force;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,203 +1,203 @@
|
|||||||
import {dispatch} from "d3-dispatch";
|
import {dispatch} from "d3-dispatch";
|
||||||
import constant from "./constant";
|
import constant from "./constant";
|
||||||
import interpBruteForce from "./interpolation/interpBruteForce";
|
import interpBruteForce from "./interpolation/interpBruteForce";
|
||||||
import interpolationPivots from "./interpolation/interpolationPivots";
|
import interpolationPivots from "./interpolation/interpolationPivots";
|
||||||
import {takeSampleFrom} from "./interpolation/helpers";
|
import {takeSampleFrom} from "./interpolation/helpers";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of Chalmers, Morrison, and Ross' 2002 hybrid layout
|
* An implementation of Chalmers, Morrison, and Ross' 2002 hybrid layout
|
||||||
* algorithm with an option to use the 2003 pivot-based near neighbour searching
|
* algorithm with an option to use the 2003 pivot-based near neighbour searching
|
||||||
* method.
|
* method.
|
||||||
* It performs the 1996 neighbour sampling spring simulation model, on a
|
* It performs the 1996 neighbour sampling spring simulation model, on a
|
||||||
* "sample set", sqrt(n) samples of the data.
|
* "sample set", sqrt(n) samples of the data.
|
||||||
* Other data points are then interpolated into the model.
|
* Other data points are then interpolated into the model.
|
||||||
* Finally, another spring simulation may be performed on the entire dataset to
|
* Finally, another spring simulation may be performed on the entire dataset to
|
||||||
* clean up the model.
|
* clean up the model.
|
||||||
* @param {object} sim - D3 Simulation object
|
* @param {object} sim - D3 Simulation object
|
||||||
* @param {object} forceS - Pre-configured D3 force object for the sample set.
|
* @param {object} forceS - Pre-configured D3 force object for the sample set.
|
||||||
The ending handler will be re-configured.
|
The ending handler will be re-configured.
|
||||||
Neighbour sampling force is expected, but other D3
|
Neighbour sampling force is expected, but other D3
|
||||||
forces may also work.
|
forces may also work.
|
||||||
* @param {object} forceF - Pre-configured D3 force object for the simultion ran
|
* @param {object} forceF - Pre-configured D3 force object for the simultion ran
|
||||||
on the entire dataset at the end.
|
on the entire dataset at the end.
|
||||||
Neighbour sampling force is expected, but other D3
|
Neighbour sampling force is expected, but other D3
|
||||||
forces may also work.
|
forces may also work.
|
||||||
The force should not have any ending condition.
|
The force should not have any ending condition.
|
||||||
*/
|
*/
|
||||||
export default function (sim, forceS, forceF) {
|
export default function (sim, forceS, forceF) {
|
||||||
var
|
var
|
||||||
SAMPLE_ITERATIONS = 300,
|
SAMPLE_ITERATIONS = 300,
|
||||||
FULL_ITERATIONS = 20,
|
FULL_ITERATIONS = 20,
|
||||||
interpDistanceFn,
|
interpDistanceFn,
|
||||||
NUM_PIVOTS = 0,
|
NUM_PIVOTS = 0,
|
||||||
INTERP_FINE_ITS = 20,
|
INTERP_FINE_ITS = 20,
|
||||||
sample = [],
|
sample = [],
|
||||||
remainder = [],
|
remainder = [],
|
||||||
simulation = sim,
|
simulation = sim,
|
||||||
forceSample = forceS,
|
forceSample = forceS,
|
||||||
forceFull = forceF,
|
forceFull = forceF,
|
||||||
event = d3.dispatch("sampleTick", "fullTick", "startInterp", "end"),
|
event = d3.dispatch("sampleTick", "fullTick", "startInterp", "end"),
|
||||||
initAlready = false,
|
initAlready = false,
|
||||||
nodes,
|
nodes,
|
||||||
alreadyRanIterations,
|
alreadyRanIterations,
|
||||||
hybrid;
|
hybrid;
|
||||||
|
|
||||||
if(simulation != undefined) initSimulation();
|
if(simulation != undefined) initSimulation();
|
||||||
if(forceS != undefined || forceF != undefined) initForces();
|
if(forceS != undefined || forceF != undefined) initForces();
|
||||||
|
|
||||||
// Performed on first run
|
// Performed on first run
|
||||||
function initialize() {
|
function initialize() {
|
||||||
initAlready = true;
|
initAlready = true;
|
||||||
alreadyRanIterations = 0;
|
alreadyRanIterations = 0;
|
||||||
simulation
|
simulation
|
||||||
.on("tick", sampleTick)
|
.on("tick", sampleTick)
|
||||||
.on("end", sampleEnded)
|
.on("end", sampleEnded)
|
||||||
.nodes(sample)
|
.nodes(sample)
|
||||||
.force("Sample force", forceSample);
|
.force("Sample force", forceSample);
|
||||||
console.log("Initialized Simulation for Hybrid");
|
console.log("Initialized Simulation for Hybrid");
|
||||||
}
|
}
|
||||||
|
|
||||||
function initForces(){
|
function initForces(){
|
||||||
if (forceSample.onStableVelo) {
|
if (forceSample.onStableVelo) {
|
||||||
forceSample.onStableVelo(sampleEnded);
|
forceSample.onStableVelo(sampleEnded);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceFull.onStableVelo) {
|
if (forceFull.onStableVelo) {
|
||||||
forceFull.onStableVelo(fullEnded);
|
forceFull.onStableVelo(fullEnded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default value for interpDistanceFn if not been specified yet
|
// Set default value for interpDistanceFn if not been specified yet
|
||||||
if(interpDistanceFn === undefined) {
|
if(interpDistanceFn === undefined) {
|
||||||
if(forceFull.distance == 'function')
|
if(forceFull.distance == 'function')
|
||||||
interpDistanceFn = forceFull.distance();
|
interpDistanceFn = forceFull.distance();
|
||||||
else
|
else
|
||||||
interpDistanceFn = constant(300);
|
interpDistanceFn = constant(300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSimulation(){
|
function initSimulation(){
|
||||||
nodes = simulation.nodes();
|
nodes = simulation.nodes();
|
||||||
simulation
|
simulation
|
||||||
.stop()
|
.stop()
|
||||||
.alphaDecay(0)
|
.alphaDecay(0)
|
||||||
.alpha(1)
|
.alpha(1)
|
||||||
|
|
||||||
let sets = takeSampleFrom(nodes, Math.sqrt(nodes.length));
|
let sets = takeSampleFrom(nodes, Math.sqrt(nodes.length));
|
||||||
sample = sets.sample;
|
sample = sets.sample;
|
||||||
remainder = sets.remainder;
|
remainder = sets.remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sample simulation ticked 1 frame, keep track of number of iterations here.
|
// Sample simulation ticked 1 frame, keep track of number of iterations here.
|
||||||
function sampleTick() {
|
function sampleTick() {
|
||||||
event.call("sampleTick");
|
event.call("sampleTick");
|
||||||
if(alreadyRanIterations++ >= SAMPLE_ITERATIONS){
|
if(alreadyRanIterations++ >= SAMPLE_ITERATIONS){
|
||||||
sampleEnded();
|
sampleEnded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full simulation ticked 1 frame, keep track of number of iterations here.
|
// Full simulation ticked 1 frame, keep track of number of iterations here.
|
||||||
function fullTick() {
|
function fullTick() {
|
||||||
event.call("fullTick");
|
event.call("fullTick");
|
||||||
if(alreadyRanIterations++ >= FULL_ITERATIONS){
|
if(alreadyRanIterations++ >= FULL_ITERATIONS){
|
||||||
fullEnded();
|
fullEnded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fullEnded() {
|
function fullEnded() {
|
||||||
simulation.stop();
|
simulation.stop();
|
||||||
initAlready = false;
|
initAlready = false;
|
||||||
simulation.force("Full force", null);
|
simulation.force("Full force", null);
|
||||||
event.call("end");
|
event.call("end");
|
||||||
}
|
}
|
||||||
|
|
||||||
function sampleEnded() {
|
function sampleEnded() {
|
||||||
simulation.stop();
|
simulation.stop();
|
||||||
simulation.force("Sample force", null);
|
simulation.force("Sample force", null);
|
||||||
// Reset velocity of all nodes
|
// Reset velocity of all nodes
|
||||||
for (let i=sample.length-1; i>=0; i--){
|
for (let i=sample.length-1; i>=0; i--){
|
||||||
sample[i].vx=0;
|
sample[i].vx=0;
|
||||||
sample[i].vy=0;
|
sample[i].vy=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.call("startInterp");
|
event.call("startInterp");
|
||||||
if (NUM_PIVOTS>=1) {
|
if (NUM_PIVOTS>=1) {
|
||||||
interpolationPivots(sample, remainder, NUM_PIVOTS, interpDistanceFn, INTERP_FINE_ITS);
|
interpolationPivots(sample, remainder, NUM_PIVOTS, interpDistanceFn, INTERP_FINE_ITS);
|
||||||
} else {
|
} else {
|
||||||
interpBruteForce(sample, remainder, interpDistanceFn, INTERP_FINE_ITS);
|
interpBruteForce(sample, remainder, interpDistanceFn, INTERP_FINE_ITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.call("fullTick");
|
event.call("fullTick");
|
||||||
alreadyRanIterations = 0;
|
alreadyRanIterations = 0;
|
||||||
simulation
|
simulation
|
||||||
.on("tick", null)
|
.on("tick", null)
|
||||||
.on("end", null) // The ending condition should be iterations count
|
.on("end", null) // The ending condition should be iterations count
|
||||||
.nodes(nodes);
|
.nodes(nodes);
|
||||||
|
|
||||||
if (FULL_ITERATIONS<1 || forceF === undefined || forceF === null) {
|
if (FULL_ITERATIONS<1 || forceF === undefined || forceF === null) {
|
||||||
event.call("end");
|
event.call("end");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
simulation
|
simulation
|
||||||
.on("tick", fullTick)
|
.on("tick", fullTick)
|
||||||
.force("Full force", forceFull)
|
.force("Full force", forceFull)
|
||||||
.restart();
|
.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
return hybrid = {
|
return hybrid = {
|
||||||
restart: function () {
|
restart: function () {
|
||||||
if(!initAlready) initialize();
|
if(!initAlready) initialize();
|
||||||
simulation.restart();
|
simulation.restart();
|
||||||
return hybrid;
|
return hybrid;
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function () {
|
stop: function () {
|
||||||
simulation.stop();
|
simulation.stop();
|
||||||
return hybrid;
|
return hybrid;
|
||||||
},
|
},
|
||||||
|
|
||||||
numPivots: function (_) {
|
numPivots: function (_) {
|
||||||
return arguments.length ? (NUM_PIVOTS = +_, hybrid) : NUM_PIVOTS;
|
return arguments.length ? (NUM_PIVOTS = +_, hybrid) : NUM_PIVOTS;
|
||||||
},
|
},
|
||||||
|
|
||||||
sampleIterations: function (_) {
|
sampleIterations: function (_) {
|
||||||
return arguments.length ? (SAMPLE_ITERATIONS = +_, hybrid) : SAMPLE_ITERATIONS;
|
return arguments.length ? (SAMPLE_ITERATIONS = +_, hybrid) : SAMPLE_ITERATIONS;
|
||||||
},
|
},
|
||||||
|
|
||||||
fullIterations: function (_) {
|
fullIterations: function (_) {
|
||||||
return arguments.length ? (FULL_ITERATIONS = +_, hybrid) : FULL_ITERATIONS;
|
return arguments.length ? (FULL_ITERATIONS = +_, hybrid) : FULL_ITERATIONS;
|
||||||
},
|
},
|
||||||
|
|
||||||
interpFindTuneIts: function (_) {
|
interpFindTuneIts: function (_) {
|
||||||
return arguments.length ? (INTERP_FINE_ITS = +_, hybrid) : INTERP_FINE_ITS;
|
return arguments.length ? (INTERP_FINE_ITS = +_, hybrid) : INTERP_FINE_ITS;
|
||||||
},
|
},
|
||||||
|
|
||||||
on: function (name, _) {
|
on: function (name, _) {
|
||||||
return arguments.length > 1 ? (event.on(name, _), hybrid) : event.on(name);
|
return arguments.length > 1 ? (event.on(name, _), hybrid) : event.on(name);
|
||||||
},
|
},
|
||||||
|
|
||||||
subSet: function (_) {
|
subSet: function (_) {
|
||||||
return arguments.length ? (sample = _, hybrid) : sample;
|
return arguments.length ? (sample = _, hybrid) : sample;
|
||||||
},
|
},
|
||||||
|
|
||||||
nonSubSet: function (_) {
|
nonSubSet: function (_) {
|
||||||
return arguments.length ? (remainder = _, hybrid) : remainder;
|
return arguments.length ? (remainder = _, hybrid) : remainder;
|
||||||
},
|
},
|
||||||
|
|
||||||
interpDistanceFn: function (_) {
|
interpDistanceFn: function (_) {
|
||||||
return arguments.length ? (interpDistanceFn = typeof _ === "function" ? _ : constant(+_), hybrid) : interpDistanceFn;
|
return arguments.length ? (interpDistanceFn = typeof _ === "function" ? _ : constant(+_), hybrid) : interpDistanceFn;
|
||||||
},
|
},
|
||||||
|
|
||||||
simulation: function (_) {
|
simulation: function (_) {
|
||||||
return arguments.length ? (initAlready = false, simulation = _, hybrid) : simulation;
|
return arguments.length ? (initAlready = false, simulation = _, hybrid) : simulation;
|
||||||
},
|
},
|
||||||
|
|
||||||
forceSample: function (_) {
|
forceSample: function (_) {
|
||||||
return arguments.length ? (forceSample = _, initForces(), hybrid) : forceSample;
|
return arguments.length ? (forceSample = _, initForces(), hybrid) : forceSample;
|
||||||
},
|
},
|
||||||
|
|
||||||
forceFull: function (_) {
|
forceFull: function (_) {
|
||||||
return arguments.length ? (forceFull = _, initForces(), hybrid) : forceFull;
|
return arguments.length ? (forceFull = _, initForces(), hybrid) : forceFull;
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
*/
|
*/
|
||||||
export function takeSampleFrom(sourceList, amount) {
|
export function takeSampleFrom(sourceList, amount) {
|
||||||
let randElements = [],
|
let randElements = [],
|
||||||
max = sourceList.length,
|
max = sourceList.length,
|
||||||
swap = false;
|
swap = false;
|
||||||
|
|
||||||
if (amount >= max) {
|
if (amount >= max) {
|
||||||
return {sample: sourceList, remainder: {}};
|
return {sample: sourceList, remainder: {}};
|
||||||
@@ -40,8 +40,7 @@ export function takeSampleFrom(sourceList, amount) {
|
|||||||
sample: remainder,
|
sample: remainder,
|
||||||
remainder: randElements
|
remainder: randElements
|
||||||
};
|
};
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return {
|
return {
|
||||||
sample: randElements,
|
sample: randElements,
|
||||||
remainder: remainder
|
remainder: remainder
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function(sampleSet, remainderSet, distanceFn, endingIts) {
|
|||||||
sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample,
|
sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample,
|
||||||
sampleSubsetDistanceCache = [];
|
sampleSubsetDistanceCache = [];
|
||||||
|
|
||||||
// For each datapoint "node" to be interpolated
|
// For each datapoint "node" to be interpolated
|
||||||
for (let i = remainderSet.length-1; i>=0; i--) {
|
for (let i = remainderSet.length-1; i>=0; i--) {
|
||||||
let
|
let
|
||||||
node = remainderSet[i],
|
node = remainderSet[i],
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleS
|
|||||||
lowBound = 0.0,
|
lowBound = 0.0,
|
||||||
highBound = 0.0;
|
highBound = 0.0;
|
||||||
|
|
||||||
// Determine the closest quadrant
|
// Determine the closest quadrant
|
||||||
if (dist0 == dist180) {
|
if (dist0 == dist180) {
|
||||||
if (dist90 > dist270)
|
if (dist90 > dist270)
|
||||||
lowBound = highBound = 270;
|
lowBound = highBound = 270;
|
||||||
@@ -55,14 +55,12 @@ export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleS
|
|||||||
lowBound = 90;
|
lowBound = 90;
|
||||||
highBound = 180;
|
highBound = 180;
|
||||||
}
|
}
|
||||||
|
} else if (dist90 > dist270) {
|
||||||
|
lowBound = 270;
|
||||||
|
highBound = 360;
|
||||||
} else {
|
} else {
|
||||||
if (dist90 > dist270) {
|
lowBound = 0;
|
||||||
lowBound = 270;
|
highBound = 90;
|
||||||
highBound = 360;
|
|
||||||
} else {
|
|
||||||
lowBound = 0;
|
|
||||||
highBound = 90;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the angle
|
// Determine the angle
|
||||||
@@ -84,8 +82,8 @@ export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleS
|
|||||||
|
|
||||||
function sumForcesToSample(node, samples, sampleCache) {
|
function sumForcesToSample(node, samples, sampleCache) {
|
||||||
let nodeVx = 0,
|
let nodeVx = 0,
|
||||||
nodeVy = 0,
|
nodeVy = 0,
|
||||||
x, y, l, i, sample;
|
x, y, l, i, sample;
|
||||||
|
|
||||||
for (i = samples.length-1; i >=0 ; i--) {
|
for (i = samples.length-1; i >=0 ; i--) {
|
||||||
sample = samples[i];
|
sample = samples[i];
|
||||||
|
|||||||
216
src/link.js
216
src/link.js
@@ -1,108 +1,108 @@
|
|||||||
import constant from "./constant";
|
import constant from "./constant";
|
||||||
import jiggle from "./jiggle";
|
import jiggle from "./jiggle";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified link force algorithm
|
* Modified link force algorithm
|
||||||
* - simplify calculations for parameters locked for spring model
|
* - simplify calculations for parameters locked for spring model
|
||||||
* - replace the use of links {} with loop. greatly reduce memory usage
|
* - replace the use of links {} with loop. greatly reduce memory usage
|
||||||
* - removed other unused functions
|
* - removed other unused functions
|
||||||
* Alpha should be constant 1 for accurate simulation
|
* Alpha should be constant 1 for accurate simulation
|
||||||
*/
|
*/
|
||||||
export default function() {
|
export default function() {
|
||||||
var dataSizeFactor,
|
var dataSizeFactor,
|
||||||
distance = constant(30),
|
distance = constant(30),
|
||||||
distances = [],
|
distances = [],
|
||||||
nodes,
|
nodes,
|
||||||
stableVelocity = 0,
|
stableVelocity = 0,
|
||||||
stableVeloHandler = null,
|
stableVeloHandler = null,
|
||||||
latestVelocityDiff = 0,
|
latestVelocityDiff = 0,
|
||||||
iterations = 1;
|
iterations = 1;
|
||||||
|
|
||||||
function force(alpha) {
|
function force(alpha) {
|
||||||
let n = nodes.length;
|
let n = nodes.length;
|
||||||
// Cache old velocity for comparison later
|
// Cache old velocity for comparison later
|
||||||
if (stableVeloHandler!==null && stableVelocity>=0) {
|
if (stableVeloHandler!==null && stableVelocity>=0) {
|
||||||
for (let i = n-1, node; i>=0; i--) {
|
for (let i = n-1, node; i>=0; i--) {
|
||||||
node = nodes[i];
|
node = nodes[i];
|
||||||
node.oldvx = node.vx;
|
node.oldvx = node.vx;
|
||||||
node.oldvy = node.vy;
|
node.oldvy = node.vy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each iteration in a tick
|
// Each iteration in a tick
|
||||||
for (var k = 0, source, target, i, j, x, y, l; k < iterations; ++k) {
|
for (var k = 0, source, target, i, j, x, y, l; k < iterations; ++k) {
|
||||||
// For each link
|
// For each link
|
||||||
for (i = 1; i < n; i++) for (j = 0; j < i; j++) {
|
for (i = 1; i < n; i++) for (j = 0; j < i; j++) {
|
||||||
// jiggle so l won't be zero and divide by zero error after this
|
// jiggle so l won't be zero and divide by zero error after this
|
||||||
source = nodes[i];
|
source = nodes[i];
|
||||||
target = nodes[j];
|
target = nodes[j];
|
||||||
x = target.x + target.vx - source.x - source.vx || jiggle();
|
x = target.x + target.vx - source.x - source.vx || jiggle();
|
||||||
y = target.y + target.vy - source.y - source.vy || jiggle();
|
y = target.y + target.vy - source.y - source.vy || jiggle();
|
||||||
l = Math.sqrt(x * x + y * y);
|
l = Math.sqrt(x * x + y * y);
|
||||||
l = (l - distances[i*(i-1)/2+j]) / l * dataSizeFactor * alpha;
|
l = (l - distances[i*(i-1)/2+j]) / l * dataSizeFactor * alpha;
|
||||||
x *= l, y *= l;
|
x *= l, y *= l;
|
||||||
target.vx -= x;
|
target.vx -= x;
|
||||||
target.vy -= y;
|
target.vy -= y;
|
||||||
source.vx += x;
|
source.vx += x;
|
||||||
source.vy += y;
|
source.vy += y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate velocity changes, aka force applied.
|
// Calculate velocity changes, aka force applied.
|
||||||
if (stableVeloHandler!==null && stableVelocity>=0) {
|
if (stableVeloHandler!==null && stableVelocity>=0) {
|
||||||
let velocityDiff = 0;
|
let velocityDiff = 0;
|
||||||
for (let i = n-1, node; i>=0; i--) {
|
for (let i = n-1, node; i>=0; i--) {
|
||||||
node = nodes[i];
|
node = nodes[i];
|
||||||
velocityDiff += Math.abs(Math.hypot(node.vx-node.oldvx, node.vy-node.oldvy));
|
velocityDiff += Math.abs(Math.hypot(node.vx-node.oldvx, node.vy-node.oldvy));
|
||||||
}
|
}
|
||||||
velocityDiff /= n;
|
velocityDiff /= n;
|
||||||
latestVelocityDiff = velocityDiff;
|
latestVelocityDiff = velocityDiff;
|
||||||
|
|
||||||
if(velocityDiff<stableVelocity){
|
if(velocityDiff<stableVelocity){
|
||||||
stableVeloHandler();
|
stableVeloHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
if (!nodes) return;
|
if (!nodes) return;
|
||||||
// 0.5 to divide the force to two part for source and target node
|
// 0.5 to divide the force to two part for source and target node
|
||||||
dataSizeFactor = 0.5/(nodes.length-1);
|
dataSizeFactor = 0.5/(nodes.length-1);
|
||||||
initializeDistance();
|
initializeDistance();
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeDistance() {
|
function initializeDistance() {
|
||||||
if (!nodes) return;
|
if (!nodes) return;
|
||||||
for (let i = 1, n = nodes.length; i < n; i++) {
|
for (let i = 1, n = nodes.length; i < n; i++) {
|
||||||
for (let j = 0; j < i; j++) {
|
for (let j = 0; j < i; j++) {
|
||||||
distances.push(distance(nodes[i], nodes[j]));
|
distances.push(distance(nodes[i], nodes[j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
force.initialize = function(_) {
|
force.initialize = function(_) {
|
||||||
nodes = _;
|
nodes = _;
|
||||||
initialize();
|
initialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
force.iterations = function(_) {
|
force.iterations = function(_) {
|
||||||
return arguments.length ? (iterations = +_, force) : iterations;
|
return arguments.length ? (iterations = +_, force) : iterations;
|
||||||
};
|
};
|
||||||
|
|
||||||
force.distance = function(_) {
|
force.distance = function(_) {
|
||||||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
|
||||||
};
|
};
|
||||||
|
|
||||||
force.latestAccel = function () {
|
force.latestAccel = function () {
|
||||||
return latestVelocityDiff;
|
return latestVelocityDiff;
|
||||||
};
|
};
|
||||||
|
|
||||||
force.onStableVelo = function (_) {
|
force.onStableVelo = function (_) {
|
||||||
return arguments.length ? (stableVeloHandler = _, force) : stableVeloHandler;
|
return arguments.length ? (stableVeloHandler = _, force) : stableVeloHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
force.stableVelocity = function (_) {
|
force.stableVelocity = function (_) {
|
||||||
return arguments.length ? (stableVelocity = _, force) : stableVelocity;
|
return arguments.length ? (stableVelocity = _, force) : stableVelocity;
|
||||||
};
|
};
|
||||||
return force;
|
return force;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function () {
|
|||||||
dataSizeFactor,
|
dataSizeFactor,
|
||||||
latestVelocityDiff = 0;
|
latestVelocityDiff = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply spring forces at each simulation iteration.
|
* Apply spring forces at each simulation iteration.
|
||||||
* @param {number} alpha - multiplier for amount of force applied
|
* @param {number} alpha - multiplier for amount of force applied
|
||||||
*/
|
*/
|
||||||
|
|||||||
752
src/t-sne.js
752
src/t-sne.js
@@ -1,375 +1,377 @@
|
|||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
import constant from "./constant";
|
import constant from "./constant";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the node id accessor to the specified i.
|
* Set the node id accessor to the specified i.
|
||||||
* @param {node} d - node.
|
* @param {node} d - node.
|
||||||
* @param {accessor} i - id accessor.
|
* @param {accessor} i - id accessor.
|
||||||
* @return {accessor} - node id accessor.
|
* @return {accessor} - node id accessor.
|
||||||
*/
|
*/
|
||||||
function index(d, i) {
|
function index(d, i) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* t-SNE implementation in D3 by using the code existing in tsnejs
|
* t-SNE implementation in D3 by using the code existing in tsnejs
|
||||||
* (https://github.com/karpathy/tsnejs) to compute the solution.
|
* (https://github.com/karpathy/tsnejs) to compute the solution.
|
||||||
*/
|
*/
|
||||||
export default function() {
|
export default function() {
|
||||||
var id = index,
|
var id = index,
|
||||||
distance = constant(300),
|
distance = constant(300),
|
||||||
nodes,
|
nodes,
|
||||||
perplexity = 30,
|
perplexity = 30,
|
||||||
learningRate = 10,
|
learningRate = 10,
|
||||||
iteration = 0,
|
iteration = 0,
|
||||||
dim = 2,
|
dim = 2,
|
||||||
N, // length of the nodes.
|
N, // length of the nodes.
|
||||||
P, // probability matrix.
|
P, // probability matrix.
|
||||||
Y, // solution.
|
Y, // solution.
|
||||||
gains,
|
gains,
|
||||||
ystep;
|
ystep;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a step in t-SNE algorithm and set the velocities for the nodes
|
* Make a step in t-SNE algorithm and set the velocities for the nodes
|
||||||
* to accumulate the values from solution.
|
* to accumulate the values from solution.
|
||||||
*/
|
*/
|
||||||
function force() {
|
function force() {
|
||||||
// Make a step at each iteration.
|
// Make a step at each iteration.
|
||||||
step();
|
step();
|
||||||
var solution = getSolution();
|
var solution = getSolution();
|
||||||
|
|
||||||
// Set the velocity for each node using the solution.
|
// Set the velocity for each node using the solution.
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
nodes[i].vx += solution[i][0];
|
nodes[i].vx += solution[i][0];
|
||||||
nodes[i].vy += solution[i][1];
|
nodes[i].vy += solution[i][1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the random number from Gaussian distribution.
|
* Calculates the random number from Gaussian distribution.
|
||||||
* @return {number} random number.
|
* @return {number} random number.
|
||||||
*/
|
*/
|
||||||
function gaussRandom() {
|
function gaussRandom() {
|
||||||
let u = 2 * Math.random() - 1;
|
let u = 2 * Math.random() - 1;
|
||||||
let v = 2 * Math.random() - 1;
|
let v = 2 * Math.random() - 1;
|
||||||
let r = u * u + v * v;
|
let r = u * u + v * v;
|
||||||
if (r == 0 || r > 1) {return gaussRandom();}
|
if (r == 0 || r > 1) {
|
||||||
return u * Math.sqrt(-2 * Math.log(r) / r);
|
return gaussRandom();
|
||||||
}
|
}
|
||||||
|
return u * Math.sqrt(-2 * Math.log(r) / r);
|
||||||
/**
|
}
|
||||||
* Return the normalized number.
|
|
||||||
* @return {number} normalized random number from Gaussian distribution.
|
/**
|
||||||
*/
|
* Return the normalized number.
|
||||||
function randomN() {
|
* @return {number} normalized random number from Gaussian distribution.
|
||||||
return gaussRandom() * 1e-4;
|
*/
|
||||||
}
|
function randomN() {
|
||||||
|
return gaussRandom() * 1e-4;
|
||||||
function sign(x) {
|
}
|
||||||
return x > 0 ? 1 : x < 0 ? -1 : 0;
|
|
||||||
}
|
function sign(x) {
|
||||||
|
return x > 0 ? 1 : x < 0 ? -1 : 0;
|
||||||
/**
|
}
|
||||||
* Create an array of length n filled with zeros.
|
|
||||||
* @param {number} n - length of array.
|
/**
|
||||||
* @return {Float64Array} - array of zeros with length n.
|
* Create an array of length n filled with zeros.
|
||||||
*/
|
* @param {number} n - length of array.
|
||||||
function zeros(n) {
|
* @return {Float64Array} - array of zeros with length n.
|
||||||
if (typeof n === 'undefined' || isNaN(n)) {
|
*/
|
||||||
return [];
|
function zeros(n) {
|
||||||
}
|
if (typeof n === 'undefined' || isNaN(n)) {
|
||||||
return new Float64Array(n); // typed arrays are faster
|
return [];
|
||||||
}
|
}
|
||||||
|
return new Float64Array(n); // typed arrays are faster
|
||||||
// Returns a 2d array of random numbers
|
}
|
||||||
/**
|
|
||||||
* Creates a 2d array filled with random numbers.
|
// Returns a 2d array of random numbers
|
||||||
* @param {number} n - rows.
|
/**
|
||||||
* @param {number} d - columns.
|
* Creates a 2d array filled with random numbers.
|
||||||
* @return {array} - 2d array
|
* @param {number} n - rows.
|
||||||
*/
|
* @param {number} d - columns.
|
||||||
function random2d(n, d) {
|
* @return {array} - 2d array
|
||||||
var x = [];
|
*/
|
||||||
for (var i = 0; i < n; i++) {
|
function random2d(n, d) {
|
||||||
var y = [];
|
var x = [];
|
||||||
for (var j = 0; j < d; j++) {
|
for (var i = 0; i < n; i++) {
|
||||||
y.push(randomN());
|
var y = [];
|
||||||
}
|
for (var j = 0; j < d; j++) {
|
||||||
x.push(y);
|
y.push(randomN());
|
||||||
}
|
}
|
||||||
return x;
|
x.push(y);
|
||||||
}
|
}
|
||||||
|
return x;
|
||||||
/**
|
}
|
||||||
* Compute the probability matrix using the provided data.
|
|
||||||
* @param {array} data - nodes.
|
/**
|
||||||
* @param {number} perplexity - used to calculate entropy of distribution.
|
* Compute the probability matrix using the provided data.
|
||||||
* @param {number} tol - limit for entropy difference.
|
* @param {array} data - nodes.
|
||||||
* @return {2d array} - 2d matrix containing probabilities.
|
* @param {number} perplexity - used to calculate entropy of distribution.
|
||||||
*/
|
* @param {number} tol - limit for entropy difference.
|
||||||
function d2p(data, perplexity, tol) {
|
* @return {2d array} - 2d matrix containing probabilities.
|
||||||
N = Math.floor(data.length);
|
*/
|
||||||
var Htarget = Math.log(perplexity); // target entropy of distribution.
|
function d2p(data, perplexity, tol) {
|
||||||
var P1 = zeros(N * N); // temporary probability matrix.
|
N = Math.floor(data.length);
|
||||||
|
var Htarget = Math.log(perplexity); // target entropy of distribution.
|
||||||
var prow = zeros(N); // a temporary storage compartment.
|
var P1 = zeros(N * N); // temporary probability matrix.
|
||||||
for (var i = 0; i < N; i++) {
|
|
||||||
var betamin = -Infinity;
|
var prow = zeros(N); // a temporary storage compartment.
|
||||||
var betamax = Infinity;
|
for (var i = 0; i < N; i++) {
|
||||||
var beta = 1; // initial value of precision.
|
var betamin = -Infinity;
|
||||||
var done = false;
|
var betamax = Infinity;
|
||||||
var maxtries = 50;
|
var beta = 1; // initial value of precision.
|
||||||
|
var done = false;
|
||||||
// Perform binary search to find a suitable precision beta
|
var maxtries = 50;
|
||||||
// so that the entropy of the distribution is appropriate.
|
|
||||||
var num = 0;
|
// Perform binary search to find a suitable precision beta
|
||||||
while (!done) {
|
// so that the entropy of the distribution is appropriate.
|
||||||
// Compute entropy and kernel row with beta precision.
|
var num = 0;
|
||||||
var psum = 0.0;
|
while (!done) {
|
||||||
for (var j = 0; j < N; j++) {
|
// Compute entropy and kernel row with beta precision.
|
||||||
var pj = Math.exp(-distance(data[i], data[j]) * beta);
|
var psum = 0.0;
|
||||||
// Ignore the diagonals
|
for (var j = 0; j < N; j++) {
|
||||||
if (i === j) {
|
var pj = Math.exp(-distance(data[i], data[j]) * beta);
|
||||||
pj = 0;
|
// Ignore the diagonals
|
||||||
}
|
if (i === j) {
|
||||||
prow[j] = pj;
|
pj = 0;
|
||||||
psum += pj;
|
}
|
||||||
}
|
prow[j] = pj;
|
||||||
// Normalize p and compute entropy.
|
psum += pj;
|
||||||
var Hhere = 0.0;
|
}
|
||||||
for (j = 0; j < N; j++) {
|
// Normalize p and compute entropy.
|
||||||
if (psum == 0) {
|
var Hhere = 0.0;
|
||||||
pj = 0;
|
for (j = 0; j < N; j++) {
|
||||||
} else {
|
if (psum == 0) {
|
||||||
pj = prow[j] / psum;
|
pj = 0;
|
||||||
}
|
} else {
|
||||||
prow[j] = pj;
|
pj = prow[j] / psum;
|
||||||
if (pj > 1e-7) {
|
}
|
||||||
Hhere -= pj * Math.log(pj);
|
prow[j] = pj;
|
||||||
}
|
if (pj > 1e-7) {
|
||||||
}
|
Hhere -= pj * Math.log(pj);
|
||||||
|
}
|
||||||
// Adjust beta based on result.
|
}
|
||||||
if (Hhere > Htarget) {
|
|
||||||
// Entropy was too high (distribution too diffuse)
|
// Adjust beta based on result.
|
||||||
// so we need to increase the precision for more peaky distribution.
|
if (Hhere > Htarget) {
|
||||||
betamin = beta; // move up the bounds.
|
// Entropy was too high (distribution too diffuse)
|
||||||
if (betamax === Infinity) {
|
// so we need to increase the precision for more peaky distribution.
|
||||||
beta = beta * 2;
|
betamin = beta; // move up the bounds.
|
||||||
} else {
|
if (betamax === Infinity) {
|
||||||
beta = (beta + betamax) / 2;
|
beta = beta * 2;
|
||||||
}
|
} else {
|
||||||
|
beta = (beta + betamax) / 2;
|
||||||
} else {
|
}
|
||||||
// Converse case. Make distrubtion less peaky.
|
|
||||||
betamax = beta;
|
} else {
|
||||||
if (betamin === -Infinity) {
|
// Converse case. Make distrubtion less peaky.
|
||||||
beta = beta / 2;
|
betamax = beta;
|
||||||
} else {
|
if (betamin === -Infinity) {
|
||||||
beta = (beta + betamin) / 2;
|
beta = beta / 2;
|
||||||
}
|
} else {
|
||||||
}
|
beta = (beta + betamin) / 2;
|
||||||
|
}
|
||||||
// Stopping conditions: too many tries or got a good precision.
|
}
|
||||||
num++;
|
|
||||||
if (Math.abs(Hhere - Htarget) < tol || num >= maxtries) {
|
// Stopping conditions: too many tries or got a good precision.
|
||||||
done = true;
|
num++;
|
||||||
}
|
if (Math.abs(Hhere - Htarget) < tol || num >= maxtries) {
|
||||||
}
|
done = true;
|
||||||
|
}
|
||||||
// Copy over the final prow to P1 at row i
|
}
|
||||||
for (j = 0; j < N; j++) {
|
|
||||||
P1[i * N + j] = prow[j];
|
// Copy over the final prow to P1 at row i
|
||||||
}
|
for (j = 0; j < N; j++) {
|
||||||
|
P1[i * N + j] = prow[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symmetrize P and normalize it to sum to 1 over all ij
|
}
|
||||||
var Pout = zeros(N * N);
|
|
||||||
var N2 = N * 2;
|
// Symmetrize P and normalize it to sum to 1 over all ij
|
||||||
for (i = 0; i < N; i++) {
|
var Pout = zeros(N * N);
|
||||||
for (j = 0; j < N; j++) {
|
var N2 = N * 2;
|
||||||
Pout[i * N + j] = Math.max((P1[i * N + j] + P1[j * N + i]) / N2, 1e-100);
|
for (i = 0; i < N; i++) {
|
||||||
}
|
for (j = 0; j < N; j++) {
|
||||||
}
|
Pout[i * N + j] = Math.max((P1[i * N + j] + P1[j * N + i]) / N2, 1e-100);
|
||||||
return Pout;
|
}
|
||||||
}
|
}
|
||||||
|
return Pout;
|
||||||
/**
|
}
|
||||||
* Initialize a starting (random) solution.
|
|
||||||
*/
|
/**
|
||||||
function initSolution() {
|
* Initialize a starting (random) solution.
|
||||||
Y = random2d(N, dim);
|
*/
|
||||||
// Step gains to accelerate progress in unchanging directions.
|
function initSolution() {
|
||||||
gains = random2d(N, dim, 1.0);
|
Y = random2d(N, dim);
|
||||||
// Momentum accumulator.
|
// Step gains to accelerate progress in unchanging directions.
|
||||||
ystep = random2d(N, dim, 0.0);
|
gains = random2d(N, dim, 1.0);
|
||||||
iteration = 0;
|
// Momentum accumulator.
|
||||||
}
|
ystep = random2d(N, dim, 0.0);
|
||||||
|
iteration = 0;
|
||||||
/**
|
}
|
||||||
* @return {2d array} the solution.
|
|
||||||
*/
|
/**
|
||||||
function getSolution() {
|
* @return {2d array} the solution.
|
||||||
return Y;
|
*/
|
||||||
}
|
function getSolution() {
|
||||||
|
return Y;
|
||||||
/**
|
}
|
||||||
* Do a single step (iteration) for the layout.
|
|
||||||
* @return {number} the current cost.
|
/**
|
||||||
*/
|
* Do a single step (iteration) for the layout.
|
||||||
function step() {
|
* @return {number} the current cost.
|
||||||
iteration += 1;
|
*/
|
||||||
|
function step() {
|
||||||
var cg = costGrad(Y); // Evaluate gradient.
|
iteration += 1;
|
||||||
var cost = cg.cost;
|
|
||||||
var grad = cg.grad;
|
var cg = costGrad(Y); // Evaluate gradient.
|
||||||
|
var cost = cg.cost;
|
||||||
// Perform gradient step.
|
var grad = cg.grad;
|
||||||
var ymean = zeros(dim);
|
|
||||||
for (var i = 0; i < N; i++) {
|
// Perform gradient step.
|
||||||
for (var d = 0; d < dim; d++) {
|
var ymean = zeros(dim);
|
||||||
var gid = grad[i][d];
|
for (var i = 0; i < N; i++) {
|
||||||
var sid = ystep[i][d];
|
for (var d = 0; d < dim; d++) {
|
||||||
var gainid = gains[i][d];
|
var gid = grad[i][d];
|
||||||
|
var sid = ystep[i][d];
|
||||||
// Compute gain update.
|
var gainid = gains[i][d];
|
||||||
var newgain = sign(gid) === sign(sid) ? gainid * 0.8 : gainid + 0.2;
|
|
||||||
if (newgain < 0.01) {
|
// Compute gain update.
|
||||||
newgain = 0.01;
|
var newgain = sign(gid) === sign(sid) ? gainid * 0.8 : gainid + 0.2;
|
||||||
}
|
if (newgain < 0.01) {
|
||||||
gains[i][d] = newgain;
|
newgain = 0.01;
|
||||||
|
}
|
||||||
// Compute momentum step direction.
|
gains[i][d] = newgain;
|
||||||
var momval = iteration < 250 ? 0.5 : 0.8;
|
|
||||||
var newsid = momval * sid - learningRate * newgain * grad[i][d];
|
// Compute momentum step direction.
|
||||||
ystep[i][d] = newsid;
|
var momval = iteration < 250 ? 0.5 : 0.8;
|
||||||
|
var newsid = momval * sid - learningRate * newgain * grad[i][d];
|
||||||
// Do the step.
|
ystep[i][d] = newsid;
|
||||||
Y[i][d] += newsid;
|
|
||||||
|
// Do the step.
|
||||||
// Accumulate mean so that we can center later.
|
Y[i][d] += newsid;
|
||||||
ymean[d] += Y[i][d];
|
|
||||||
}
|
// Accumulate mean so that we can center later.
|
||||||
}
|
ymean[d] += Y[i][d];
|
||||||
|
}
|
||||||
// Reproject Y to have the zero mean.
|
}
|
||||||
for (i = 0; i < N; i++) {
|
|
||||||
for (d = 0; d < dim; d++) {
|
// Reproject Y to have the zero mean.
|
||||||
Y[i][d] -= ymean[d] / N;
|
for (i = 0; i < N; i++) {
|
||||||
}
|
for (d = 0; d < dim; d++) {
|
||||||
}
|
Y[i][d] -= ymean[d] / N;
|
||||||
return cost;
|
}
|
||||||
}
|
}
|
||||||
|
return cost;
|
||||||
/**
|
}
|
||||||
* Calculate the cost and the gradient.
|
|
||||||
* @param {2d array} Y - the current solution to evaluate.
|
/**
|
||||||
* @return {object} that contains a cost and a gradient.
|
* Calculate the cost and the gradient.
|
||||||
*/
|
* @param {2d array} Y - the current solution to evaluate.
|
||||||
function costGrad(Y) {
|
* @return {object} that contains a cost and a gradient.
|
||||||
|
*/
|
||||||
var pmul = iteration < 100 ? 4 : 1;
|
function costGrad(Y) {
|
||||||
|
|
||||||
// Compute current Q distribution, unnormalized first.
|
var pmul = iteration < 100 ? 4 : 1;
|
||||||
var Qu = zeros(N * N);
|
|
||||||
var qsum = 0.0;
|
// Compute current Q distribution, unnormalized first.
|
||||||
for (var i = 0; i < N; i++) {
|
var Qu = zeros(N * N);
|
||||||
for (var j = i + 1; j < N; j++) {
|
var qsum = 0.0;
|
||||||
var dsum = 0.0;
|
for (var i = 0; i < N; i++) {
|
||||||
for (var d = 0; d < dim; d++) {
|
for (var j = i + 1; j < N; j++) {
|
||||||
var dhere = Y[i][d] - Y[j][d];
|
var dsum = 0.0;
|
||||||
dsum += dhere * dhere;
|
for (var d = 0; d < dim; d++) {
|
||||||
}
|
var dhere = Y[i][d] - Y[j][d];
|
||||||
var qu = 1.0 / (1.0 + dsum); // Student t-distribution.
|
dsum += dhere * dhere;
|
||||||
Qu[i * N + j] = qu;
|
}
|
||||||
Qu[j * N + i] = qu;
|
var qu = 1.0 / (1.0 + dsum); // Student t-distribution.
|
||||||
qsum += 2 * qu;
|
Qu[i * N + j] = qu;
|
||||||
}
|
Qu[j * N + i] = qu;
|
||||||
}
|
qsum += 2 * qu;
|
||||||
// Normalize Q distribution to sum to 1.
|
}
|
||||||
var NN = N * N;
|
}
|
||||||
var Q = zeros(NN);
|
// Normalize Q distribution to sum to 1.
|
||||||
for (var q = 0; q < NN; q++) {
|
var NN = N * N;
|
||||||
Q[q] = Math.max(Qu[q] / qsum, 1e-100);
|
var Q = zeros(NN);
|
||||||
}
|
for (var q = 0; q < NN; q++) {
|
||||||
|
Q[q] = Math.max(Qu[q] / qsum, 1e-100);
|
||||||
var cost = 0.0;
|
}
|
||||||
var grad = [];
|
|
||||||
for (i = 0; i < N; i++) {
|
var cost = 0.0;
|
||||||
var gsum = new Array(dim); // Initialize gradiet for point i.
|
var grad = [];
|
||||||
for (d = 0; d < dim; d++) {
|
for (i = 0; i < N; i++) {
|
||||||
gsum[d] = 0.0;
|
var gsum = new Array(dim); // Initialize gradiet for point i.
|
||||||
}
|
for (d = 0; d < dim; d++) {
|
||||||
for (j = 0; j < N; j++) {
|
gsum[d] = 0.0;
|
||||||
// Accumulate the cost.
|
}
|
||||||
cost += -P[i * N + j] * Math.log(Q[i * N + j]);
|
for (j = 0; j < N; j++) {
|
||||||
var premult = 4 * (pmul * P[i * N + j] - Q[i * N + j]) * Qu[i * N + j];
|
// Accumulate the cost.
|
||||||
for (d = 0; d < dim; d++) {
|
cost += -P[i * N + j] * Math.log(Q[i * N + j]);
|
||||||
gsum[d] += premult * (Y[i][d] - Y[j][d]);
|
var premult = 4 * (pmul * P[i * N + j] - Q[i * N + j]) * Qu[i * N + j];
|
||||||
}
|
for (d = 0; d < dim; d++) {
|
||||||
}
|
gsum[d] += premult * (Y[i][d] - Y[j][d]);
|
||||||
grad.push(gsum);
|
}
|
||||||
}
|
}
|
||||||
|
grad.push(gsum);
|
||||||
return {
|
}
|
||||||
cost: cost,
|
|
||||||
grad: grad
|
return {
|
||||||
};
|
cost: cost,
|
||||||
}
|
grad: grad
|
||||||
|
};
|
||||||
/**
|
}
|
||||||
* Calculates the stress. Basically, it computes the difference between
|
|
||||||
* high dimensional distance and real distance. The lower the stress is,
|
/**
|
||||||
* the better layout.
|
* Calculates the stress. Basically, it computes the difference between
|
||||||
* @return {number} - stress of the layout.
|
* high dimensional distance and real distance. The lower the stress is,
|
||||||
*/
|
* the better layout.
|
||||||
function getStress() {
|
* @return {number} - stress of the layout.
|
||||||
var totalDiffSq = 0,
|
*/
|
||||||
totalHighDistSq = 0;
|
function getStress() {
|
||||||
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
var totalDiffSq = 0,
|
||||||
for (var j = 0; j < nodes.length; j++) {
|
totalHighDistSq = 0;
|
||||||
if (i !== j) {
|
for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) {
|
||||||
source = nodes[i], target = nodes[j];
|
for (var j = 0; j < nodes.length; j++) {
|
||||||
realDist = Math.hypot(target.x - source.x, target.y - source.y);
|
if (i !== j) {
|
||||||
highDist = +distance(nodes[i], nodes[j]);
|
source = nodes[i], target = nodes[j];
|
||||||
totalDiffSq += Math.pow(realDist - highDist, 2);
|
realDist = Math.hypot(target.x - source.x, target.y - source.y);
|
||||||
totalHighDistSq += highDist * highDist;
|
highDist = +distance(nodes[i], nodes[j]);
|
||||||
}
|
totalDiffSq += Math.pow(realDist - highDist, 2);
|
||||||
}
|
totalHighDistSq += highDist * highDist;
|
||||||
}
|
}
|
||||||
return Math.sqrt(totalDiffSq / totalHighDistSq);
|
}
|
||||||
}
|
}
|
||||||
|
return Math.sqrt(totalDiffSq / totalHighDistSq);
|
||||||
// API for initializing the algorithm, setting parameters and querying
|
}
|
||||||
// metrics.
|
|
||||||
force.initialize = function(_) {
|
// API for initializing the algorithm, setting parameters and querying
|
||||||
nodes = _;
|
// metrics.
|
||||||
N = nodes.length;
|
force.initialize = function(_) {
|
||||||
// Initialize the probability matrix.
|
nodes = _;
|
||||||
P = d2p(nodes, perplexity, 1e-4);
|
N = nodes.length;
|
||||||
initSolution();
|
// Initialize the probability matrix.
|
||||||
};
|
P = d2p(nodes, perplexity, 1e-4);
|
||||||
|
initSolution();
|
||||||
force.id = function(_) {
|
};
|
||||||
return arguments.length ? (id = _, force) : id;
|
|
||||||
};
|
force.id = function(_) {
|
||||||
|
return arguments.length ? (id = _, force) : id;
|
||||||
force.distance = function(_) {
|
};
|
||||||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
|
||||||
};
|
force.distance = function(_) {
|
||||||
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
|
||||||
force.stress = function() {
|
};
|
||||||
return getStress();
|
|
||||||
};
|
force.stress = function() {
|
||||||
|
return getStress();
|
||||||
force.learningRate = function(_) {
|
};
|
||||||
return arguments.length ? (learningRate = +_, force) : learningRate;
|
|
||||||
};
|
force.learningRate = function(_) {
|
||||||
|
return arguments.length ? (learningRate = +_, force) : learningRate;
|
||||||
force.perplexity = function(_) {
|
};
|
||||||
return arguments.length ? (perplexity = +_, force) : perplexity;
|
|
||||||
};
|
force.perplexity = function(_) {
|
||||||
|
return arguments.length ? (perplexity = +_, force) : perplexity;
|
||||||
return force;
|
};
|
||||||
}
|
|
||||||
|
return force;
|
||||||
|
}
|
||||||
|
|||||||
26
src/xy.js
26
src/xy.js
@@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* @return x value of a node
|
* @return x value of a node
|
||||||
*/
|
*/
|
||||||
export function x(d) {
|
export function x(d) {
|
||||||
return d.x;
|
return d.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return y value of a node
|
* @return y value of a node
|
||||||
*/
|
*/
|
||||||
export function y(d) {
|
export function y(d) {
|
||||||
return d.y;
|
return d.y;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user