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); } // 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){ 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"); let p1 = performance.now(); if (NUM_PIVOTS>=1) { interpolationPivots(sample, remainder, NUM_PIVOTS, interpDistanceFn, INTERP_FINE_ITS); } else { interpBruteForce(sample, remainder, interpDistanceFn, INTERP_FINE_ITS); } let p2 = performance.now(); console.log("Interpolation time: " + (p2 - p1)); 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; }, }; }