import {Transform} from 'vega-dataflow'; import { accessorFields, array, error, hasOwnProperty, inherits, isFunction } from 'vega-util'; import { forceSimulation, forceCenter, forceCollide, forceManyBody, forceLink, forceX, forceY } from 'd3-force'; var ForceMap = { center: forceCenter, collide: forceCollide, nbody: forceManyBody, link: forceLink, x: forceX, y: forceY }; var Forces = 'forces', ForceParams = [ 'alpha', 'alphaMin', 'alphaTarget', 'velocityDecay', 'forces' ], ForceConfig = ['static', 'iterations'], ForceOutput = ['x', 'y', 'vx', 'vy']; /** * Force simulation layout. * @constructor * @param {object} params - The parameters for this operator. * @param {Array} params.forces - The forces to apply. */ export default function Force(params) { Transform.call(this, null, params); } Force.Definition = { "type": "Force", "metadata": {"modifies": true}, "params": [ { "name": "static", "type": "boolean", "default": false }, { "name": "restart", "type": "boolean", "default": false }, { "name": "iterations", "type": "number", "default": 300 }, { "name": "alpha", "type": "number", "default": 1 }, { "name": "alphaMin", "type": "number", "default": 0.001 }, { "name": "alphaTarget", "type": "number", "default": 0 }, { "name": "velocityDecay", "type": "number", "default": 0.4 }, { "name": "forces", "type": "param", "array": true, "params": [ { "key": {"force": "center"}, "params": [ { "name": "x", "type": "number", "default": 0 }, { "name": "y", "type": "number", "default": 0 } ] }, { "key": {"force": "collide"}, "params": [ { "name": "radius", "type": "number", "expr": true }, { "name": "strength", "type": "number", "default": 0.7 }, { "name": "iterations", "type": "number", "default": 1 } ] }, { "key": {"force": "nbody"}, "params": [ { "name": "strength", "type": "number", "default": -30 }, { "name": "theta", "type": "number", "default": 0.9 }, { "name": "distanceMin", "type": "number", "default": 1 }, { "name": "distanceMax", "type": "number" } ] }, { "key": {"force": "link"}, "params": [ { "name": "links", "type": "data" }, { "name": "id", "type": "field" }, { "name": "distance", "type": "number", "default": 30, "expr": true }, { "name": "strength", "type": "number", "expr": true }, { "name": "iterations", "type": "number", "default": 1 } ] }, { "key": {"force": "x"}, "params": [ { "name": "strength", "type": "number", "default": 0.1 }, { "name": "x", "type": "field" } ] }, { "key": {"force": "y"}, "params": [ { "name": "strength", "type": "number", "default": 0.1 }, { "name": "y", "type": "field" } ] } ] }, { "name": "as", "type": "string", "array": true, "modify": false, "default": ForceOutput } ] }; var prototype = inherits(Force, Transform); prototype.transform = function(_, pulse) { var sim = this.value, change = pulse.changed(pulse.ADD_REM), params = _.modified(ForceParams), iters = _.iterations || 300; // configure simulation if (!sim) { this.value = sim = simulation(pulse.source, _); sim.on('tick', rerun(pulse.dataflow, this)); if (!_.static) { change = true; sim.tick(); // ensure we run on init } pulse.modifies('index'); } else { if (change) { pulse.modifies('index'); sim.nodes(pulse.source); } if (params || pulse.changed(pulse.MOD)) { setup(sim, _, 0, pulse); } } // run simulation if (params || change || _.modified(ForceConfig) || (pulse.changed() && _.restart)) { sim.alpha(Math.max(sim.alpha(), _.alpha || 1)) .alphaDecay(1 - Math.pow(sim.alphaMin(), 1 / iters)); if (_.static) { for (sim.stop(); --iters >= 0;) sim.tick(); } else { if (sim.stopped()) sim.restart(); if (!change) return pulse.StopPropagation; // defer to sim ticks } } return this.finish(_, pulse); }; prototype.finish = function(_, pulse) { var dataflow = pulse.dataflow; // inspect dependencies, touch link source data for (var args=this._argops, j=0, m=args.length, arg; j