/// <summary> /// Aggregates the <paramref name="aggregate"/> as well as all its children recursively. /// </summary> /// <remarks> /// Can be used to apply the initial aggregation. If this is not the initial aggregation run, it will reuse existing /// aggregation nodes. /// </remarks> /// <param name="aggregate">The "root" aggregate.</param> /// <returns>The aggregation node representing the passed <paramref name="aggregate"/></returns> public INode AggregateRecursively(NodeAggregation.Aggregate aggregate) { if (aggregate.Children.Count == 0) { return(aggregate.Node); } PointD originalCenter; if (aggregateToNode.TryGetValue(aggregate, out var node)) { originalCenter = node.Layout.GetCenter(); var aggregationInfo = (AggregationNodeInfo)node.Tag; if (aggregationInfo.IsAggregated) { return(node); } else { AggregateGraph.Separate(node); } } else { originalCenter = PointD.Origin; } var nodesToAggregate = aggregate.Children.Select(AggregateRecursively).ToList(); if (aggregate.Node != null) { nodesToAggregate.Add(aggregate.Node); } var size = 30 + Math.Sqrt(aggregate.DescendantWeightSum) * 4; var layout = RectD.FromCenter(originalCenter, new SizeD(size, size)); var aggregationNode = AggregateGraph.Aggregate(new ListEnumerable <INode>(nodesToAggregate), layout, AggregationNodeStyle); aggregateToNode[aggregate] = aggregationNode; aggregationNode.Tag = new AggregationNodeInfo(aggregate, true); if (aggregate.Node != null) { placeholderMap[aggregate.Node] = aggregationNode; CopyLabels(aggregate.Node, aggregationNode); } else { var maxChild = GetMostImportantDescendant(aggregate); if (maxChild.Node != null && maxChild.Node.Labels.Any()) { AggregateGraph.AddLabel(aggregationNode, $"({maxChild.Node.Labels[0].Text}, …)", FreeNodeLabelModel.Instance.CreateDefaultParameter(), DescendantLabelStyle); } } return(aggregationNode); }
/// <summary> /// Separates all <paramref name="nodes"/> and runs the layout afterwards. /// </summary> private void Separate(IEnumerable <INode> nodes) { foreach (var child in nodes) { if (AggregateGraph.IsAggregationItem(child)) { AggregateGraph.Separate(child); } } RunLayout(); }
/// <summary> /// Separates an aggregated aggregation node and replaces it by a new aggregation node. /// </summary> /// <remarks> /// Creates hierarchy edges between the new aggregation node and its children. /// </remarks> /// <param name="node">The node.</param> /// <returns>The nodes affected by this operation. The created aggregation node is always the first item.</returns> public IListEnumerable <INode> Separate(INode node) { var aggregationInfo = (AggregationNodeInfo)node.Tag; var aggregate = aggregationInfo.Aggregate; var aggregatedItems = AggregateGraph.GetAggregatedItems(node) .Where(n => n != aggregate.Node) .Cast <INode>().ToList(); AggregateGraph.Separate(node); var nodesToAggregate = aggregate.Node != null ? new ListEnumerable <INode>(new List <INode> { aggregate.Node }) : ListEnumerable <INode> .Empty; var aggregationNode = AggregateGraph.Aggregate(nodesToAggregate, node.Layout.ToRectD(), AggregationNodeStyle); foreach (var child in aggregatedItems) { AggregateGraph.CreateEdge(aggregationNode, child, HierarchyEdgeStyle, true); AggregateGraph.SetNodeLayout(child, RectD.FromCenter(aggregationNode.Layout.GetCenter(), child.Layout.ToSizeD())); ReplaceEdges(child); } aggregationInfo.IsAggregated = false; aggregateToNode[aggregate] = aggregationNode; aggregationNode.Tag = aggregationInfo; var affectedNodes = new List <INode> { aggregationNode }; affectedNodes.AddRange(aggregatedItems); if (aggregate.Parent != null && aggregateToNode.TryGetValue(aggregate.Parent, out var parentNode)) { AggregateGraph.CreateEdge(parentNode, aggregationNode, HierarchyEdgeStyle, true); affectedNodes.Add(parentNode); } if (aggregate.Node != null) { placeholderMap[aggregate.Node] = aggregationNode; CopyLabels(aggregate.Node, aggregationNode); ReplaceEdges(aggregationNode); } return(new ListEnumerable <INode>(affectedNodes)); }