UNPKG

pareto-anywhere

Version:

Open source IoT middleware suite that makes the data from just about anything usable. We believe in an open Internet of Things.

1,346 lines (1,134 loc) 119 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("layout-base")); else if(typeof define === 'function' && define.amd) define(["layout-base"], factory); else if(typeof exports === 'object') exports["coseBase"] = factory(require("layout-base")); else root["coseBase"] = factory(root["layoutBase"]); })(this, function(__WEBPACK_EXTERNAL_MODULE__551__) { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 45: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var coseBase = {}; coseBase.layoutBase = __webpack_require__(551); coseBase.CoSEConstants = __webpack_require__(806); coseBase.CoSEEdge = __webpack_require__(767); coseBase.CoSEGraph = __webpack_require__(880); coseBase.CoSEGraphManager = __webpack_require__(578); coseBase.CoSELayout = __webpack_require__(765); coseBase.CoSENode = __webpack_require__(991); coseBase.ConstraintHandler = __webpack_require__(902); module.exports = coseBase; /***/ }), /***/ 806: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var FDLayoutConstants = __webpack_require__(551).FDLayoutConstants; function CoSEConstants() {} //CoSEConstants inherits static props in FDLayoutConstants for (var prop in FDLayoutConstants) { CoSEConstants[prop] = FDLayoutConstants[prop]; } CoSEConstants.DEFAULT_USE_MULTI_LEVEL_SCALING = false; CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH; CoSEConstants.DEFAULT_COMPONENT_SEPERATION = 60; CoSEConstants.TILE = true; CoSEConstants.TILING_PADDING_VERTICAL = 10; CoSEConstants.TILING_PADDING_HORIZONTAL = 10; CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true; CoSEConstants.ENFORCE_CONSTRAINTS = true; CoSEConstants.APPLY_LAYOUT = true; CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS = true; CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true; // this should be set to false if there will be a constraint // This constant is for differentiating whether actual layout algorithm that uses cose-base wants to apply only incremental layout or // an incremental layout on top of a randomized layout. If it is only incremental layout, then this constant should be true. CoSEConstants.PURE_INCREMENTAL = CoSEConstants.DEFAULT_INCREMENTAL; module.exports = CoSEConstants; /***/ }), /***/ 767: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var FDLayoutEdge = __webpack_require__(551).FDLayoutEdge; function CoSEEdge(source, target, vEdge) { FDLayoutEdge.call(this, source, target, vEdge); } CoSEEdge.prototype = Object.create(FDLayoutEdge.prototype); for (var prop in FDLayoutEdge) { CoSEEdge[prop] = FDLayoutEdge[prop]; } module.exports = CoSEEdge; /***/ }), /***/ 880: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var LGraph = __webpack_require__(551).LGraph; function CoSEGraph(parent, graphMgr, vGraph) { LGraph.call(this, parent, graphMgr, vGraph); } CoSEGraph.prototype = Object.create(LGraph.prototype); for (var prop in LGraph) { CoSEGraph[prop] = LGraph[prop]; } module.exports = CoSEGraph; /***/ }), /***/ 578: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var LGraphManager = __webpack_require__(551).LGraphManager; function CoSEGraphManager(layout) { LGraphManager.call(this, layout); } CoSEGraphManager.prototype = Object.create(LGraphManager.prototype); for (var prop in LGraphManager) { CoSEGraphManager[prop] = LGraphManager[prop]; } module.exports = CoSEGraphManager; /***/ }), /***/ 765: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var FDLayout = __webpack_require__(551).FDLayout; var CoSEGraphManager = __webpack_require__(578); var CoSEGraph = __webpack_require__(880); var CoSENode = __webpack_require__(991); var CoSEEdge = __webpack_require__(767); var CoSEConstants = __webpack_require__(806); var ConstraintHandler = __webpack_require__(902); var FDLayoutConstants = __webpack_require__(551).FDLayoutConstants; var LayoutConstants = __webpack_require__(551).LayoutConstants; var Point = __webpack_require__(551).Point; var PointD = __webpack_require__(551).PointD; var DimensionD = __webpack_require__(551).DimensionD; var Layout = __webpack_require__(551).Layout; var Integer = __webpack_require__(551).Integer; var IGeometry = __webpack_require__(551).IGeometry; var LGraph = __webpack_require__(551).LGraph; var Transform = __webpack_require__(551).Transform; var LinkedList = __webpack_require__(551).LinkedList; function CoSELayout() { FDLayout.call(this); this.toBeTiled = {}; // Memorize if a node is to be tiled or is tiled this.constraints = {}; // keep layout constraints } CoSELayout.prototype = Object.create(FDLayout.prototype); for (var prop in FDLayout) { CoSELayout[prop] = FDLayout[prop]; } CoSELayout.prototype.newGraphManager = function () { var gm = new CoSEGraphManager(this); this.graphManager = gm; return gm; }; CoSELayout.prototype.newGraph = function (vGraph) { return new CoSEGraph(null, this.graphManager, vGraph); }; CoSELayout.prototype.newNode = function (vNode) { return new CoSENode(this.graphManager, vNode); }; CoSELayout.prototype.newEdge = function (vEdge) { return new CoSEEdge(null, null, vEdge); }; CoSELayout.prototype.initParameters = function () { FDLayout.prototype.initParameters.call(this, arguments); if (!this.isSubLayout) { if (CoSEConstants.DEFAULT_EDGE_LENGTH < 10) { this.idealEdgeLength = 10; } else { this.idealEdgeLength = CoSEConstants.DEFAULT_EDGE_LENGTH; } this.useSmartIdealEdgeLengthCalculation = CoSEConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION; this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH; this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH; this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR; this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR; // variables for tree reduction support this.prunedNodesAll = []; this.growTreeIterations = 0; this.afterGrowthIterations = 0; this.isTreeGrowing = false; this.isGrowthFinished = false; } }; // This method is used to set CoSE related parameters used by spring embedder. CoSELayout.prototype.initSpringEmbedder = function () { FDLayout.prototype.initSpringEmbedder.call(this); // variables for cooling this.coolingCycle = 0; this.maxCoolingCycle = this.maxIterations / FDLayoutConstants.CONVERGENCE_CHECK_PERIOD; this.finalTemperature = 0.04; this.coolingAdjuster = 1; }; CoSELayout.prototype.layout = function () { var createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; if (createBendsAsNeeded) { this.createBendpoints(); this.graphManager.resetAllEdges(); } this.level = 0; return this.classicLayout(); }; CoSELayout.prototype.classicLayout = function () { this.nodesWithGravity = this.calculateNodesToApplyGravitationTo(); this.graphManager.setAllNodesToApplyGravitation(this.nodesWithGravity); this.calcNoOfChildrenForAllNodes(); this.graphManager.calcLowestCommonAncestors(); this.graphManager.calcInclusionTreeDepths(); this.graphManager.getRoot().calcEstimatedSize(); this.calcIdealEdgeLengths(); if (!this.incremental) { var forest = this.getFlatForest(); // The graph associated with this layout is flat and a forest if (forest.length > 0) { this.positionNodesRadially(forest); } // The graph associated with this layout is not flat or a forest else { // Reduce the trees when incremental mode is not enabled and graph is not a forest this.reduceTrees(); // Update nodes that gravity will be applied this.graphManager.resetAllNodesToApplyGravitation(); var allNodes = new Set(this.getAllNodes()); var intersection = this.nodesWithGravity.filter(function (x) { return allNodes.has(x); }); this.graphManager.setAllNodesToApplyGravitation(intersection); this.positionNodesRandomly(); } } else { if (CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL) { // Reduce the trees in incremental mode if only this constant is set to true this.reduceTrees(); // Update nodes that gravity will be applied this.graphManager.resetAllNodesToApplyGravitation(); var allNodes = new Set(this.getAllNodes()); var intersection = this.nodesWithGravity.filter(function (x) { return allNodes.has(x); }); this.graphManager.setAllNodesToApplyGravitation(intersection); } } if (Object.keys(this.constraints).length > 0) { ConstraintHandler.handleConstraints(this); this.initConstraintVariables(); } this.initSpringEmbedder(); if (CoSEConstants.APPLY_LAYOUT) { this.runSpringEmbedder(); } return true; }; CoSELayout.prototype.tick = function () { this.totalIterations++; if (this.totalIterations === this.maxIterations && !this.isTreeGrowing && !this.isGrowthFinished) { if (this.prunedNodesAll.length > 0) { this.isTreeGrowing = true; } else { return true; } } if (this.totalIterations % FDLayoutConstants.CONVERGENCE_CHECK_PERIOD == 0 && !this.isTreeGrowing && !this.isGrowthFinished) { if (this.isConverged()) { if (this.prunedNodesAll.length > 0) { this.isTreeGrowing = true; } else { return true; } } this.coolingCycle++; if (this.layoutQuality == 0) { // quality - "draft" this.coolingAdjuster = this.coolingCycle; } else if (this.layoutQuality == 1) { // quality - "default" this.coolingAdjuster = this.coolingCycle / 3; } // cooling schedule is based on http://www.btluke.com/simanf1.html -> cooling schedule 3 this.coolingFactor = Math.max(this.initialCoolingFactor - Math.pow(this.coolingCycle, Math.log(100 * (this.initialCoolingFactor - this.finalTemperature)) / Math.log(this.maxCoolingCycle)) / 100 * this.coolingAdjuster, this.finalTemperature); this.animationPeriod = Math.ceil(this.initialAnimationPeriod * Math.sqrt(this.coolingFactor)); } // Operations while tree is growing again if (this.isTreeGrowing) { if (this.growTreeIterations % 10 == 0) { if (this.prunedNodesAll.length > 0) { this.graphManager.updateBounds(); this.updateGrid(); this.growTree(this.prunedNodesAll); // Update nodes that gravity will be applied this.graphManager.resetAllNodesToApplyGravitation(); var allNodes = new Set(this.getAllNodes()); var intersection = this.nodesWithGravity.filter(function (x) { return allNodes.has(x); }); this.graphManager.setAllNodesToApplyGravitation(intersection); this.graphManager.updateBounds(); this.updateGrid(); if (CoSEConstants.PURE_INCREMENTAL) this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL / 2;else this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL; } else { this.isTreeGrowing = false; this.isGrowthFinished = true; } } this.growTreeIterations++; } // Operations after growth is finished if (this.isGrowthFinished) { if (this.isConverged()) { return true; } if (this.afterGrowthIterations % 10 == 0) { this.graphManager.updateBounds(); this.updateGrid(); } if (CoSEConstants.PURE_INCREMENTAL) this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL / 2 * ((100 - this.afterGrowthIterations) / 100);else this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL * ((100 - this.afterGrowthIterations) / 100); this.afterGrowthIterations++; } var gridUpdateAllowed = !this.isTreeGrowing && !this.isGrowthFinished; var forceToNodeSurroundingUpdate = this.growTreeIterations % 10 == 1 && this.isTreeGrowing || this.afterGrowthIterations % 10 == 1 && this.isGrowthFinished; this.totalDisplacement = 0; this.graphManager.updateBounds(); this.calcSpringForces(); this.calcRepulsionForces(gridUpdateAllowed, forceToNodeSurroundingUpdate); this.calcGravitationalForces(); this.moveNodes(); this.animate(); return false; // Layout is not ended yet return false }; CoSELayout.prototype.getPositionsData = function () { var allNodes = this.graphManager.getAllNodes(); var pData = {}; for (var i = 0; i < allNodes.length; i++) { var rect = allNodes[i].rect; var id = allNodes[i].id; pData[id] = { id: id, x: rect.getCenterX(), y: rect.getCenterY(), w: rect.width, h: rect.height }; } return pData; }; CoSELayout.prototype.runSpringEmbedder = function () { this.initialAnimationPeriod = 25; this.animationPeriod = this.initialAnimationPeriod; var layoutEnded = false; // If aminate option is 'during' signal that layout is supposed to start iterating if (FDLayoutConstants.ANIMATE === 'during') { this.emit('layoutstarted'); } else { // If aminate option is 'during' tick() function will be called on index.js while (!layoutEnded) { layoutEnded = this.tick(); } this.graphManager.updateBounds(); } }; // overrides moveNodes method in FDLayout CoSELayout.prototype.moveNodes = function () { var lNodes = this.getAllNodes(); var node; // calculate displacement for each node for (var i = 0; i < lNodes.length; i++) { node = lNodes[i]; node.calculateDisplacement(); } if (Object.keys(this.constraints).length > 0) { this.updateDisplacements(); } // move each node for (var i = 0; i < lNodes.length; i++) { node = lNodes[i]; node.move(); } }; // constraint related methods: initConstraintVariables and updateDisplacements // initialize constraint related variables CoSELayout.prototype.initConstraintVariables = function () { var self = this; this.idToNodeMap = new Map(); this.fixedNodeSet = new Set(); var allNodes = this.graphManager.getAllNodes(); // fill idToNodeMap for (var i = 0; i < allNodes.length; i++) { var node = allNodes[i]; this.idToNodeMap.set(node.id, node); } // calculate fixed node weight for given compound node var calculateCompoundWeight = function calculateCompoundWeight(compoundNode) { var nodes = compoundNode.getChild().getNodes(); var node; var fixedNodeWeight = 0; for (var i = 0; i < nodes.length; i++) { node = nodes[i]; if (node.getChild() == null) { if (self.fixedNodeSet.has(node.id)) { fixedNodeWeight += 100; } } else { fixedNodeWeight += calculateCompoundWeight(node); } } return fixedNodeWeight; }; if (this.constraints.fixedNodeConstraint) { // fill fixedNodeSet this.constraints.fixedNodeConstraint.forEach(function (nodeData) { self.fixedNodeSet.add(nodeData.nodeId); }); // assign fixed node weights to compounds if they contain fixed nodes var allNodes = this.graphManager.getAllNodes(); var node; for (var i = 0; i < allNodes.length; i++) { node = allNodes[i]; if (node.getChild() != null) { var fixedNodeWeight = calculateCompoundWeight(node); if (fixedNodeWeight > 0) { node.fixedNodeWeight = fixedNodeWeight; } } } } if (this.constraints.relativePlacementConstraint) { var nodeToDummyForVerticalAlignment = new Map(); var nodeToDummyForHorizontalAlignment = new Map(); this.dummyToNodeForVerticalAlignment = new Map(); this.dummyToNodeForHorizontalAlignment = new Map(); this.fixedNodesOnHorizontal = new Set(); this.fixedNodesOnVertical = new Set(); // fill maps and sets this.fixedNodeSet.forEach(function (nodeId) { self.fixedNodesOnHorizontal.add(nodeId); self.fixedNodesOnVertical.add(nodeId); }); if (this.constraints.alignmentConstraint) { if (this.constraints.alignmentConstraint.vertical) { var verticalAlignment = this.constraints.alignmentConstraint.vertical; for (var i = 0; i < verticalAlignment.length; i++) { this.dummyToNodeForVerticalAlignment.set("dummy" + i, []); verticalAlignment[i].forEach(function (nodeId) { nodeToDummyForVerticalAlignment.set(nodeId, "dummy" + i); self.dummyToNodeForVerticalAlignment.get("dummy" + i).push(nodeId); if (self.fixedNodeSet.has(nodeId)) { self.fixedNodesOnHorizontal.add("dummy" + i); } }); } } if (this.constraints.alignmentConstraint.horizontal) { var horizontalAlignment = this.constraints.alignmentConstraint.horizontal; for (var i = 0; i < horizontalAlignment.length; i++) { this.dummyToNodeForHorizontalAlignment.set("dummy" + i, []); horizontalAlignment[i].forEach(function (nodeId) { nodeToDummyForHorizontalAlignment.set(nodeId, "dummy" + i); self.dummyToNodeForHorizontalAlignment.get("dummy" + i).push(nodeId); if (self.fixedNodeSet.has(nodeId)) { self.fixedNodesOnVertical.add("dummy" + i); } }); } } } if (CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS) { this.shuffle = function (array) { var j, x, i; for (i = array.length - 1; i >= 2 * array.length / 3; i--) { j = Math.floor(Math.random() * (i + 1)); x = array[i]; array[i] = array[j]; array[j] = x; } return array; }; this.nodesInRelativeHorizontal = []; this.nodesInRelativeVertical = []; this.nodeToRelativeConstraintMapHorizontal = new Map(); this.nodeToRelativeConstraintMapVertical = new Map(); this.nodeToTempPositionMapHorizontal = new Map(); this.nodeToTempPositionMapVertical = new Map(); // fill arrays and maps this.constraints.relativePlacementConstraint.forEach(function (constraint) { if (constraint.left) { var nodeIdLeft = nodeToDummyForVerticalAlignment.has(constraint.left) ? nodeToDummyForVerticalAlignment.get(constraint.left) : constraint.left; var nodeIdRight = nodeToDummyForVerticalAlignment.has(constraint.right) ? nodeToDummyForVerticalAlignment.get(constraint.right) : constraint.right; if (!self.nodesInRelativeHorizontal.includes(nodeIdLeft)) { self.nodesInRelativeHorizontal.push(nodeIdLeft); self.nodeToRelativeConstraintMapHorizontal.set(nodeIdLeft, []); if (self.dummyToNodeForVerticalAlignment.has(nodeIdLeft)) { self.nodeToTempPositionMapHorizontal.set(nodeIdLeft, self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeIdLeft)[0]).getCenterX()); } else { self.nodeToTempPositionMapHorizontal.set(nodeIdLeft, self.idToNodeMap.get(nodeIdLeft).getCenterX()); } } if (!self.nodesInRelativeHorizontal.includes(nodeIdRight)) { self.nodesInRelativeHorizontal.push(nodeIdRight); self.nodeToRelativeConstraintMapHorizontal.set(nodeIdRight, []); if (self.dummyToNodeForVerticalAlignment.has(nodeIdRight)) { self.nodeToTempPositionMapHorizontal.set(nodeIdRight, self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeIdRight)[0]).getCenterX()); } else { self.nodeToTempPositionMapHorizontal.set(nodeIdRight, self.idToNodeMap.get(nodeIdRight).getCenterX()); } } self.nodeToRelativeConstraintMapHorizontal.get(nodeIdLeft).push({ right: nodeIdRight, gap: constraint.gap }); self.nodeToRelativeConstraintMapHorizontal.get(nodeIdRight).push({ left: nodeIdLeft, gap: constraint.gap }); } else { var nodeIdTop = nodeToDummyForHorizontalAlignment.has(constraint.top) ? nodeToDummyForHorizontalAlignment.get(constraint.top) : constraint.top; var nodeIdBottom = nodeToDummyForHorizontalAlignment.has(constraint.bottom) ? nodeToDummyForHorizontalAlignment.get(constraint.bottom) : constraint.bottom; if (!self.nodesInRelativeVertical.includes(nodeIdTop)) { self.nodesInRelativeVertical.push(nodeIdTop); self.nodeToRelativeConstraintMapVertical.set(nodeIdTop, []); if (self.dummyToNodeForHorizontalAlignment.has(nodeIdTop)) { self.nodeToTempPositionMapVertical.set(nodeIdTop, self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeIdTop)[0]).getCenterY()); } else { self.nodeToTempPositionMapVertical.set(nodeIdTop, self.idToNodeMap.get(nodeIdTop).getCenterY()); } } if (!self.nodesInRelativeVertical.includes(nodeIdBottom)) { self.nodesInRelativeVertical.push(nodeIdBottom); self.nodeToRelativeConstraintMapVertical.set(nodeIdBottom, []); if (self.dummyToNodeForHorizontalAlignment.has(nodeIdBottom)) { self.nodeToTempPositionMapVertical.set(nodeIdBottom, self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeIdBottom)[0]).getCenterY()); } else { self.nodeToTempPositionMapVertical.set(nodeIdBottom, self.idToNodeMap.get(nodeIdBottom).getCenterY()); } } self.nodeToRelativeConstraintMapVertical.get(nodeIdTop).push({ bottom: nodeIdBottom, gap: constraint.gap }); self.nodeToRelativeConstraintMapVertical.get(nodeIdBottom).push({ top: nodeIdTop, gap: constraint.gap }); } }); } else { var subGraphOnHorizontal = new Map(); // subgraph from vertical RP constraints var subGraphOnVertical = new Map(); // subgraph from vertical RP constraints // construct subgraphs from relative placement constraints this.constraints.relativePlacementConstraint.forEach(function (constraint) { if (constraint.left) { var left = nodeToDummyForVerticalAlignment.has(constraint.left) ? nodeToDummyForVerticalAlignment.get(constraint.left) : constraint.left; var right = nodeToDummyForVerticalAlignment.has(constraint.right) ? nodeToDummyForVerticalAlignment.get(constraint.right) : constraint.right; if (subGraphOnHorizontal.has(left)) { subGraphOnHorizontal.get(left).push(right); } else { subGraphOnHorizontal.set(left, [right]); } if (subGraphOnHorizontal.has(right)) { subGraphOnHorizontal.get(right).push(left); } else { subGraphOnHorizontal.set(right, [left]); } } else { var top = nodeToDummyForHorizontalAlignment.has(constraint.top) ? nodeToDummyForHorizontalAlignment.get(constraint.top) : constraint.top; var bottom = nodeToDummyForHorizontalAlignment.has(constraint.bottom) ? nodeToDummyForHorizontalAlignment.get(constraint.bottom) : constraint.bottom; if (subGraphOnVertical.has(top)) { subGraphOnVertical.get(top).push(bottom); } else { subGraphOnVertical.set(top, [bottom]); } if (subGraphOnVertical.has(bottom)) { subGraphOnVertical.get(bottom).push(top); } else { subGraphOnVertical.set(bottom, [top]); } } }); // function to construct components from a given graph // also returns an array that keeps whether each component contains fixed node var constructComponents = function constructComponents(graph, fixedNodes) { var components = []; var isFixed = []; var queue = new LinkedList(); var visited = new Set(); var count = 0; graph.forEach(function (value, key) { if (!visited.has(key)) { components[count] = []; isFixed[count] = false; var currentNode = key; queue.push(currentNode); visited.add(currentNode); components[count].push(currentNode); while (queue.length != 0) { currentNode = queue.shift(); if (fixedNodes.has(currentNode)) { isFixed[count] = true; } var neighbors = graph.get(currentNode); neighbors.forEach(function (neighbor) { if (!visited.has(neighbor)) { queue.push(neighbor); visited.add(neighbor); components[count].push(neighbor); } }); } count++; } }); return { components: components, isFixed: isFixed }; }; var resultOnHorizontal = constructComponents(subGraphOnHorizontal, self.fixedNodesOnHorizontal); this.componentsOnHorizontal = resultOnHorizontal.components; this.fixedComponentsOnHorizontal = resultOnHorizontal.isFixed; var resultOnVertical = constructComponents(subGraphOnVertical, self.fixedNodesOnVertical); this.componentsOnVertical = resultOnVertical.components; this.fixedComponentsOnVertical = resultOnVertical.isFixed; } } }; // updates node displacements based on constraints CoSELayout.prototype.updateDisplacements = function () { var self = this; if (this.constraints.fixedNodeConstraint) { this.constraints.fixedNodeConstraint.forEach(function (nodeData) { var fixedNode = self.idToNodeMap.get(nodeData.nodeId); fixedNode.displacementX = 0; fixedNode.displacementY = 0; }); } if (this.constraints.alignmentConstraint) { if (this.constraints.alignmentConstraint.vertical) { var allVerticalAlignments = this.constraints.alignmentConstraint.vertical; for (var i = 0; i < allVerticalAlignments.length; i++) { var totalDisplacementX = 0; for (var j = 0; j < allVerticalAlignments[i].length; j++) { if (this.fixedNodeSet.has(allVerticalAlignments[i][j])) { totalDisplacementX = 0; break; } totalDisplacementX += this.idToNodeMap.get(allVerticalAlignments[i][j]).displacementX; } var averageDisplacementX = totalDisplacementX / allVerticalAlignments[i].length; for (var j = 0; j < allVerticalAlignments[i].length; j++) { this.idToNodeMap.get(allVerticalAlignments[i][j]).displacementX = averageDisplacementX; } } } if (this.constraints.alignmentConstraint.horizontal) { var allHorizontalAlignments = this.constraints.alignmentConstraint.horizontal; for (var i = 0; i < allHorizontalAlignments.length; i++) { var totalDisplacementY = 0; for (var j = 0; j < allHorizontalAlignments[i].length; j++) { if (this.fixedNodeSet.has(allHorizontalAlignments[i][j])) { totalDisplacementY = 0; break; } totalDisplacementY += this.idToNodeMap.get(allHorizontalAlignments[i][j]).displacementY; } var averageDisplacementY = totalDisplacementY / allHorizontalAlignments[i].length; for (var j = 0; j < allHorizontalAlignments[i].length; j++) { this.idToNodeMap.get(allHorizontalAlignments[i][j]).displacementY = averageDisplacementY; } } } } if (this.constraints.relativePlacementConstraint) { if (CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS) { // shuffle array to randomize node processing order if (this.totalIterations % 10 == 0) { this.shuffle(this.nodesInRelativeHorizontal); this.shuffle(this.nodesInRelativeVertical); } this.nodesInRelativeHorizontal.forEach(function (nodeId) { if (!self.fixedNodesOnHorizontal.has(nodeId)) { var displacement = 0; if (self.dummyToNodeForVerticalAlignment.has(nodeId)) { displacement = self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeId)[0]).displacementX; } else { displacement = self.idToNodeMap.get(nodeId).displacementX; } self.nodeToRelativeConstraintMapHorizontal.get(nodeId).forEach(function (constraint) { if (constraint.right) { var diff = self.nodeToTempPositionMapHorizontal.get(constraint.right) - self.nodeToTempPositionMapHorizontal.get(nodeId) - displacement; if (diff < constraint.gap) { displacement -= constraint.gap - diff; } } else { var diff = self.nodeToTempPositionMapHorizontal.get(nodeId) - self.nodeToTempPositionMapHorizontal.get(constraint.left) + displacement; if (diff < constraint.gap) { displacement += constraint.gap - diff; } } }); self.nodeToTempPositionMapHorizontal.set(nodeId, self.nodeToTempPositionMapHorizontal.get(nodeId) + displacement); if (self.dummyToNodeForVerticalAlignment.has(nodeId)) { self.dummyToNodeForVerticalAlignment.get(nodeId).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementX = displacement; }); } else { self.idToNodeMap.get(nodeId).displacementX = displacement; } } }); this.nodesInRelativeVertical.forEach(function (nodeId) { if (!self.fixedNodesOnHorizontal.has(nodeId)) { var displacement = 0; if (self.dummyToNodeForHorizontalAlignment.has(nodeId)) { displacement = self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeId)[0]).displacementY; } else { displacement = self.idToNodeMap.get(nodeId).displacementY; } self.nodeToRelativeConstraintMapVertical.get(nodeId).forEach(function (constraint) { if (constraint.bottom) { var diff = self.nodeToTempPositionMapVertical.get(constraint.bottom) - self.nodeToTempPositionMapVertical.get(nodeId) - displacement; if (diff < constraint.gap) { displacement -= constraint.gap - diff; } } else { var diff = self.nodeToTempPositionMapVertical.get(nodeId) - self.nodeToTempPositionMapVertical.get(constraint.top) + displacement; if (diff < constraint.gap) { displacement += constraint.gap - diff; } } }); self.nodeToTempPositionMapVertical.set(nodeId, self.nodeToTempPositionMapVertical.get(nodeId) + displacement); if (self.dummyToNodeForHorizontalAlignment.has(nodeId)) { self.dummyToNodeForHorizontalAlignment.get(nodeId).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementY = displacement; }); } else { self.idToNodeMap.get(nodeId).displacementY = displacement; } } }); } else { for (var i = 0; i < this.componentsOnHorizontal.length; i++) { var component = this.componentsOnHorizontal[i]; if (this.fixedComponentsOnHorizontal[i]) { for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForVerticalAlignment.has(component[j])) { this.dummyToNodeForVerticalAlignment.get(component[j]).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementX = 0; }); } else { this.idToNodeMap.get(component[j]).displacementX = 0; } } } else { var sum = 0; var count = 0; for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForVerticalAlignment.has(component[j])) { var actualNodes = this.dummyToNodeForVerticalAlignment.get(component[j]); sum += actualNodes.length * this.idToNodeMap.get(actualNodes[0]).displacementX; count += actualNodes.length; } else { sum += this.idToNodeMap.get(component[j]).displacementX; count++; } } var averageDisplacement = sum / count; for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForVerticalAlignment.has(component[j])) { this.dummyToNodeForVerticalAlignment.get(component[j]).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementX = averageDisplacement; }); } else { this.idToNodeMap.get(component[j]).displacementX = averageDisplacement; } } } } for (var i = 0; i < this.componentsOnVertical.length; i++) { var component = this.componentsOnVertical[i]; if (this.fixedComponentsOnVertical[i]) { for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { this.dummyToNodeForHorizontalAlignment.get(component[j]).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementY = 0; }); } else { this.idToNodeMap.get(component[j]).displacementY = 0; } } } else { var sum = 0; var count = 0; for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { var actualNodes = this.dummyToNodeForHorizontalAlignment.get(component[j]); sum += actualNodes.length * this.idToNodeMap.get(actualNodes[0]).displacementY; count += actualNodes.length; } else { sum += this.idToNodeMap.get(component[j]).displacementY; count++; } } var averageDisplacement = sum / count; for (var j = 0; j < component.length; j++) { if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { this.dummyToNodeForHorizontalAlignment.get(component[j]).forEach(function (nodeId) { self.idToNodeMap.get(nodeId).displacementY = averageDisplacement; }); } else { this.idToNodeMap.get(component[j]).displacementY = averageDisplacement; } } } } } } }; CoSELayout.prototype.calculateNodesToApplyGravitationTo = function () { var nodeList = []; var graph; var graphs = this.graphManager.getGraphs(); var size = graphs.length; var i; for (i = 0; i < size; i++) { graph = graphs[i]; graph.updateConnected(); if (!graph.isConnected) { nodeList = nodeList.concat(graph.getNodes()); } } return nodeList; }; CoSELayout.prototype.createBendpoints = function () { var edges = []; edges = edges.concat(this.graphManager.getAllEdges()); var visited = new Set(); var i; for (i = 0; i < edges.length; i++) { var edge = edges[i]; if (!visited.has(edge)) { var source = edge.getSource(); var target = edge.getTarget(); if (source == target) { edge.getBendpoints().push(new PointD()); edge.getBendpoints().push(new PointD()); this.createDummyNodesForBendpoints(edge); visited.add(edge); } else { var edgeList = []; edgeList = edgeList.concat(source.getEdgeListToNode(target)); edgeList = edgeList.concat(target.getEdgeListToNode(source)); if (!visited.has(edgeList[0])) { if (edgeList.length > 1) { var k; for (k = 0; k < edgeList.length; k++) { var multiEdge = edgeList[k]; multiEdge.getBendpoints().push(new PointD()); this.createDummyNodesForBendpoints(multiEdge); } } edgeList.forEach(function (edge) { visited.add(edge); }); } } } if (visited.size == edges.length) { break; } } }; CoSELayout.prototype.positionNodesRadially = function (forest) { // We tile the trees to a grid row by row; first tree starts at (0,0) var currentStartingPoint = new Point(0, 0); var numberOfColumns = Math.ceil(Math.sqrt(forest.length)); var height = 0; var currentY = 0; var currentX = 0; var point = new PointD(0, 0); for (var i = 0; i < forest.length; i++) { if (i % numberOfColumns == 0) { // Start of a new row, make the x coordinate 0, increment the // y coordinate with the max height of the previous row currentX = 0; currentY = height; if (i != 0) { currentY += CoSEConstants.DEFAULT_COMPONENT_SEPERATION; } height = 0; } var tree = forest[i]; // Find the center of the tree var centerNode = Layout.findCenterOfTree(tree); // Set the staring point of the next tree currentStartingPoint.x = currentX; currentStartingPoint.y = currentY; // Do a radial layout starting with the center point = CoSELayout.radialLayout(tree, centerNode, currentStartingPoint); if (point.y > height) { height = Math.floor(point.y); } currentX = Math.floor(point.x + CoSEConstants.DEFAULT_COMPONENT_SEPERATION); } this.transform(new PointD(LayoutConstants.WORLD_CENTER_X - point.x / 2, LayoutConstants.WORLD_CENTER_Y - point.y / 2)); }; CoSELayout.radialLayout = function (tree, centerNode, startingPoint) { var radialSep = Math.max(this.maxDiagonalInTree(tree), CoSEConstants.DEFAULT_RADIAL_SEPARATION); CoSELayout.branchRadialLayout(centerNode, null, 0, 359, 0, radialSep); var bounds = LGraph.calculateBounds(tree); var transform = new Transform(); transform.setDeviceOrgX(bounds.getMinX()); transform.setDeviceOrgY(bounds.getMinY()); transform.setWorldOrgX(startingPoint.x); transform.setWorldOrgY(startingPoint.y); for (var i = 0; i < tree.length; i++) { var node = tree[i]; node.transform(transform); } var bottomRight = new PointD(bounds.getMaxX(), bounds.getMaxY()); return transform.inverseTransformPoint(bottomRight); }; CoSELayout.branchRadialLayout = function (node, parentOfNode, startAngle, endAngle, distance, radialSeparation) { // First, position this node by finding its angle. var halfInterval = (endAngle - startAngle + 1) / 2; if (halfInterval < 0) { halfInterval += 180; } var nodeAngle = (halfInterval + startAngle) % 360; var teta = nodeAngle * IGeometry.TWO_PI / 360; // Make polar to java cordinate conversion. var cos_teta = Math.cos(teta); var x_ = distance * Math.cos(teta); var y_ = distance * Math.sin(teta); node.setCenter(x_, y_); // Traverse all neighbors of this node and recursively call this // function. var neighborEdges = []; neighborEdges = neighborEdges.concat(node.getEdges()); var childCount = neighborEdges.length; if (parentOfNode != null) { childCount--; } var branchCount = 0; var incEdgesCount = neighborEdges.length; var startIndex; var edges = node.getEdgesBetween(parentOfNode); // If there are multiple edges, prune them until there remains only one // edge. while (edges.length > 1) { //neighborEdges.remove(edges.remove(0)); var temp = edges[0]; edges.splice(0, 1); var index = neighborEdges.indexOf(temp); if (index >= 0) { neighborEdges.splice(index, 1); } incEdgesCount--; childCount--; } if (parentOfNode != null) { //assert edges.length == 1; startIndex = (neighborEdges.indexOf(edges[0]) + 1) % incEdgesCount; } else { startIndex = 0; } var stepAngle = Math.abs(endAngle - startAngle) / childCount; for (var i = startIndex; branchCount != childCount; i = ++i % incEdgesCount) { var currentNeighbor = neighborEdges[i].getOtherEnd(node); // Don't back traverse to root node in current tree. if (currentNeighbor == parentOfNode) { continue; } var childStartAngle = (startAngle + branchCount * stepAngle) % 360; var childEndAngle = (childStartAngle + stepAngle) % 360; CoSELayout.branchRadialLayout(currentNeighbor, node, childStartAngle, childEndAngle, distance + radialSeparation, radialSeparation); branchCount++; } }; CoSELayout.maxDiagonalInTree = function (tree) { var maxDiagonal = Integer.MIN_VALUE; for (var i = 0; i < tree.length; i++) { var node = tree[i]; var diagonal = node.getDiagonal(); if (diagonal > maxDiagonal) { maxDiagonal = diagonal; } } return maxDiagonal; }; CoSELayout.prototype.calcRepulsionRange = function () { // formula is 2 x (level + 1) x idealEdgeLength return 2 * (this.level + 1) * this.idealEdgeLength; }; // Tiling methods // Group zero degree members whose parents are not to be tiled, create dummy parents where needed and fill memberGroups by their dummp parent id's CoSELayout.prototype.groupZeroDegreeMembers = function () { var self = this; // array of [parent_id x oneDegreeNode_id] var tempMemberGroups = {}; // A temporary map of parent node and its zero degree members this.memberGroups = {}; // A map of dummy parent node and its zero degree members whose parents are not to be tiled this.idToDummyNode = {}; // A map of id to dummy node var zeroDegree = []; // List of zero degree nodes whose parents are not to be tiled var allNodes = this.graphManager.getAllNodes(); // Fill zero degree list for (var i = 0; i < allNodes.length; i++) { var node = allNodes[i]; var parent = node.getParent(); // If a node has zero degree and its parent is not to be tiled if exists add that node to zeroDegres list if (this.getNodeDegreeWithChildren(node) === 0 && (parent.id == undefined || !this.getToBeTiled(parent))) { zeroDegree.push(node); } } // Create a map of parent node and its zero degree members for (var i = 0; i < zeroDegree.length; i++) { var node = zeroDegree[i]; // Zero degree node itself var p_id = node.getParent().id; // Parent id if (typeof tempMemberGroups[p_id] === "undefined") tempMemberGroups[p_id] = []; tempMemberGroups[p_id] = tempMemberGroups[p_id].concat(node); // Push node to the list belongs to its parent in tempMemberGroups } // If there are at least two nodes at a level, create a dummy compound for them Object.keys(tempMemberGroups).forEach(function (p_id) { if (tempMemberGroups[p_id].length > 1) { var dummyCompoundId = "DummyCompound_" + p_id; // The id of dummy compound which will be created soon self.memberGroups[dummyCompoundId] = tempMemberGroups[p_id]; // Add dummy compound to memberGroups var parent = tempMemberGroups[p_id][0].getParent(); // The parent of zero degree nodes will be the parent of new dummy compound // Create a dummy compound with calculated id var dummyCompound = new CoSENode(self.graphManager); dummyCompound.id = dummyCompoundId; dummyCompound.paddingLeft = parent.paddingLeft || 0; dummyCompound.paddingRight = parent.paddingRight || 0; dummyCompound.paddingBottom = parent.paddingBottom || 0; dummyCompound.paddingTop = parent.paddingTop || 0; self.idToDummyNode[dummyCompoundId] = dummyCompound; var dummyParentGraph = self.getGraphManager().add(self.newGraph(), dummyCompound); var parentGraph = parent.getChild(); // Add dummy compound to parent the graph parentGraph.add(dummyCompound); // For each zero degree node in this level remove it from its parent graph and add it to the graph of dummy parent for (var i = 0; i < tempMemberGroups[p_id].length; i++) { var node = tempMemberGroups[p_id][i]; parentGraph.remove(node); dummyParentGraph.add(node); } } }); }; CoSELayout.prototype.clearCompounds = function () { var childGraphMap = {}; var idToNode = {}; // Get compound ordering by finding the inner one first this.performDFSOnCompounds(); for (var i = 0; i < this.compoundOrder.length; i++) { idToNode[this.compoundOrder[i].id] = this.compoundOrder[i]; childGraphMap[this.compoundOrder[i].id] = [].concat(this.compoundOrder[i].getChild().getNodes()); // Remove children of compounds this.graphManager.remove(this.compoundOrder[i].getChild()); this.compoundOrder[i].child = null; } this.graphManager.resetAllNodes(); // Tile the removed children this.tileCompoundMembers(childGraphMap, idToNode); }; CoSELayout.prototype.clearZeroDegreeMembers = function () { var self = this; var tiledZeroDegreePack = this.tiledZeroDegreePack = []; Object.keys(this.memberGroups).forEach(function (id) { var compoundNode = self.idToDummyNode[id]; // Get the dummy compound tiledZeroDegreePack[id] = self.tileNodes(self.memberGroups[id], compoundNode.paddingLeft + compoundNode.paddingRight); // Set the width and height of the dummy compound as calculated compoundNode.rect.width = tiledZeroDegreePack[id].width; compoundNode.rect.height = tiledZeroDegreePack[id].height; compoundNode.setCenter(tiledZeroDegreePack[id].centerX, tiledZeroDegreePack[id].centerY); // compound left and top margings for labels // when node labels are included, these values may be set to different values below and are used in tilingPostLayout, // otherwise they stay as zero compoundNode.labelMarginLeft = 0; compoundNode.labelMarginTop = 0; // Update compound bounds considering its label properties and set label margins for left and top if (CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS) { var width = compoundNode.rect.width; var height = compoundNode.rect.height; if (compoundNode.labelWidth) { if (compoundNode.labelPosHorizontal == "left") { compoundNode.rect.x -= compoundNode.labelWidth; compoundNode.setWidth(width + compoundNode.labelWidth); compoundNode.labelMarginLeft = compoundNode.labelWidth; } else if (compoundNode.labelPosHorizontal == "center" && compoundNode.labelWidth > width) { compoundNode.rect.x -= (compoundNode.labelWidth - width) / 2; compoundNode.setWidth(compoundNode.labelWidth); compoundNode.labelMarginLeft = (compoundNode.labelWidth - width) / 2; } else if (compoundNode.labelPosHorizontal == "right") { compoundNode.setWidth(width + compoundNode.labelWidth); } } if (compoundNode.labelHeight) { if (compoundNode.labelPosVertical == "top") { compoundNode.rect.y -= compoundNode.labelHeight; compoundNode.setHeight(height + compoundNode.labelHeight); compoundNode.labelMarginTop = compoundNode.labelHeight; } else if (compoundNode.labelPosVertical == "center" && compoundNode.labelHeight > height) { compoundNode.rect.y -= (compoundNode.labelHeight - height) / 2; compoundNode.setHeight(compoundNode.labelHeight); compoundNode.labelMarginTop = (compoundNode.labelHeight - height) / 2; } else if (compoundNode.labelPosVertical == "bottom") { compoundNode.setHeight(height + compoundNode.labelHeight); } } } }); }; CoSELayout.prototype.repopulateCompounds = function () { for (var i = this.compoundOrder.length - 1; i >= 0; i--) { var lCompoundNode = this.compoundOrder[i]; var id = lCompoundNode.id; var horizontalMargin = lCompoundNode.paddingLeft; var verticalMargin = lCompoundNode.paddingTop; var labelMarginLeft = lCompoundNode.labelMarginLeft; var labelMarginTop = lCompoundNode.labelMarginTop; this.adjustLocations(this.tiledMemberPack[id], lCompoundNode.rect.x, lCompoundNode.rect.y, horizontalMargin, verticalMargin, labelMarginLeft, labelMarginTop); } }; CoSELayout.prototype.repopulateZeroDegreeMembers = function () { var self = this; var tiledPack = this.tiledZeroDegreePack; Object.keys(tiledPack).forEach(function (id) { var compoundNode = self.idToDummyNode[id]; // Get the dummy compound by its id var horizontalMargin = compoundNode.paddingLeft; var verticalMargin = compoundNode.paddingTop; var labelMarginLeft = compoundNode.labelMarginLeft; var labelMarginTop = compoundNode.labelMarginTop; // Adjust the positions of nodes wrt its compound self.adjustLocations(tiledPack[id], compoundNode.rect.x, compoundNode.rect.y, horizontalMargin, verticalMargin, labelMarginLeft, labelMarginTop); }); }; CoSELayout.prototype.getToBeTiled = function (node) { var id = node.id; //firstly check the previous results if (this.toBeTiled[id] != null) { return this.toBeTiled[id]; } //only compound nodes are to be tiled var childGraph = node.getChild(); if (childGraph == null) { this.toBeTiled[id] = false; return false; } var children = childGraph.getNodes(); // Get the children nodes //a compound node is not to be tiled if all of its compound children are not to be tiled for (var i = 0; i < children.length; i++) { var theChild = children[i]; if (this.getNodeDegree(theChild) > 0) { this.toBeTiled[id] = false; return false; } //pass the children not having the compound structure if (theChild.getChild() == null) { this.toBeTiled[theChild.id] = false; continue; } if (!this.getToBeTiled(theChild)) { this.toBeTiled[id] = false; return false; } } this.toBeTiled[id] = true; return true; }; // Get degree of a node depending of its edges and independent of its children CoSELayout.prototype.getNodeDegree = function (node) { var id = node.id; var edges = node.getEdges(); var degree = 0; // For the edges connected for (var i = 0; i < edges.length; i++) { var edge = edges[i]; if (edge.getSource().id !== edge.getTarget().id) { degree = degree + 1; } } return degree; }; // Get degree of a node with its children CoSELayout.prototype.getNodeDegreeWithChildren = function (node) { var degre