UNPKG

basicprimitives

Version:

Basic Primitives Diagrams for JavaScript - data visualization components library that implements organizational chart and multi-parent dependency diagrams, contains implementations of JavaScript Controls and PDF rendering plugins.

237 lines (203 loc) 8.95 kB
import getFamilyUnits from './getFamilyUnits'; import FamilyMargins from './FamilyMargins'; /** * Callback function for getting family node width * * @callback onFamilyAlignmentItemSizeCallback * @param {string} itemid Family node id * @param {object} item Family node context object * @returns {number} Family node width */ /** * Creates family alignment data structure. This structure aligns horizontally planar family of nodes. * @class FamilyAlignment * * @param {Object} thisArg The callback function invocation context * @param {family} family Family data structure * @param {TreeLevels} treeLevels Three levels data structure * @param {onFamilyAlignmentItemSizeCallback} onItemSize Callback function to measure family node width * @returns {FamilyAlignment} Returns family alignment structure */ export default function FamilyAlignment(thisArg, family, treeLevels, onItemSize) { var offsets, sizes = {}, childrenDistances; if (onItemSize != null) { treeLevels.loopItems(this, function (itemid, item, position, levelIndex, level) { sizes[itemid] = onItemSize.call(thisArg, itemid, item); }); } childrenDistances = getDistancesBetweenChildren(family, treeLevels); offsets = getTreeLevelsOffsets(family, treeLevels, childrenDistances); function _getNodeMargins(margins, nodeid) { // create margins for node if it does not exists var nodeMargins = margins[nodeid]; if (nodeMargins == null) { nodeMargins = new FamilyMargins(); margins[nodeid] = nodeMargins; } return nodeMargins; } function getDistancesBetweenChildren(family, treeLevels) { var distances = {}; var margins = {}; var levelMargins = null; treeLevels.loopLevelsReversed(this, function (levelIndex, level) { var newMargins = new FamilyMargins(); if (levelMargins != null) { levelMargins.add(0, Number.MAX_VALUE); newMargins.merge(levelMargins, 0); } levelMargins = newMargins; var previousParentMargins = levelMargins; treeLevels.loopLevelItems(this, levelIndex, function (nodeid, node, position) { var nodeMargins = _getNodeMargins(margins, nodeid); // add node size into its margin nodeMargins.add(sizes[nodeid], position); switch (family.countParents(nodeid)) { case 0: if (previousParentMargins != null) { distances[nodeid] = previousParentMargins.attach(nodeMargins); } break; case 1: family.loopParents(this, nodeid, function (parentid, parent, levelIndex) { var parentMargins = _getNodeMargins(margins, parentid); distances[nodeid] = parentMargins.merge(nodeMargins); previousParentMargins = parentMargins; return family.BREAK; }); break; default: // loop parents and find total size of them var totalSize = 0; var fromIndex = null; var toIndex = null; var hash = {}; family.loopParents(this, nodeid, function (parentid, parent, levelIndex) { if (levelIndex > 0) { return family.BREAK; } totalSize += sizes[parentid]; var position = treeLevels.getItemPosition(parentid); fromIndex = fromIndex == null ? position : Math.min(fromIndex, position); toIndex = toIndex == null ? position : Math.max(toIndex, position); hash[position] = parentid; }); var offset = -totalSize / 2; for (var index = fromIndex; index <= toIndex; index += 1) { var parentid = hash[index]; offset += sizes[parentid] / 2.0; var parentMargins = _getNodeMargins(margins, parentid); parentMargins.attach(nodeMargins, -offset); previousParentMargins = parentMargins; offset += sizes[parentid] / 2.0; } break; } }); }); return distances; } function getTreeLevelsOffsets(family, treeLevels, childrenDistances) { var offsets = {}; var familyUnitsById = getFamilyUnits(family); var processedFamilyUnits = {}; treeLevels.loopLevels(this, function (levelIndex, level) { treeLevels.loopLevelItems(this, levelIndex, function (nodeid, node, position) { if (!offsets.hasOwnProperty(nodeid)) { var offset = 0; if (position === 0) { if (childrenDistances[nodeid] != null) { offset += childrenDistances[nodeid] + sizes[nodeid] / 2; } } else { var prevNodeId = treeLevels.getItemAtPosition(levelIndex, position - 1); offset += offsets[prevNodeId] + sizes[prevNodeId] / 2 + (childrenDistances[nodeid] || 0) + sizes[nodeid] / 2; } offsets[nodeid] = offset; } var familyUnits = familyUnitsById[nodeid]; if (familyUnits != null) { for (var index = 0; index < familyUnits.length; index += 1) { var familyUnit = familyUnits[index]; if (!processedFamilyUnits.hasOwnProperty(familyUnit.id)) { processedFamilyUnits[familyUnit.id] = true; setFamilyOffsets(offsets, nodeid, familyUnit, levelIndex, levelIndex + 1, position, treeLevels, childrenDistances); } } } }); }); return offsets; } function setFamilyOffsets(offsets, itemid, familyUnit, fromLevel, toLevel, itemIndex, treeLevels, childrenDistances) { var fromIndex = itemIndex; var toIndex = itemIndex; familyUnit.loopSiblings(this, itemid, function (siblingid) { var position = treeLevels.getItemPosition(siblingid); fromIndex = Math.min(fromIndex, position); toIndex = Math.max(toIndex, position); }); // Place nodes on the left side of start node for (var index = itemIndex - 1; index >= fromIndex; index -= 1) { var siblingid = treeLevels.getItemAtPosition(fromLevel, index); if (!offsets.hasOwnProperty(siblingid)) { var nodeid = treeLevels.getItemAtPosition(fromLevel, index + 1); offsets[siblingid] = offsets[nodeid] - (sizes[siblingid] / 2 + (childrenDistances[nodeid] || 0) + sizes[nodeid] / 2); } } // Place nodes on the right side of start node for (index = itemIndex + 1; index <= toIndex; index += 1) { siblingid = treeLevels.getItemAtPosition(fromLevel, index); if (!offsets.hasOwnProperty(siblingid)) { nodeid = treeLevels.getItemAtPosition(fromLevel, index - 1); offsets[siblingid] = offsets[nodeid] + (sizes[nodeid] / 2 + (childrenDistances[siblingid] || 0) + sizes[siblingid] / 2); } } siblingid = treeLevels.getItemAtPosition(fromLevel, fromIndex); var siblingsMedian = offsets[siblingid] - sizes[siblingid] / 2; siblingid = treeLevels.getItemAtPosition(fromLevel, toIndex); siblingsMedian += offsets[siblingid] + sizes[siblingid] / 2; siblingsMedian /= 2; fromIndex = null; toIndex = null; familyUnit.loopNonSiblings(this, itemid, function (siblingid) { var position = treeLevels.getItemPosition(siblingid); fromIndex = fromIndex != null ? Math.min(fromIndex, position) : position; toIndex = toIndex != null ? Math.max(toIndex, position) : position; }); var nonSiblingsWidth = 0; for (index = fromIndex; index <= toIndex; index += 1) { var relatedItemId = treeLevels.getItemAtPosition(toLevel, index); nonSiblingsWidth += sizes[relatedItemId]; if (index > fromIndex) { nonSiblingsWidth += (childrenDistances[relatedItemId] || 0); } } var offset = siblingsMedian - nonSiblingsWidth / 2; relatedItemId = treeLevels.getItemAtPosition(toLevel, fromIndex); if (!offsets.hasOwnProperty(relatedItemId)) { offsets[relatedItemId] = offset + sizes[relatedItemId] / 2; } for (index = fromIndex + 1; index <= toIndex; index += 1) { relatedItemId = treeLevels.getItemAtPosition(toLevel, index); if (!offsets.hasOwnProperty(relatedItemId)) { nodeid = treeLevels.getItemAtPosition(toLevel, index - 1); offsets[relatedItemId] = offsets[nodeid] + (sizes[nodeid] / 2 + (childrenDistances[relatedItemId] || 0) + sizes[relatedItemId] / 2); } } } /** * Returns horizontal node offset from left margin of the family diagram * * @param {string} nodeid Family node id * @returns {number} Node offset */ function getOffset(nodeid) { return offsets[nodeid]; } return { getOffset: getOffset }; };