function dvBeeswarmForce(elem,data,width,height,sizeField,options) {
nodes.length = 0;
if (typeof(options) === "undefined") {var options = {}}
if (typeof(options.strength) === "undefined") {options.strength = -1}
if (typeof(options.kFactor) === "undefined") {options.kFactor = 0.1}
if (typeof(options.collisionFactor) === "undefined") {options.collisionFactor = 1.1}
if (typeof(options.alphaTarget) === "undefined") {options.alphaTarget = 0.1}
if (typeof(options.alphaMin) === "undefined") {options.alphaMin = 0.1}
if (options.alphaTarget > options.alphaMin) {console.log("Warning: alphaTarget > alphaMin. Simulation will run forever.")}
if (typeof(options.maxRadius) === "undefined" || options.maxRadius == '') {options.maxRadius = 20}
if (typeof(options.dotScaleFactor) === "undefined" || options.dotScaleFactor == '') {options.dotScaleFactor = 0.005}
if (typeof(options.axisScaleFunction) === "undefined") {
let scaleField;
if (typeof(options.axisField) === "undefined") {
options.axisScaleFunction = d3.scaleLinear().domain([0,1000]).range([0,width]);
} else {
if (typeof(options.axisField) === "string") {scaleField = options.axisField}
options.axisScaleFunction = d3.scaleLinear().domain(d3.extent(data, d => d[scaleField])).range([0,width]);
}
}
if (typeof(options.direction) === "undefined") {options.direction = "horizontal"}
if (typeof(options.fociPositions) === "undefined") {
if (options.direction == "horizontal" || options.direction == "h") {options.fociPositions = {default: height/2}}
else if (options.direction == "vertical" || options.direction == "v") {options.fociPositions = {default: width/2}}
}
mutable foci = options.fociPositions;
force
.force('nbody', d3.forceManyBody().strength(options.strength))
.force('foci', alpha => {
for (var i = 0, n = nodes.length, o, k = alpha * options.kFactor; i < n; ++i) {
o = nodes[i];
if (typeof(options.axisField) !== "undefined" && options.axisField != "") {
if (options.direction == "horizontal" || options.direction == "h") {
o.vx += (o.axisPos - o.x) * k;
o.vy += (mutable foci[o.id] - o.y) * k;
} else if (options.direction == "vertical" || options.direction == "v") {
o.vx += (mutable foci[o.id] - o.x) * k;
o.vy += (o.axisPos - o.y) * k;
} else if (options.direction == "path") {
o.vx += (o.axisPos.x - o.x) * k;
o.vy += (o.axisPos.y - o.y) * k;
}
} else {
o.vx += (width/2 - o.x) * k;
o.vy += (height/2 - o.y) * k;
}
}
})
.force('collision', d3.forceCollide(function (d) {return options.collisionFactor*d.r;}))
.alphaTarget(options.alphaTarget)
.alphaMin(options.alphaMin)
.on("tick", tick);
// the tick is what updates their position.
function tick(e) {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// this creates the dots
function addDots(dot) {
let fociID;
let axisPosition;
var radius = rFromArea(dot[sizeField]) * options.dotScaleFactor;
if (isFinite(sizeField)) {radius = sizeField};
if (radius < 0 || isNaN(radius)) {radius = 1;}
if (typeof(options.fociField) !== "undefined") {fociID = dot[options.fociField]} else {fociID = "default";}
if (typeof(options.axisField) !== "undefined") {axisPosition = options.axisScaleFunction(toNum(dot[options.axisField]))} else {axisPosition = options.axisScaleFunction(0);}
nodes.push({
id: fociID,
axisPos: axisPosition,
x: 0, // could also set starting position to center or other input?
y: 0,
r: radius,
data: dot
});
}
function drawNodes() {
node = svgElem.selectAll("circle").data(nodes);
node = node.join("circle")
.attr("class", function(d) { return "node" + options.classField ? d.data[options.classField] : ""})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) {return d.r})
.style("fill", function(d) {return options.colorSet ? options.colorSet[d.id] : ""});
}
// draw it.
let svgElem = elem;
let node = svgElem.selectAll(".node");
data.forEach(function(d){addDots(d)}); // adds each time we run. would rather join.
force.nodes(nodes);
drawNodes();
return node;
}