Esempio n. 1
0
        /// <summary>
        /// Distribute nodes evenly between two sibling nodes.
        /// It will do nothing if there are no nodes between the two nodes.
        /// </summary>
        /// <param name="leftNode">Node on the left.</param>
        /// <param name="rightNode">Node on the right.</param>
        private void DistributeEvenlyNodesBetween(TreeLayoutNode leftNode, TreeLayoutNode rightNode)
        {
            var parentChildren = leftNode.Parent.Children;
            var leftIndex      = parentChildren.IndexOf(leftNode);
            var rightIndex     = parentChildren.IndexOf(rightNode);

            var countNodesBetween = rightIndex - leftIndex - 1;

            if (countNodesBetween < 1)
            {
                return;
            }

            var distanceBetweenNodes = (leftNode.X - rightNode.X)
                                       / (countNodesBetween + 1);

            var count = 1;

            for (var i = leftIndex + 1; i < rightIndex; i++, count++)
            {
                var middleNode = parentChildren[i];

                var desiredX = rightNode.X + distanceBetweenNodes * count;
                var offset   = desiredX - middleNode.X;
                middleNode.X += offset;
                middleNode.ChildrenShiftX += offset;

                this.FixConflicts(middleNode);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Calculate tree side contour (recursive).
        /// </summary>
        private void CalculateTreeContour(
            TreeLayoutNode node,
            List <double> contour,
            bool isLeftContour,
            int maxArrayIndex = int.MaxValue,
            int arrayLevel    = 0,
            double shiftX     = 0)
        {
            var currentNodeX = node.X + shiftX;

            if (arrayLevel < contour.Count)
            {
                var currentValue = contour[arrayLevel];
                contour[arrayLevel] = isLeftContour
                                          ? Math.Min(currentValue, currentNodeX)
                                          : Math.Max(currentValue, currentNodeX);
            }
            else
            {
                contour.Add(currentNodeX);
            }

            if (arrayLevel == maxArrayIndex ||
                node.Children.Count == 0)
            {
                // no need (or cannot) traverse deeper
                return;
            }

            shiftX += node.ChildrenShiftX;
            foreach (var child in node.Children)
            {
                this.CalculateTreeContour(child, contour, isLeftContour, maxArrayIndex, arrayLevel + 1, shiftX);
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Layout multiple root nodes.
        /// </summary>
        /// <param name="rootNodes">List of root nodes.</param>
        public void DoLayout(IReadOnlyCollection <T> rootNodes)
        {
            var layoutRootNode = new TreeLayoutNode(null, rootNodes.Count);

            foreach (var node in rootNodes)
            {
                var layoutNode = this.CreateLayoutNode(node);
                layoutRootNode.Children.Add(layoutNode);
                layoutNode.Parent = layoutRootNode;
            }

            this.DoLayout(layoutRootNode);
        }
Esempio n. 4
0
        /// <summary>
        /// Traverse the tree and ensure that every node is in the screen bounds (minimal X>=0).
        /// </summary>
        private void EnsureAllNodesInScreenBounds(TreeLayoutNode rootNode)
        {
            // calculate left contour
            var contour = new List <double>(capacity: this.estimatedTreeMaxDepth);

            this.CalculateTreeContour(rootNode, contour, isLeftContour: true);

            // calculate min X position in contour
            var minX = contour.Min();

            // shift root node to keep it in bounds
            rootNode.X -= minX;
            rootNode.ChildrenShiftX -= minX;
        }
Esempio n. 5
0
        /// <summary>
        /// Calculate final positions - apply parent X shift values to their children.
        /// </summary>
        private void CalculateFinalX(TreeLayoutNode node, double shiftX)
        {
            // offset this node on the shift value
            node.X += shiftX;

            // write result X position
            node.SetLayoutPositionX(node.X);

            if (node.Children.Count == 0)
            {
                return;
            }

            // process children
            // all the next children will be offset on the combined shift value
            shiftX += node.ChildrenShiftX;
            foreach (var child in node.Children)
            {
                this.CalculateFinalX(child, shiftX);
            }
        }
Esempio n. 6
0
        private TreeLayoutNode CreateLayoutNode(T rootNode)
        {
            var childrenCount = rootNode.Children.Count;
            var layoutNode    = new TreeLayoutNode(rootNode, childrenCount);

            if (childrenCount == 0)
            {
                return(layoutNode);
            }

            var layoutNodeChildren = layoutNode.Children;

            foreach (var childNode in rootNode.Children)
            {
                var childLayoutNode = this.CreateLayoutNode(childNode);
                childLayoutNode.Parent = layoutNode;
                layoutNodeChildren.Add(childLayoutNode);
            }

            return(layoutNode);
        }
Esempio n. 7
0
        /// <summary>
        /// Calculate initial X and children shift values.
        /// </summary>
        /// <param name="node">Current node.</param>
        /// <param name="previousNode">Previous (left sibling to current) node.</param>
        private void CalculateInitialX(TreeLayoutNode node, TreeLayoutNode previousNode)
        {
            var childrenCount = node.Children.Count;

            if (childrenCount > 0)
            {
                TreeLayoutNode previousChildNode = null;
                foreach (var childNode in node.Children)
                {
                    this.CalculateInitialX(childNode, previousChildNode);
                    previousChildNode = childNode;
                }
            }

            if (childrenCount == 0)
            {
                // no children
                if (previousNode == null)
                {
                    // first node in the row
                    node.X = 0;
                }
                else
                {
                    // take previous node X and add the padding distance
                    node.X = previousNode.X + this.siblingNodePadding;
                }

                return;
            }

            // has children
            double middleX;
            var    leftChild = node.Children[0];

            if (childrenCount == 1)
            {
                // single children - middleX equals to the (only) child X
                middleX = leftChild.X;
            }
            else
            {
                // many children - calculate middle X
                var rightChild = node.Children[node.Children.Count - 1];
                middleX = (leftChild.X + rightChild.X) / 2;
            }

            if (previousNode == null)
            {
                // no left sibling
                node.X = middleX;
            }
            else
            {
                // has left sibling - offset accordingly
                node.X = previousNode.X + this.siblingNodePadding;
                node.ChildrenShiftX = node.X - middleX;

                // since subtrees can overlap, check for conflicts and shift tree right wherever needed
                this.FixConflicts(node);
            }
        }
Esempio n. 8
0
        /// <summary>
        /// Check and resolve conflicts with all the left-side sibling trees.
        /// This method will check collisions of current node tree with all the sibling node trees on the left side starting from
        /// the most left node.
        /// The implementation will calculate left contour of the provided node and compare it with the right contours of the
        /// sibling nodes.
        /// </summary>
        /// <param name="node">Current node (it will be shifted when needed).</param>
        private void FixConflicts(TreeLayoutNode node)
        {
            var siblingNode = node.GetLeftMostSibling();

            if (siblingNode == node)
            {
                return;
            }

            var minDistance                      = this.siblingNodePadding;
            var nodeLeftContour                  = new List <double>(capacity: this.estimatedTreeMaxDepth);
            var nodeLeftContourMaxLevel          = 0;
            var isNeedRecalculateNodeLeftContour = true;
            var siblingNodeRightContour          = new List <double>(capacity: this.estimatedTreeMaxDepth);

            do
            {
                if (isNeedRecalculateNodeLeftContour)
                {
                    // calculate current node left contour
                    isNeedRecalculateNodeLeftContour = false;
                    nodeLeftContour.Clear();
                    this.CalculateTreeContour(node, nodeLeftContour, isLeftContour: true);
                    nodeLeftContourMaxLevel = nodeLeftContour.Count - 1;
                }

                if (siblingNodeRightContour.Count > 0)
                {
                    siblingNodeRightContour.Clear();
                }

                // calculate right contour of the sibling tree
                this.CalculateTreeContour(
                    siblingNode,
                    siblingNodeRightContour,
                    isLeftContour: false,
                    maxArrayIndex: nodeLeftContourMaxLevel);
                var siblingNodeRightContourMaxLevel = siblingNodeRightContour.Count - 1;
                var maxLevel = Math.Min(nodeLeftContourMaxLevel, siblingNodeRightContourMaxLevel);

                // compare contours to find shift value
                // (separation distance between the adjacent nodes of two trees)
                var maxMeltaDistance = 0.0;
                for (var level = 1; level <= maxLevel; level++)
                {
                    var deltaDistance = minDistance + siblingNodeRightContour[level] - nodeLeftContour[level];
                    if (deltaDistance > maxMeltaDistance)
                    {
                        maxMeltaDistance = deltaDistance;
                    }
                }

                if (maxMeltaDistance > 0)
                {
                    // need to shift node right to resolve collision with the tree on the left side
                    node.X += maxMeltaDistance;
                    node.ChildrenShiftX += maxMeltaDistance;
                    isNeedRecalculateNodeLeftContour = true;

                    // need to distribute (shift) all the nodes between current node and sibling node
                    this.DistributeEvenlyNodesBetween(leftNode: siblingNode, rightNode: node);
                }

                siblingNode = siblingNode.GetNextSibling();
            }while (siblingNode != node);
        }
Esempio n. 9
0
 private void DoLayout(TreeLayoutNode layoutRootNode)
 {
     this.CalculateInitialX(layoutRootNode, previousNode: null);
     this.EnsureAllNodesInScreenBounds(layoutRootNode);
     this.CalculateFinalX(layoutRootNode, shiftX: 0);
 }