UNPKG

topola

Version:

Topola – online genealogy visualization

318 lines (317 loc) 15.3 kB
"use strict"; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KinshipChartRenderer = void 0; var d3_array_1 = require("d3-array"); var api_1 = require("./api"); var chart_util_1 = require("../chart-util"); var utils_1 = require("../utils"); var LINKS_BASE_OFFSET = 17; var PARENT_LINK_ANCHOR_X_OFFSET = 15; var SIBLING_LINK_ANCHOR_Y_OFFSET = 5; var SIBLING_LINK_STARTER_LENGTH = 7; var LINKS_SEPARATION = 6; var LINK_STUB_CIRCLE_R = 3; var KinshipChartRenderer = /** @class */ (function () { function KinshipChartRenderer(options) { this.options = options; this.util = new chart_util_1.ChartUtil(this.options); } KinshipChartRenderer.prototype.layOut = function (upRoot, downRoot) { var svg = this.util.getSvgForRendering(); // Add styles so that calculating text size is correct. if (svg.select('style').empty()) { svg.append('style').text(this.options.renderer.getCss()); } return [ this.util.layOutChart(upRoot, { flipVertically: true }), this.util.layOutChart(downRoot), ]; }; KinshipChartRenderer.prototype.render = function (upNodes, downNodes, rootsCount) { var _this = this; var allNodes = upNodes.concat(downNodes); var allNodesDeduped = allNodes.slice(1); // Remove duplicate start/center node // Prepare for rendering upNodes.forEach(function (node) { return _this.setLinkYs(node, true); }); downNodes.forEach(function (node) { return _this.setLinkYs(node, false); }); // Render chart var animationPromise = this.util.renderNodes(allNodesDeduped, this.util.getSvgForRendering()); this.renderLinks(allNodes); if (rootsCount > 1) { this.renderRootDummyAdditionalMarriageLinkStub(allNodes[0]); } var info = (0, chart_util_1.getChartInfo)(allNodesDeduped); this.util.updateSvgDimensions(info); return Object.assign(info, { animationPromise: animationPromise }); }; KinshipChartRenderer.prototype.renderLinks = function (nodes) { var _this = this; var svgg = this.util.getSvgForRendering().select('g'); var keyFn = function (d) { return d.data.id; }; // Render links var boundLinkNodes = svgg.selectAll('path.internode-link').data(nodes.filter(function (n) { return !!n.parent; }), keyFn); boundLinkNodes .enter() .insert('path', 'g') .attr('class', function (node) { return _this.cssClassForLink(node); }) .merge(boundLinkNodes) .attr('d', function (node) { var linkPoints = node.data.primaryMarriage ? _this.additionalMarriageLinkPoints(node) : _this.linkPoints(node.parent, node, node.data.linkFromParentType); return (0, utils_1.points2pathd)(linkPoints); }); boundLinkNodes.exit().remove(); // Render link stubs container "g" element var boundLinkStubNodes = svgg.selectAll('g.link-stubs').data(nodes.filter(function (n) { return n.data.duplicateOf || n.data.duplicated || n.data.primaryMarriage; }), keyFn); var linkStubNodesEnter = boundLinkStubNodes .enter() .insert('g', 'g') .attr('class', 'link-stubs'); boundLinkStubNodes.exit().remove(); // Render link stubs var boundLinkStubs = linkStubNodesEnter .merge(boundLinkStubNodes) .selectAll('g') .data(function (node) { return _this.nodeToLinkStubRenderInfos(node); }, function (d) { return d.linkType.toString(); }); boundLinkStubs .enter() .append('g') .call(function (g) { return g .append('path') .attr('class', function (d) { return _this.cssClassForLinkStub(d.linkType); }) .merge(boundLinkStubs.select('path.link-stub')) .attr('d', function (d) { return (0, utils_1.points2pathd)(d.points); }); }) .call(function (g) { return g .append('circle') .attr('r', LINK_STUB_CIRCLE_R) .style('stroke', 'black') .style('fill', 'none') .merge(boundLinkStubs.select('circle')) .attr('transform', function (d) { return "translate(".concat((0, utils_1.last)(d.points).x, ", ").concat((0, utils_1.last)(d.points).y + LINK_STUB_CIRCLE_R * d.treeDir, ")"); }); }); boundLinkStubs.exit().remove(); }; KinshipChartRenderer.prototype.cssClassForLink = function (fromNode) { if (fromNode.data.primaryMarriage) { return 'link internode-link additional-marriage'; } return ('link internode-link ' + this.cssClassForLinkType(fromNode.data.linkFromParentType)); }; KinshipChartRenderer.prototype.cssClassForLinkStub = function (linkType) { return 'link link-stub ' + this.cssClassForLinkType(linkType); }; KinshipChartRenderer.prototype.cssClassForLinkType = function (linkType) { switch (linkType) { case api_1.LinkType.IndiParents: case api_1.LinkType.SpouseParents: return 'parents-link'; case api_1.LinkType.IndiSiblings: case api_1.LinkType.SpouseSiblings: return 'siblings-link'; case api_1.LinkType.Children: return 'children-link'; } }; KinshipChartRenderer.prototype.nodeToLinkStubRenderInfos = function (node) { var _this = this; return node.data.linkStubs.map(function (linkType) { var isUpTree = node.y < node.parent.y; var treeDir = isUpTree ? -1 : 1; var anchorPoints = _this.linkAnchorPoints(node, linkType, isUpTree); var y = node.data.linkYs.children - (2 * LINKS_SEPARATION + 2 * LINK_STUB_CIRCLE_R) * treeDir; return { treeDir: treeDir, linkType: linkType, points: __spreadArray(__spreadArray([], anchorPoints, true), [{ x: (0, utils_1.last)(anchorPoints).x, y: y }], false), }; }); }; KinshipChartRenderer.prototype.getLinkY = function (node, type) { switch (type) { case api_1.LinkType.IndiParents: return node.data.linkYs.indi; case api_1.LinkType.IndiSiblings: return node.data.linkYs.indi; case api_1.LinkType.SpouseParents: return node.data.linkYs.spouse; case api_1.LinkType.SpouseSiblings: return node.data.linkYs.spouse; case api_1.LinkType.Children: return node.data.linkYs.children; } }; KinshipChartRenderer.prototype.setLinkYs = function (node, isUpTree) { var treeDir = isUpTree ? -1 : 1; var base = node.y + (node.data.height / 2 + LINKS_BASE_OFFSET) * treeDir; var offset = LINKS_SEPARATION * treeDir; var _a = this.calcLinkOffsetDirs(node), indiOffsetDir = _a[0], spouseOffsetDir = _a[1]; node.data.linkYs = { indi: base + offset * indiOffsetDir, spouse: base + offset * spouseOffsetDir, children: base, }; }; /*** * Calculates indi (indiParent and indiSiblings) and spouse (spouseParent and spouseSiblings) * links offset directions, so they don't merge/collide with children links and with each other. ***/ KinshipChartRenderer.prototype.calcLinkOffsetDirs = function (node) { var childNodes = node.data.childNodes; if (childNodes.children.length) { // Check children-indi and children-spouse links collisions var indiParentLinkAnchorX = this.linkAnchorPoints(node, api_1.LinkType.IndiParents, true)[0].x; var spouseParentLinkAnchorX = this.linkAnchorPoints(node, api_1.LinkType.SpouseParents, true)[0].x; var childrenLinksX = { min: this.findMinXOfChildNodesAnchors(node, childNodes.children), max: this.findMaxXOfChildNodesAnchors(node, childNodes.children), }; if (childrenLinksX.min < indiParentLinkAnchorX && childrenLinksX.max > spouseParentLinkAnchorX) { return [-1, -1]; // This shouldn't happen! It can't happen with start node, because start node have children links going down and other links going up. It can't happen with non-start node, as there can't be outgoing indi, spouse and children links at the same time on non-start node. -- But.. It might be useful to not remove it, so that this function might be used when constructing links for other types of charts. } else if (childrenLinksX.min < indiParentLinkAnchorX) { return [-1, 1]; } else if (childrenLinksX.max > spouseParentLinkAnchorX) { return [1, -1]; } } else if ((childNodes.indiParents.length || childNodes.indiSiblings.length) && (childNodes.spouseParents.length || childNodes.spouseSiblings.length)) { // Check indi-spouse links collision var indiParentLinkAnchorX = this.linkAnchorPoints(node, api_1.LinkType.IndiParents, true)[0].x; var spouseLinksMinX = this.findMinXOfChildNodesAnchors(node, childNodes.spouseSiblings.concat(childNodes.spouseParents)); if (spouseLinksMinX < indiParentLinkAnchorX) { return [-1, 1]; } } return [1, -1]; }; KinshipChartRenderer.prototype.findMinXOfChildNodesAnchors = function (parentNode, childNodes) { return this.findExtremeXOfChildNodesAnchors(parentNode, childNodes, true); }; KinshipChartRenderer.prototype.findMaxXOfChildNodesAnchors = function (parentNode, childNodes) { return this.findExtremeXOfChildNodesAnchors(parentNode, childNodes, false); }; KinshipChartRenderer.prototype.findExtremeXOfChildNodesAnchors = function (parentNode, childNodes, isMin) { var extremeFindingFunction = isMin ? d3_array_1.min : d3_array_1.max; var dir = isMin ? -1 : 1; var childNodesSet = new Set(childNodes); return (extremeFindingFunction(parentNode.children.filter(function (n) { return childNodesSet.has(n.data); }), function (n) { return n.x + (dir * n.data.width) / 2; }) + dir * SIBLING_LINK_STARTER_LENGTH); }; KinshipChartRenderer.prototype.linkPoints = function (from, to, type) { var isUpTree = from.y > to.y; var pointsFrom = this.linkAnchorPoints(from, type, isUpTree); var pointsTo = this.linkAnchorPoints(to, (0, api_1.otherSideLinkType)(type), !isUpTree).reverse(); var y = this.getLinkY(from, type); return __spreadArray(__spreadArray(__spreadArray([], pointsFrom, true), [ { x: pointsFrom[pointsFrom.length - 1].x, y: y }, { x: pointsTo[0].x, y: y } ], false), pointsTo, true); }; KinshipChartRenderer.prototype.additionalMarriageLinkPoints = function (node) { var nodeIndex = node.parent.children.findIndex(function (n) { return n.data.id === node.data.id; }); var prevSiblingNode = node.parent.children[nodeIndex - 1]; var y = this.indiMidY(node); return [ { x: prevSiblingNode.x, y: y }, { x: node.x, y: y }, ]; }; KinshipChartRenderer.prototype.linkAnchorPoints = function (node, type, top) { var _a = [node.x, node.y], x = _a[0], y = _a[1]; var _b = [node.data.width, node.data.height], w = _b[0], h = _b[1]; var leftEdge = x - w / 2; var rightEdge = x + w / 2; var _c = [ node.data.indi, node.data.spouse, node.data.family, ].map(function (e) { return (e ? e.width : 0); }), indiW = _c[0], spouseW = _c[1], familyW = _c[2]; var indisW = indiW + spouseW; var indisLeftEdge = x - w / 2 + (familyW > indisW ? (familyW - indisW) / 2 : 0); var indisRightEdge = indisLeftEdge + indisW; var siblingAnchorY = this.indiMidY(node) + SIBLING_LINK_ANCHOR_Y_OFFSET * (top ? -1 : 1); switch (type) { case api_1.LinkType.IndiParents: return [ { x: indisLeftEdge + PARENT_LINK_ANCHOR_X_OFFSET, y: y - h / 2 }, ]; case api_1.LinkType.SpouseParents: return [ { x: indisRightEdge - PARENT_LINK_ANCHOR_X_OFFSET, y: y - h / 2 }, ]; case api_1.LinkType.IndiSiblings: return [ { x: indisLeftEdge, y: siblingAnchorY }, { x: (familyW > indisW && !top ? leftEdge : indisLeftEdge) - SIBLING_LINK_STARTER_LENGTH, y: siblingAnchorY, }, ]; case api_1.LinkType.SpouseSiblings: return [ { x: indisRightEdge, y: siblingAnchorY }, { x: (familyW > indisW && !top ? rightEdge : indisRightEdge) + SIBLING_LINK_STARTER_LENGTH, y: siblingAnchorY, }, ]; case api_1.LinkType.Children: return [ { x: indisLeftEdge + (node.data.spouse ? indiW : indiW / 2), y: y }, ]; } }; KinshipChartRenderer.prototype.indiMidY = function (node) { return node.y - node.data.height / 2 + node.data.indi.height / 2; }; KinshipChartRenderer.prototype.renderRootDummyAdditionalMarriageLinkStub = function (root) { var svgg = this.util.getSvgForRendering().select('g'); var y = this.indiMidY(root); var x = root.data.width / 2 + 20; var r = 3; svgg.selectAll('.root-dummy-additional-marriage').remove(); svgg .insert('g', 'g') .attr('class', 'root-dummy-additional-marriage') .call(function (g) { return g .append('path') .attr('d', "M 0 ".concat(y, " L ").concat(x, " ").concat(y)) .attr('class', 'link additional-marriage'); }) .call(function (g) { return g .append('circle') .attr('transform', "translate(".concat(x + r, ", ").concat(y, ")")) .attr('r', r) .style('stroke', 'black') .style('fill', 'black'); }); }; return KinshipChartRenderer; }()); exports.KinshipChartRenderer = KinshipChartRenderer;