/// <summary> /// Returns a sequence of TreeMapNodes in breadth-first order. /// </summary> /// <returns>Sequence of TreeMapNodes.</returns> private IEnumerable <TreeMapNode> GetTreeMapNodes() { if (_getTreeMapNodesCache == null) { // Create a new list List <TreeMapNode> allNodes = new List <TreeMapNode>(); // Seed the queue with the roots Queue <TreeMapNode> nodes = new Queue <TreeMapNode>(); foreach (TreeMapNode node in _nodeRoots ?? Enumerable.Empty <TreeMapNode>()) { nodes.Enqueue(node); } // Process the queue in breadth-first order while (0 < nodes.Count) { TreeMapNode node = nodes.Dequeue(); allNodes.Add(node); foreach (TreeMapNode child in node.Children) { nodes.Enqueue(child); } } // Cache the list _getTreeMapNodesCache = allNodes; } // Return the cached sequence return(_getTreeMapNodesCache); }
public void NullChildrenReturnsEmptyEnumeration() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 1.0, Children = null }; IEnumerable<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.IsFalse(result.Any()); }
public void ZeroParentAreaReturnsEmptyEnumeration() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 0.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 0.0, Children = null } } }; IList<SWCDV::Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.IsFalse(result.Any()); }
/// <summary> /// Performs the Arrange pass of the layout. /// </summary> /// <remarks> /// We round rectangles to snap to nearest pixels. We do that to avoid /// anti-aliasing which results in better appearance. Moreover to get /// correct layout we would need to use UseLayoutRounding=false which /// is Silverlight specific. A side effect is that areas for rectangles /// in the visual tree no longer can be used to compare them as dimensions /// are not rounded and therefore not precise. /// </remarks> /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param> /// <returns>The actual size used.</returns> protected override Size ArrangeOverride(Size finalSize) { // Sets ActualHeight & ActualWidth for the container finalSize = base.ArrangeOverride(finalSize); if (_nodeRoots != null && ContainerElement != null) { // Create a temporary pseudo-root node containing all the top-level nodes TreeMapNode root = new TreeMapNode() { Area = _nodeRoots.Sum(x => x.Area), Children = _nodeRoots, ChildItemPadding = new Thickness(0) }; // Calculate associated rectangles. We use ContainerElement, // not finalSize so all elements that are above it like border // (with padding and border) are taken into account IEnumerable <Tuple <Rect, TreeMapNode> > measuredRectangles = ComputeRectangles( root, new Rect(0, 0, ContainerElement.ActualWidth, ContainerElement.ActualHeight)); // Position everything foreach (Tuple <Rect, TreeMapNode> rectangle in measuredRectangles) { FrameworkElement element = rectangle.Item2.Element; if (element != null) { double roundedTop = Math.Round(rectangle.Item1.Top); double roundedLeft = Math.Round(rectangle.Item1.Left); double height = Math.Round(rectangle.Item1.Height + rectangle.Item1.Top) - roundedTop; double width = Math.Round(rectangle.Item1.Width + rectangle.Item1.Left) - roundedLeft; // Fully specify element location/size (setting size is required on WPF) Canvas.SetLeft(element, roundedLeft); Canvas.SetTop(element, roundedTop); element.Width = width; element.Height = height; element.Arrange(new Rect(roundedLeft, roundedTop, width, height)); } } } return(finalSize); }
/// <summary> /// Subdivides the parent rectangle using squaring tree map algorithm into /// rectangles with areas specified by the children. The areas must add up /// to at most the area of the rectangle. /// </summary> /// <param name="parentRectangle">Total area being split.</param> /// <param name="parentNode"> /// The node associated with the total area. The /// children of this node will be allocated small chunks of the parent rectangle. /// </param> /// <param name="margin">How much of a gap should be left between the parent rectangle and the children.</param> /// <returns>A list of RectangularArea objects describing areas associated with each of the children of parentNode.</returns> public IEnumerable <Tuple <Rect, TreeMapNode> > Split(Rect parentRectangle, TreeMapNode parentNode, Thickness margin) { IEnumerable <Tuple <Rect, TreeMapNode> > retVal; double area = parentNode.Area; if (parentNode.Children == null || parentNode.Children.Count() == 0 || area == 0) { retVal = Enumerable.Empty <Tuple <Rect, TreeMapNode> >(); } else { if (parentRectangle.Width - margin.Left - margin.Right <= 0 || parentRectangle.Height - margin.Top - margin.Bottom <= 0) { // Margins too big, no more room for children. Returning // zero sized rectangles for all children. retVal = from child in parentNode.Children select new Tuple <Rect, TreeMapNode>(new Rect(0, 0, 0, 0), child); } else { // Leave as much room as specified by the margin _currentRectangle = new Rect( parentRectangle.X + margin.Left, parentRectangle.Y + margin.Top, parentRectangle.Width - margin.Left - margin.Right, parentRectangle.Height - margin.Top - margin.Bottom); _areas = (from child in parentNode.Children where child.Area != 0 orderby child.Area descending select child).ToArray(); // Factor is only computed once and used during the algorithm _factor = _currentRectangle.Width * _currentRectangle.Height / area; retVal = BuildTreeMap().ToArray(); } } return(retVal); }
/// <summary> /// Subdivides the parent rectangle using squaring tree map algorithm into /// rectangles with areas specified by the children. The areas must add up /// to at most the area of the rectangle. /// </summary> /// <param name="parentRectangle">Total area being split.</param> /// <param name="parentNode">The node associated with the total area. The /// children of this node will be allocated small chunks of the parent rectangle.</param> /// <param name="margin">How much of a gap should be left between the parent rectangle and the children.</param> /// <returns>A list of RectangularArea objects describing areas associated with each of the children of parentNode.</returns> public IEnumerable<Tuple<Rect, TreeMapNode>> Split(Rect parentRectangle, TreeMapNode parentNode, Thickness margin) { IEnumerable<Tuple<Rect, TreeMapNode>> retVal; double area = parentNode.Area; if (parentNode.Children == null || parentNode.Children.Count() == 0 || area == 0) { retVal = Enumerable.Empty<Tuple<Rect, TreeMapNode>>(); } else { if (parentRectangle.Width - margin.Left - margin.Right <= 0 || parentRectangle.Height - margin.Top - margin.Bottom <= 0) { // Margins too big, no more room for children. Returning // zero sized rectangles for all children. retVal = from child in parentNode.Children select new Tuple<Rect, TreeMapNode>(new Rect(0, 0, 0, 0), child); } else { // Leave as much room as specified by the margin _currentRectangle = new Rect( parentRectangle.X + margin.Left, parentRectangle.Y + margin.Top, parentRectangle.Width - margin.Left - margin.Right, parentRectangle.Height - margin.Top - margin.Bottom); _areas = (from child in parentNode.Children where child.Area != 0 orderby child.Area descending select child).ToArray(); // Factor is only computed once and used during the algorithm _factor = _currentRectangle.Width * _currentRectangle.Height / area; retVal = BuildTreeMap().ToArray(); } } return retVal; }
/// <summary> /// Recursively computes TreeMap rectangles given the root node and the bounding rectangle as start. /// </summary> /// <param name="root">Root of the TreeMapNode tree.</param> /// <param name="boundingRectangle">Bounding rectangle which will be sub-divided.</param> /// <returns>A list of RectangularAreas containing a rectangle for each node in the tree.</returns> private IEnumerable <Tuple <Rect, TreeMapNode> > ComputeRectangles(TreeMapNode root, Rect boundingRectangle) { Queue <Tuple <Rect, TreeMapNode> > treeQueue = new Queue <Tuple <Rect, TreeMapNode> >(); treeQueue.Enqueue(new Tuple <Rect, TreeMapNode>(boundingRectangle, root)); // Perform a breadth-first traversal of the tree SquaringAlgorithm algorithm = new SquaringAlgorithm(); while (treeQueue.Count > 0) { Tuple <Rect, TreeMapNode> currentParent = treeQueue.Dequeue(); yield return(currentParent); foreach (Tuple <Rect, TreeMapNode> rectangle in algorithm.Split(currentParent.Item1, currentParent.Item2, currentParent.Item2.ChildItemPadding)) { treeQueue.Enqueue(rectangle); } } }
/// <summary> /// Recursively computes TreeMap rectangles given the root node and the bounding rectangle as start. /// </summary> /// <param name="root">Root of the TreeMapNode tree.</param> /// <param name="boundingRectangle">Bounding rectangle which will be sub-divided.</param> /// <returns>A list of RectangularAreas containing a rectangle for each node in the tree.</returns> private IEnumerable<Tuple<Rect, TreeMapNode>> ComputeRectangles(TreeMapNode root, Rect boundingRectangle) { Queue<Tuple<Rect, TreeMapNode>> treeQueue = new Queue<Tuple<Rect, TreeMapNode>>(); treeQueue.Enqueue(new Tuple<Rect, TreeMapNode>(boundingRectangle, root)); // Perform a breadth-first traversal of the tree SquaringAlgorithm algorithm = new SquaringAlgorithm(); while (treeQueue.Count > 0) { Tuple<Rect, TreeMapNode> currentParent = treeQueue.Dequeue(); yield return currentParent; foreach (Tuple<Rect, TreeMapNode> rectangle in algorithm.Split(currentParent.Item1, currentParent.Item2, currentParent.Item2.ChildItemPadding)) { treeQueue.Enqueue(rectangle); } } }
/// <summary> /// Performs the Arrange pass of the layout. /// </summary> /// <remarks> /// We round rectangles to snap to nearest pixels. We do that to avoid /// anti-aliasing which results in better appearance. Moreover to get /// correct layout we would need to use UseLayoutRounding=false which /// is Silverlight specific. A side effect is that areas for rectangles /// in the visual tree no longer can be used to compare them as dimensions /// are not rounded and therefore not precise. /// </remarks> /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param> /// <returns>The actual size used.</returns> protected override Size ArrangeOverride(Size finalSize) { // Sets ActualHeight & ActualWidth for the container finalSize = base.ArrangeOverride(finalSize); if (_nodeRoots != null && ContainerElement != null) { // Create a temporary pseudo-root node containing all the top-level nodes TreeMapNode root = new TreeMapNode() { Area = _nodeRoots.Sum(x => x.Area), Children = _nodeRoots, ChildItemPadding = new Thickness(0) }; // Calculate associated rectangles. We use ContainerElement, // not finalSize so all elements that are above it like border // (with padding and border) are taken into account IEnumerable<Tuple<Rect, TreeMapNode>> measuredRectangles = ComputeRectangles( root, new Rect(0, 0, ContainerElement.ActualWidth, ContainerElement.ActualHeight)); // Position everything foreach (Tuple<Rect, TreeMapNode> rectangle in measuredRectangles) { FrameworkElement element = rectangle.Item2.Element; if (element != null) { double roundedTop = Math.Round(rectangle.Item1.Top); double roundedLeft = Math.Round(rectangle.Item1.Left); double height = Math.Round(rectangle.Item1.Height + rectangle.Item1.Top) - roundedTop; double width = Math.Round(rectangle.Item1.Width + rectangle.Item1.Left) - roundedLeft; // Fully specify element location/size (setting size is required on WPF) Canvas.SetLeft(element, roundedLeft); Canvas.SetTop(element, roundedTop); element.Width = width; element.Height = height; element.Arrange(new Rect(roundedLeft, roundedTop, width, height)); } } } return finalSize; }
public void ZeroChildAreaReturnsNonemptyEnumeration() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 1.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 0.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 80, 100), node, NoThickness).ToArray(); Assert.AreEqual(1, result.Count); }
public void CanSplitRectanglesWithThickness() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; Thickness thickness = new Thickness(10, 30, 20, 40); IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(40, 20, 110, 170), node, thickness).ToArray(); Assert.AreEqual(50, result[0].Item1.Top, DELTA); Assert.AreEqual(140, result[0].Item1.Bottom, DELTA); Assert.AreEqual(140, result[1].Item1.Top, DELTA); Assert.AreEqual(150, result[1].Item1.Bottom, DELTA); Assert.AreEqual(50, result[0].Item1.Left, DELTA); Assert.AreEqual(130, result[0].Item1.Right, DELTA); Assert.AreEqual(50, result[1].Item1.Left, DELTA); Assert.AreEqual(130, result[1].Item1.Right, DELTA); }
/// <summary> /// Recursively computes TreeMap rectangles given the root node and the bounding rectangle as start. /// </summary> /// <param name="root">Root of the TreeMapNode tree.</param> /// <param name="boundingRectangle">Bounding rectangle which will be sub-divided.</param> /// <returns>A list of RectangularAreas containing a rectangle for each node in the tree.</returns> private IEnumerable <(Rect RectangularArea, TreeMapNode TreeMapNode)> ComputeRectangles(TreeMapNode root, Rect boundingRectangle) { Queue <(Rect RectangularArea, TreeMapNode TreeMapNode)> treeQueue = new Queue <(Rect RectangularArea, TreeMapNode TreeMapNode)>(); treeQueue.Enqueue((boundingRectangle, root)); // Perform a breadth-first traversal of the tree SquaringAlgorithm algorithm = new SquaringAlgorithm(); while (treeQueue.Count > 0) { var currentParent = treeQueue.Dequeue(); yield return(currentParent); foreach (var rectangle in algorithm.Split(currentParent.RectangularArea, currentParent.TreeMapNode, currentParent.TreeMapNode.ChildItemPadding)) { treeQueue.Enqueue(rectangle); } } }
public void TwoReverseOrderedChildrenSplitAreaProperly() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.AreEqual(2, result.Count); Assert.AreEqual(9000, result[0].Item1.Width * result[0].Item1.Height, DELTA); Assert.AreEqual(1000, result[1].Item1.Width * result[1].Item1.Height, DELTA); }
public void MultipleVerticalChildrenTakeAllHorizontalSpace() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 150), node, NoThickness).ToArray(); Assert.AreEqual(0, result[0].Item1.Left, DELTA); Assert.AreEqual(100, result[0].Item1.Right, DELTA); Assert.AreEqual(0, result[1].Item1.Left, DELTA); Assert.AreEqual(100, result[1].Item1.Right, DELTA); }
public void RectanglesDoNotOverlap() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.AreEqual(0, result[0].Item1.Top, DELTA); Assert.AreEqual(90, result[0].Item1.Bottom, DELTA); Assert.AreEqual(90, result[1].Item1.Top, DELTA); Assert.AreEqual(100, result[1].Item1.Bottom, DELTA); }
public void TupleRectTreeMapNodeTakesCorrectNode() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.IsTrue(Object.ReferenceEquals(node.Children.First(), result[1].Item2)); }
public void PlaceholderRectangleIsCorrect() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 2.0, Children = null }, new TreeMapNode { Area = 1.0, Children = null } } } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.AreEqual(new Rect(0, 0, 100, 100), result[0].Item1); }
public void ChildrenNotAddingToParent() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 2.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.AreEqual(2, result.Count); Assert.AreEqual(result[0].Item1.Width * result[0].Item1.Height, 1999, 2001); Assert.AreEqual(result[1].Item1.Width * result[1].Item1.Height, 999, 1001); }
public void CanSplitRectanglesWithTranslatedViewport() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(50, 50, 80, 100), node, NoThickness).ToArray(); Assert.AreEqual(50, result[0].Item1.Top, DELTA); Assert.AreEqual(140, result[0].Item1.Bottom, DELTA); Assert.AreEqual(140, result[1].Item1.Top, DELTA); Assert.AreEqual(150, result[1].Item1.Bottom, DELTA); Assert.AreEqual(50, result[0].Item1.Left, DELTA); Assert.AreEqual(130, result[0].Item1.Right, DELTA); Assert.AreEqual(50, result[1].Item1.Left, DELTA); Assert.AreEqual(130, result[1].Item1.Right, DELTA); }
public void SingleChildTakesAllSpace() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 1.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null } } }; IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(0, 0, 100, 100), node, NoThickness).ToArray(); Assert.AreEqual(1, result.Count); Assert.AreEqual(100, result[0].Item1.Width, DELTA); Assert.AreEqual(100, result[0].Item1.Height, DELTA); }
/// <summary> /// Subdivides the parent rectangle using squaring tree map algorithm into /// rectangles with areas specified by the children. The areas must add up /// to at most the area of the rectangle. /// </summary> /// <param name="parentRectangle">Total area being split.</param> /// <param name="parentNode">The node associated with the total area. The /// children of this node will be allocated small chunks of the parent rectangle.</param> /// <param name="margin">How much of a gap should be left between the parent rectangle and the children.</param> /// <returns>A list of RectangularArea objects describing areas associated with each of the children of parentNode.</returns> public IEnumerable <(Rect RectangularArea, TreeMapNode TreeMapNode)> Split(Rect parentRectangle, TreeMapNode parentNode, Thickness margin) { IEnumerable <(Rect RectangularArea, TreeMapNode TreeMapNode)> retVal; double area = parentNode.Area; if (parentNode.Children == null || !parentNode.Children.Any() || area == 0) { retVal = Enumerable.Empty <(Rect RectangularArea, TreeMapNode TreeMapNode)>(); } else { if (parentRectangle.Width - margin.Left - margin.Right <= 0 || parentRectangle.Height - margin.Top - margin.Bottom <= 0) { // Margins too big, no more room for children. Returning // zero sized rectangles for all children. retVal = parentNode.Children.Select(child => (new Rect(0, 0, 0, 0), child)); } else { // Leave as much room as specified by the margin _currentRectangle = new Rect( parentRectangle.X + margin.Left, parentRectangle.Y + margin.Top, parentRectangle.Width - margin.Left - margin.Right, parentRectangle.Height - margin.Top - margin.Bottom); _areas = parentNode.Children.Where(child => child.Area != 0).OrderByDescending(child => child.Area).ToArray(); // Factor is only computed once and used during the algorithm _factor = _currentRectangle.Width * _currentRectangle.Height / area; retVal = BuildTreeMap().ToArray(); } } return(retVal); }
public void ThicknessIsSubtractedFromRectangleSize() { SquaringAlgorithm rect = new SquaringAlgorithm(); TreeMapNode node = new TreeMapNode { Area = 10.0, Children = new List<TreeMapNode> { new TreeMapNode { Area = 1.0, Children = null }, new TreeMapNode { Area = 9.0, Children = null } } }; Thickness thickness = new Thickness(10, 30, 20, 40); IList<Tuple<Rect, TreeMapNode>> result = rect.Split(new Rect(10, 30, 130, 170), node, thickness).ToArray(); Assert.AreEqual(2, result.Count); Assert.AreEqual(9000, result[0].Item1.Width * result[0].Item1.Height); Assert.AreEqual(1000, result[1].Item1.Width * result[1].Item1.Height); }