import constant from "./constant"; import jiggle from "./jiggle"; import {map} from "d3-collection"; /** * Extended link force algorithm to include the stress metric for * comparisons between the different algorithms. * Everything else is the same as in D3 force module. */ function index(d, i) { return i; } function find(nodeById, nodeId) { var node = nodeById.get(nodeId); if (!node) throw new Error("missing: " + nodeId); return node; } export default function(links) { var id = index, strength = defaultStrength, strengths, distance = constant(30), distances, nodes, count, bias, iterations = 1; if (links == null) links = []; function defaultStrength(link) { return 1 / Math.min(count[link.source.index], count[link.target.index]); } function force(alpha) { for (var k = 0, n = links.length; k < iterations; ++k) { for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { link = links[i], source = link.source, target = link.target; x = target.x + target.vx - source.x - source.vx || jiggle(); y = target.y + target.vy - source.y - source.vy || jiggle(); l = Math.sqrt(x * x + y * y); l = (l - distances[i]) / l * alpha/* * strengths[i]*/; x *= l, y *= l; target.vx -= x * (b = bias[i]); target.vy -= y * b; source.vx += x * (b = 1 - b); source.vy += y * b; } } } function initialize() { if (!nodes) return; var i, n = nodes.length, m = links.length, nodeById = map(nodes, id), link; for (i = 0, count = new Array(n); i < n; ++i) { count[i] = 0; } for (i = 0; i < m; ++i) { link = links[i], link.index = i; if (typeof link.source !== "object") link.source = find(nodeById, link.source); if (typeof link.target !== "object") link.target = find(nodeById, link.target); ++count[link.source.index], ++count[link.target.index]; } for (i = 0, bias = new Array(m); i < m; ++i) { link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]); } strengths = new Array(m), initializeStrength(); distances = new Array(m), initializeDistance(); } function initializeStrength() { if (!nodes) return; for (var i = 0, n = links.length; i < n; ++i) { strengths[i] = +strength(links[i], i, links); } } function initializeDistance() { if (!nodes) return; for (var i = 0, n = links.length; i < n; ++i) { distances[i] = +distance(links[i], i, links); } } /** * Calculates the stress. Basically, it computes the difference between * high dimensional distance and real distance. The lower the stress is, * the better layout. * @return {number} - stress of the layout. */ function getStress() { var m = links.length, totalDiffSq = 0, totalHighDistSq = 0, link; for (var i = 0, source, target, realDist, highDist; i < m; i++) { link = links[i], source = link.source, target = link.target; realDist = Math.hypot(target.x-source.x, target.y-source.y); highDist = distances[i]; totalDiffSq += Math.pow(realDist-highDist, 2); totalHighDistSq += highDist * highDist; } return Math.sqrt(totalDiffSq/totalHighDistSq); } force.initialize = function(_) { nodes = _; initialize(); }; force.links = function(_) { return arguments.length ? (links = _, initialize(), force) : links; }; force.id = function(_) { return arguments.length ? (id = _, force) : id; }; force.iterations = function(_) { return arguments.length ? (iterations = +_, force) : iterations; }; force.strength = function(_) { return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength; }; force.distance = function(_) { return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance; }; force.stress = function() { return getStress(); } return force; }