206 lines
6.1 KiB
JavaScript
206 lines
6.1 KiB
JavaScript
import {dispatch} from "d3-dispatch";
|
|
import constant from "./constant";
|
|
import interpBruteForce from "./interpolation/interpBruteForce";
|
|
import interpolationPivots from "./interpolation/interpolationPivots";
|
|
import {takeSampleFrom} from "./interpolation/helpers";
|
|
|
|
/**
|
|
* An implementation of Chalmers, Morrison, and Ross' 2002 hybrid layout
|
|
* algorithm with an option to use the 2003 pivot-based near neighbour searching
|
|
* method.
|
|
* It performs the 1996 neighbour sampling spring simulation model, on a
|
|
* "sample set", sqrt(n) samples of the data.
|
|
* Other data points are then interpolated into the model.
|
|
* Finally, another spring simulation may be performed on the entire dataset to
|
|
* clean up the model.
|
|
* @param {object} sim - D3 Simulation object
|
|
* @param {object} forceS - Pre-configured D3 force object for the sample set.
|
|
The ending handler will be re-configured.
|
|
Neighbour sampling force is expected, but other D3
|
|
forces may also work.
|
|
* @param {object} forceF - Pre-configured D3 force object for the simultion ran
|
|
on the entire dataset at the end.
|
|
Neighbour sampling force is expected, but other D3
|
|
forces may also work.
|
|
The force should not have any ending condition.
|
|
*/
|
|
export default function (sim, forceS, forceF) {
|
|
var
|
|
SAMPLE_ITERATIONS = 300,
|
|
FULL_ITERATIONS = 20,
|
|
interpDistanceFn,
|
|
NUM_PIVOTS = 0,
|
|
INTERP_FINE_ITS = 20,
|
|
sample = [],
|
|
remainder = [],
|
|
simulation = sim,
|
|
forceSample = forceS,
|
|
forceFull = forceF,
|
|
event = d3.dispatch("sampleTick", "fullTick", "startInterp", "end"),
|
|
initAlready = false,
|
|
nodes,
|
|
alreadyRanIterations,
|
|
hybrid;
|
|
|
|
if(simulation != undefined) initSimulation();
|
|
if(forceS != undefined || forceF != undefined) initForces();
|
|
|
|
// Performed on first run
|
|
function initialize() {
|
|
initAlready = true;
|
|
console.log("Initializing Hybrid");
|
|
alreadyRanIterations = 0;
|
|
simulation
|
|
.on("tick", sampleTick)
|
|
.on("end", sampleEnded)
|
|
.nodes(sample)
|
|
.force("Sample force", forceSample);
|
|
console.log("Initialized Hybrid");
|
|
}
|
|
|
|
function initForces(){
|
|
if (forceSample.onStableVelo) {
|
|
forceSample.onStableVelo(sampleEnded);
|
|
}
|
|
|
|
if (forceFull.onStableVelo) {
|
|
forceFull.onStableVelo(fullEnded);
|
|
}
|
|
|
|
// Set default value for interpDistanceFn if not been specified yet
|
|
if(interpDistanceFn === undefined) {
|
|
if(forceFull.distance == 'function')
|
|
interpDistanceFn = forceFull.distance();
|
|
else
|
|
interpDistanceFn = constant(300);
|
|
}
|
|
}
|
|
|
|
function initSimulation(){
|
|
nodes = simulation.nodes();
|
|
simulation
|
|
.stop()
|
|
.alphaDecay(0)
|
|
.alpha(1)
|
|
|
|
let sets = takeSampleFrom(nodes, Math.sqrt(nodes.length));
|
|
sample = sets.sample;
|
|
remainder = sets.remainder;
|
|
}
|
|
|
|
// Sample simulation ticked 1 frame, keep track of number of iterations here.
|
|
function sampleTick() {
|
|
event.call("sampleTick");
|
|
if(++alreadyRanIterations >= SAMPLE_ITERATIONS){
|
|
sampleEnded();
|
|
}
|
|
}
|
|
|
|
// Full simulation ticked 1 frame, keep track of number of iterations here.
|
|
function fullTick() {
|
|
event.call("fullTick");
|
|
if(++alreadyRanIterations >= FULL_ITERATIONS){
|
|
fullEnded();
|
|
}
|
|
}
|
|
|
|
function fullEnded() {
|
|
simulation.stop();
|
|
initAlready = false;
|
|
simulation.force("Full force", null);
|
|
event.call("end");
|
|
}
|
|
}
|
|
|
|
function sampleEnded() {
|
|
simulation.stop();
|
|
simulation.force("Sample force", null);
|
|
// Reset velocity of all nodes
|
|
for (let i=sample.length-1; i>=0; i--){
|
|
sample[i].vx=0;
|
|
sample[i].vy=0;
|
|
}
|
|
|
|
event.call("startInterp");
|
|
if (NUM_PIVOTS>=1) {
|
|
interpolationPivots(sample, remainder, NUM_PIVOTS, interpDistanceFn, INTERP_FINE_ITS);
|
|
} else {
|
|
interpBruteForce(sample, remainder, interpDistanceFn, INTERP_FINE_ITS);
|
|
}
|
|
|
|
event.call("fullTick");
|
|
alreadyRanIterations = 0;
|
|
simulation
|
|
.on("tick", null)
|
|
.on("end", null) // The ending condition should be iterations count
|
|
.nodes(nodes);
|
|
|
|
if (FULL_ITERATIONS<1 || forceF === undefined || forceF === null) {
|
|
event.call("end");
|
|
return;
|
|
}
|
|
simulation
|
|
.on("tick", fullTick)
|
|
.force("Full force", forceFull)
|
|
.restart();
|
|
}
|
|
|
|
return hybrid = {
|
|
restart: function () {
|
|
if(!initAlready) initialize();
|
|
simulation.restart();
|
|
return hybrid;
|
|
},
|
|
|
|
stop: function () {
|
|
simulation.stop();
|
|
return hybrid;
|
|
},
|
|
|
|
numPivots: function (_) {
|
|
return arguments.length ? (NUM_PIVOTS = +_, hybrid) : NUM_PIVOTS;
|
|
},
|
|
|
|
sampleIterations: function (_) {
|
|
return arguments.length ? (SAMPLE_ITERATIONS = +_, hybrid) : SAMPLE_ITERATIONS;
|
|
},
|
|
|
|
fullIterations: function (_) {
|
|
return arguments.length ? (FULL_ITERATIONS = +_, hybrid) : FULL_ITERATIONS;
|
|
},
|
|
|
|
interpFindTuneIts: function (_) {
|
|
return arguments.length ? (INTERP_FINE_ITS = +_, hybrid) : INTERP_FINE_ITS;
|
|
},
|
|
|
|
on: function (name, _) {
|
|
return arguments.length > 1 ? (event.on(name, _), hybrid) : event.on(name);
|
|
},
|
|
|
|
subSet: function (_) {
|
|
return arguments.length ? (sample = _, hybrid) : sample;
|
|
},
|
|
|
|
nonSubSet: function (_) {
|
|
return arguments.length ? (remainder = _, hybrid) : remainder;
|
|
},
|
|
|
|
interpDistanceFn: function (_) {
|
|
return arguments.length ? (interpDistanceFn = typeof _ === "function" ? _ : constant(+_), hybrid) : interpDistanceFn;
|
|
},
|
|
|
|
simulation: function (_) {
|
|
return arguments.length ? (toInit = true, simulation = _, hybrid) : simulation;
|
|
},
|
|
|
|
forceSample: function (_) {
|
|
return arguments.length ? (forceSample = _, initForces(), hybrid) : forceSample;
|
|
},
|
|
|
|
forceFull: function (_) {
|
|
return arguments.length ? (forceFull = _, initForces(), hybrid) : forceFull;
|
|
},
|
|
|
|
};
|
|
}
|