UNPKG

@data-ui/xy-chart

Version:

A package of charts with standard x- and y- axes. https://williaster.github.io/data-ui

295 lines (245 loc) 7.15 kB
"use strict"; exports.__esModule = true; exports.default = computeCirclePack; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* eslint no-param-reassign: 0 */ /** * The algorthm is an implementation of the paper * Wang et al. Visualization of large hierarchical data by circle packing. * by adding horizontal constrains for each circle. * Assume that the position of x and the size of circles have been scaled. * And each circle has the following properties: * x: the position of x, * size: the radius, */ var DEFAULT_POINT_SIZE = 4; function packCircles(data, xScale, getSize) { if (getSize === void 0) { getSize = function getSize(d) { return d.size || DEFAULT_POINT_SIZE; }; } var packBounds = []; var packOutline = []; var globalMinX = xScale(data[0].x); var globalMaxX = xScale(data[data.length - 1].x); var rect = { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }; var outline = { up: [], down: [] }; function bound(node, bd) { bd.xMin = Math.min(node.px - node.r, bd.xMin); bd.xMax = Math.max(node.px + node.r, bd.xMax); bd.yMin = Math.min(node.py - node.r, bd.yMin); bd.yMax = Math.max(node.py + node.r, bd.yMax); } function outside(node, bd) { return node.x + node.r < bd.xMin || node.x - node.r > bd.xMax; } function xpackBestPlace(startn, node) { var goodnodes = []; for (var p = startn.nextPack; p !== startn; p = p.nextPack) { if (!(p.px + p.r < node.x - node.r || p.px - p.r > node.x + node.r || p.px - p.r - node.r < globalMinX || p.px + p.r + node.r > globalMaxX)) { goodnodes.push(p); } } if (goodnodes.length === 0) { return startn; } goodnodes.sort(function (a, b) { return Math.abs(a.py) - Math.abs(b.py); }); return goodnodes[0]; } function xpackInsert(a, b) { var c = a.nextPack; a.nextPack = b; b.prevPack = a; b.nextPack = c; c.prevPack = b; } function xpackPlace(a, b, c) { var db = a.r + c.r; var dx = b.px - a.px; var dy = b.py - a.py; if (db && (dx || dy)) { var da = b.r + c.r; var dc = dx * dx + dy * dy; da *= da; db *= db; var x = 0.5 + (db - da) / (2 * dc); // eslint-disable-line no-magic-numbers var y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); c.px = a.px + x * dx + y * dy; c.py = a.py + x * dy - y * dx; } else { c.px = a.px + db; c.py = a.py; } } function xpackIntersects(a, b) { var dx = b.px - a.px; var dy = b.py - a.py; var dr = a.r + b.r; return 0.999 * dr * dr > dx * dx + dy * dy; // eslint-disable-line no-magic-numbers } function xpackSplice(a, b) { a.nextPack = b; b.prevPack = a; } function xpackLink(node) { node.nextPack = node; node.prevPack = node; } function packNodes(nodes, start, n) { var bd = packBounds[n]; var a; var b; var c; var i; var j; var k; if (nodes.length - start >= 1) { // the first node a = nodes[start]; a.px = a.x; a.py = 0; bound(a, bd); if (nodes.length - start >= 2) { // the second node if (outside(nodes[start + 1], bd)) { return start + 1; } b = nodes[start + 1]; b.px = a.x + a.r + b.r; b.py = 0; bound(b, bd); xpackInsert(a, b); if (nodes.length - start >= 3) { // the third node if (outside(nodes[start + 2], bd)) { return start + 2; } c = nodes[start + 2]; xpackPlace(a, b, c); bound(c, bd); xpackInsert(a, c); // iterate through the rest var preiter = false; for (i = start + 3; i < nodes.length; i += 1) { if (!preiter) { if (outside(nodes[i], bd)) { return i; } a = xpackBestPlace(a, nodes[i]); b = a.nextPack; } xpackPlace(a, b, c = nodes[i]); // search for the closest intersection var isect = 0; var s1 = 1; var s2 = 1; for (j = b.nextPack; j !== b; j = j.nextPack, s1 += 1) { if (xpackIntersects(j, c)) { isect = 1; break; } } if (isect === 1) { for (k = a.prevPack; k !== j.prevPack; k = k.prevPack, s2 += 1) { if (xpackIntersects(k, c)) { break; } } } // update front chain if (isect) { if (s1 < s2 || s1 === s2 && b.r < a.r) { xpackSplice(a, b = j); } else { xpackSplice(a = k, b); } i -= 1; preiter = true; } else { xpackInsert(a, c); b = c; bound(c, bd); preiter = false; } } } } } return nodes.length; } if (data.length === 0) { return data; } var nodes = []; data.forEach(function (node) { nodes.push(_extends({}, node, { x: xScale(node.x), r: getSize(node) })); }); for (var _i = 0; _i < nodes.length; _i += 1) { xpackLink(nodes[_i]); } var i = 0; var n = 0; while (i < nodes.length) { packBounds.push({ xMin: Infinity, yMin: Infinity, xMax: -Infinity, yMax: -Infinity }); // pack this nodes group i = packNodes(nodes, i, n); // get the packing outline packOutline.push(nodes[i - 1]); for (var p = nodes[i - 1].nextPack; p !== nodes[i - 1]; p = p.nextPack) { packOutline.push(p); } n += 1; } rect.xMin = Infinity; rect.xMax = -Infinity; rect.yMin = Infinity; rect.yMax = -Infinity; packBounds.forEach(function (bd) { rect.xMin = Math.min(rect.xMin, bd.xMin); rect.xMax = Math.max(rect.xMax, bd.xMax); rect.yMin = Math.min(rect.yMin, bd.yMin); rect.yMax = Math.max(rect.yMax, bd.yMax); }); // compute the global outline packOutline.sort(function (a, b) { return a.px - b.px; }); packOutline.forEach(function (p) { if (p.py < 0) { outline.up.push(p); } else if (p.py > 0) { outline.down.push(p); } else { outline.up.push(p); outline.down.push(p); } }); return nodes; } function computeCirclePack(data, xScale, getSize) { var sorted = data.sort(function (a, b) { return a.x - b.x; }); var calculatedNodes = packCircles(data, xScale, getSize); var result = []; for (var i = 0; i < calculatedNodes.length; i += 1) { result.push(_extends({}, sorted[i], { x: xScale.invert(calculatedNodes[i].px), y: calculatedNodes[i].py })); } return result; }