// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System.Collections.Generic; using System.Linq; namespace System.Windows.Controls.DataVisualization { /// /// Class encapsulating the logic of sub-dividing a parent rectangular area into child rectangles. /// It implements the squaring tree map algorithm where all child nodes are allocated /// areas proportional to their values, but the aspect ratio of each rectangle is kept /// as close as possible to a square. /// internal class SquaringAlgorithm { /// /// Holds the list of nodes being considered by the algorithm. /// private IList _areas; /// /// The current rectangle being divided. /// private Rect _currentRectangle; /// /// Internal index in the list of nodes being divided. /// private int _currentStart; /// /// Temporary variable used during the algorithm. Represents the ratio between /// the real area of the rectangle and the virtual value associated with the node. /// private double _factor; /// /// 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. /// /// Total area being split. /// The node associated with the total area. The /// children of this node will be allocated small chunks of the parent rectangle. /// How much of a gap should be left between the parent rectangle and the children. /// A list of RectangularArea objects describing areas associated with each of the children of parentNode. public IEnumerable> Split(Rect parentRectangle, TreeMapNode parentNode, Thickness margin) { IEnumerable> retVal; double area = parentNode.Area; if (parentNode.Children == null || parentNode.Children.Count() == 0 || area == 0) { retVal = Enumerable.Empty>(); } 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(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; } /// /// This function returns an IEnumerable over the rectangles associated with the children, /// as divided using the tree map algorithm. /// /// A list of RectangularArea objects describing areas associated with each of the children. private IEnumerable> BuildTreeMap() { _currentStart = 0; while (_currentStart < _areas.Count) { foreach (Tuple rectangle in BuildTreeMapStep()) { yield return rectangle; } } } /// /// Performs one step of the body of the squaring tree map algorithm. /// /// List of rectangles calculated by this step. private IEnumerable> BuildTreeMapStep() { int last = _currentStart; double total = 0; double prevAspect = double.PositiveInfinity; double wh = 0; bool horizontal = _currentRectangle.Width > _currentRectangle.Height; for (; last < _areas.Count; last++) { total += GetArea(last); wh = total / (horizontal ? _currentRectangle.Height : _currentRectangle.Width); double curAspect = Math.Max(GetAspect(_currentStart, wh), GetAspect(last, wh)); if (curAspect > prevAspect) { total -= GetArea(last); wh = total / (horizontal ? _currentRectangle.Height : _currentRectangle.Width); last--; break; } prevAspect = curAspect; } if (last == _areas.Count) { last--; } double x = _currentRectangle.Left; double y = _currentRectangle.Top; for (int i = _currentStart; i <= last; i++) { if (horizontal) { double h = GetArea(i) / wh; Rect rect = new Rect(x, y, wh, h); yield return new Tuple(rect, _areas[i]); y += h; } else { double w = GetArea(i) / wh; Rect rect = new Rect(x, y, w, wh); yield return new Tuple(rect, _areas[i]); x += w; } } _currentStart = last + 1; if (horizontal) { _currentRectangle = new Rect(_currentRectangle.Left + wh, _currentRectangle.Top, Math.Max(0, _currentRectangle.Width - wh), _currentRectangle.Height); } else { _currentRectangle = new Rect(_currentRectangle.Left, _currentRectangle.Top + wh, _currentRectangle.Width, Math.Max(0, _currentRectangle.Height - wh)); } } /// /// Returns the calculated area of the node at the given index. /// /// Index of the node to consider. /// Area of the node, calculated based on the node's value multiplied by the current factor. private double GetArea(int i) { return _areas[i].Area * _factor; } /// /// Returns the aspect ratio of the area associated with the node at the given index. /// /// Index of the node to consider. /// Width of the area. /// Positive supra-unitary ratio of the given area. private double GetAspect(int i, double wh) { double aspect = GetArea(i) / (wh * wh); if (aspect < 1) { aspect = 1.0 / aspect; } return aspect; } } }