/// <summary> /// Initializes the visual tree and pool of boundary objects. /// </summary> public void AttachVisualTree([NotNull] BoxTree tree) { while (m_pooledBoundaries.Count < tree.Depth) { m_pooledBoundaries.Push(new Boundary()); } }
/// <summary> /// Computes bounding rectangle in diagram space using only visible (non-autogenerated boxes). /// Useful for rendering the chart, as boxes frequently go into negative side horizontally, and have a special root box on top - all of those should not be accounted for. /// </summary> public static Rect ComputeBranchVisualBoundingRect([NotNull] BoxTree visualTree) { var result = new Rect(); var initialized = false; visualTree.Root.IterateParentFirst(node => { var box = node.Element; if (!node.State.IsHidden && !box.IsSpecial) { if (initialized) { result += new Rect(node.State.TopLeft, node.State.Size); } else { initialized = true; result = new Rect(node.State.TopLeft, node.State.Size); } } return(!box.IsCollapsed); }); return(result); }
/// <summary> /// Constructs a new tree. /// </summary> public static BoxTree Build([NotNull] LayoutState state) { var result = new BoxTree(); // build dictionary of nodes foreach (var box in state.Diagram.Boxes.BoxesById.Values) { var node = new Node(box); result.Nodes.Add(box.Id, node); } // build the tree foreach (var node in result.Nodes.Values) { var parentKey = node.Element.ParentId; Node parentNode; if (result.Nodes.TryGetValue(parentKey, out parentNode)) { if (node.Element.IsAssistant && parentNode.Element.ParentId != Box.None) { parentNode.AddAssistantChild(node); } else { parentNode.AddRegularChild(node); } } else { if (result.Root != null) { throw new InvalidOperationException("More then one root found: " + node.Element.Id); } // In case of data errors, parent key may be not null, but parent node is not there. // Just add the node to roots. result.Root = node; } } return(result); }
private static void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree visualTree) { visualTree.IterateParentFirst(node => { if (node.Element.IsCollapsed || node.State.NumberOfSiblings == 0 && node.AssistantsRoot == null) { return(false); } if (node.Level == 0) { return(true); } if (!node.Element.IsSpecial || node.IsAssistantRoot) { node.State.RequireLayoutStrategy().RouteConnectors(state, node); return(true); } return(false); }); }
/// <summary> /// Initializes <paramref name="state"/> and performs all layout operations. /// </summary> public static void Apply([NotNull] LayoutState state) { // verify the root if (state.Diagram.Boxes.SystemRoot == null) { throw new InvalidOperationException("SystemRoot is not initialized on the box container"); } state.CurrentOperation = LayoutState.Operation.Preparing; var tree = BoxTree.Build(state); state.Diagram.VisualTree = tree; // verify the root: regardless of data items, there must be a system root box on top of everything // the corresponding node is not supposed to be rendered, it only serves as layout algorithm's starting point if (tree.Root == null || tree.Root.Element.Id != state.Diagram.Boxes.SystemRoot.Id) { throw new Exception("SystemRoot is not on the top of the visual tree"); } // set the tree and update visibility tree.UpdateHierarchyStats(); state.AttachVisualTree(tree); // update visibility of boxes based on collapsed state tree.IterateParentFirst( node => { node.State.IsHidden = node.ParentNode != null && (node.ParentNode.State.IsHidden || node.ParentNode.Element.IsCollapsed); return(true); }); // In this phase, we will figure out layout strategy // and initialize layout state for each node. // Event listener may perform initial rendering /measuring of boxes when this event fires, // to determine box sizes and be ready to supply them via BoxSizeFunc delegate. state.CurrentOperation = LayoutState.Operation.PreprocessVisualTree; // initialize box sizes if (state.BoxSizeFunc != null) { // apply box sizes foreach (var box in state.Diagram.Boxes.BoxesById.Values.Where(x => x.IsDataBound)) { box.Size = state.BoxSizeFunc(box.DataId); } } foreach (var box in state.Diagram.Boxes.BoxesById.Values) { AssertBoxSize(box); } // initialize layout state on each node tree.IterateParentFirst( node => { node.State.MoveTo(0, 0); node.State.Size = node.Element.Size; node.State.BranchExterior = new Rect(new Point(0, 0), node.Element.Size); return(true); }); PreprocessVisualTree(state, tree); tree.UpdateHierarchyStats(); state.CurrentOperation = LayoutState.Operation.VerticalLayout; VerticalLayout(state, tree.Root); state.CurrentOperation = LayoutState.Operation.HorizontalLayout; HorizontalLayout(state, tree.Root); state.CurrentOperation = LayoutState.Operation.ConnectorsLayout; RouteConnectors(state, tree); state.CurrentOperation = LayoutState.Operation.Completed; }
private static void PreprocessVisualTree([NotNull] LayoutState state, [NotNull] BoxTree visualTree) { var defaultStrategy = state.Diagram.LayoutSettings.RequireDefaultLayoutStrategy(); var defaultAssistantsStrategy = state.Diagram.LayoutSettings.RequireDefaultAssistantLayoutStrategy(); var regular = new Stack <LayoutStrategyBase>(); regular.Push(defaultStrategy); var assistants = new Stack <LayoutStrategyBase>(); assistants.Push(defaultAssistantsStrategy); visualTree.IterateParentFirst(node => { if (node.State.IsHidden) { return(false); } LayoutStrategyBase strategy = null; if (state.LayoutOptimizerFunc != null) { var suggestedStrategyId = state.LayoutOptimizerFunc(node); if (!string.IsNullOrEmpty(suggestedStrategyId)) { strategy = state.Diagram.LayoutSettings.LayoutStrategies[suggestedStrategyId]; } } if (node.IsAssistantRoot) { if (strategy == null) { strategy = node.ParentNode.Element.AssistantLayoutStrategyId != null ? state.Diagram.LayoutSettings.LayoutStrategies[ node.ParentNode.Element.AssistantLayoutStrategyId] : assistants.Peek(); } assistants.Push(strategy); } else { if (strategy == null) { strategy = node.Element.LayoutStrategyId != null ? state.Diagram.LayoutSettings.LayoutStrategies[node.Element.LayoutStrategyId] : regular.Peek(); } regular.Push(strategy); if (!strategy.SupportsAssistants) { node.SuppressAssistants(); } } // now let it pre-allocate special boxes etc node.State.EffectiveLayoutStrategy = strategy; node.State.RequireLayoutStrategy().PreProcessThisNode(state, node); return((!node.Element.IsCollapsed && node.ChildCount > 0) || node.AssistantsRoot != null); }, node => { if (!node.State.IsHidden) { if (node.IsAssistantRoot) { assistants.Pop(); } else { regular.Pop(); } } }); }