private void RegisterAggregationCallbacks() { Graph.NodeCreated += (sender, args) => { if (AggregateGraph.IsAggregationItem(args.Item)) { // add a label with the number of aggregated items to the new aggregation node Graph.AddLabel(args.Item, AggregateGraph.GetAggregatedItems(args.Item).Count.ToString()); } }; Graph.EdgeCreated += (sender, args) => { var edge = args.Item; if (!AggregateGraph.IsAggregationItem(edge)) { return; } // add a label with the number of all original aggregated edges represented by the new aggregation edge var aggregatedEdgesCount = AggregateGraph.GetAllAggregatedOriginalItems(edge).Count; if (aggregatedEdgesCount > 1) { Graph.AddLabel(edge, aggregatedEdgesCount.ToString()); } // set the thickness to the number of aggregated edges Graph.SetStyle(edge, new PolylineEdgeStyle { Pen = new Pen(Brushes.Gray, 1 + aggregatedEdgesCount) }); }; }
/// <summary> /// Registers a listener to the <see cref="GraphInputMode.ItemClicked"/> event that toggles the aggregation of a node, /// runs a layout and sets the current item. /// </summary> private void InitializeToggleAggregation() { graphViewerInputMode.ClickableItems = GraphItemTypes.Node; graphViewerInputMode.ItemClicked += async(sender, args) => { // prevent default behavior, which would select nodes that are no longer in the graph args.Handled = true; var node = (INode)args.Item; if (!AggregateGraph.IsAggregationItem(node)) { // is an original node -> only set current item GraphControl.CurrentItem = node; OnInfoPanelPropertiesChanged(); return; } // toggle the aggregation var affectedNodes = aggregationHelper.ToggleAggregation(node); // set the current item to the new aggregation node (which is the first in the list) GraphControl.CurrentItem = affectedNodes[0]; // notify UI OnInfoPanelPropertiesChanged(); // run layout await RunBalloonLayout(affectedNodes); }; }
/// <summary> /// Collects all un-aggregated nodes that match the kind of <paramref name="node"/> by the selector. /// </summary> private List <INode> CollectNodesOfSameKind <TKey>(INode node, Func <INode, TKey> selector) { var nodeKind = selector(node); return(Graph.Nodes .Where(n => !AggregateGraph.IsAggregationItem(n)) .Where(n => selector(n).Equals(nodeKind)) .ToList()); }
private void CreateReplacementEdge(INode sourceNode, INode targetNode, IEdge edge) { if ((AggregateGraph.IsAggregationItem(sourceNode) || AggregateGraph.IsAggregationItem(targetNode)) && AggregateGraph.GetEdge(sourceNode, targetNode) == null && AggregateGraph.GetEdge(targetNode, sourceNode) == null) { AggregateGraph.CreateEdge(sourceNode, targetNode, edge.Style); } }
/// <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> /// Returns the <see cref="NodeAggregation.Aggregate"/> for a node. /// </summary> public NodeAggregation.Aggregate GetAggregateForNode(INode node) { if (AggregateGraph.IsAggregationItem(node)) { return(((AggregationNodeInfo)node.Tag).Aggregate); } else { return(aggregationResult.AggregateMap[node]); } }
/// <summary> /// For all passed nodes, aggregates all nodes that match the given node by the selector. /// </summary> /// <remarks> /// After the aggregation a layout calculation is run. /// </remarks> private void AggregateSame <TKey>(IList <INode> nodes, Func <INode, TKey> selector, Func <TKey, INodeStyle> styleFactory) { // get one representative of each kind of node (determined by the selector) ignoring aggregation nodes IList <INode> distinctNodes = nodes.Where(n => !AggregateGraph.IsAggregationItem(n)) .GroupBy(selector) .Select(g => g.First()) .ToList(); foreach (var node in distinctNodes) { // aggregate all nodes of the same kind as the representing node var nodesOfSameKind = CollectNodesOfSameKind(node, selector); Aggregate(nodesOfSameKind, selector(node), styleFactory); } RunLayout(); }
/// <summary> /// Fills the context menu with menu items based on the clicked node. /// </summary> private void OnPopulateItemContextMenu(object sender, PopulateItemContextMenuEventArgs <IModelItem> e) { // first update the selection INode node = e.Item as INode; // if the cursor is over a node select it, else clear selection UpdateSelection(node); // Create the context menu items var selectedNodes = graphControl.Selection.SelectedNodes; if (selectedNodes.Count > 0) { // only allow aggregation operations on nodes that are not aggregation nodes already var aggregateAllowed = selectedNodes.Any(n => !AggregateGraph.IsAggregationItem(n)); var aggregateByShape = new ToolStripMenuItem("Aggregate Nodes with Same Shape") { Enabled = aggregateAllowed }; aggregateByShape.Click += (o, args) => AggregateSame(selectedNodes.ToList(), ShapeSelector, ShapeStyle); e.Menu.Items.Add(aggregateByShape); var aggregateByColor = new ToolStripMenuItem("Aggregate Nodes with Same Color") { Enabled = aggregateAllowed }; aggregateByColor.Click += (o, args) => AggregateSame(selectedNodes.ToList(), ColorSelector, ColorStyle); e.Menu.Items.Add(aggregateByColor); var aggregateByShapeAndColor = new ToolStripMenuItem("Aggregate Nodes with Same Shape and Color") { Enabled = aggregateAllowed }; aggregateByShapeAndColor.Click += (o, args) => AggregateSame(selectedNodes.ToList(), ShapeAndColorSelector, ShapeAndColorStyle); e.Menu.Items.Add(aggregateByShapeAndColor); var separateAllowed = selectedNodes.Any(n => AggregateGraph.IsAggregationItem(n)); var separate = new ToolStripMenuItem("Separate") { Enabled = separateAllowed }; separate.Click += (o, args) => Separate(selectedNodes.ToList()); e.Menu.Items.Add(separate); } else { var aggregateByShape = new ToolStripMenuItem("Aggregate All Nodes by Shape"); aggregateByShape.Click += (o, args) => AggregateAll(ShapeSelector, ShapeStyle); e.Menu.Items.Add(aggregateByShape); var aggregateByColor = new ToolStripMenuItem("Aggregate All Nodes by Color"); aggregateByColor.Click += (o, args) => AggregateAll(ColorSelector, ColorStyle); e.Menu.Items.Add(aggregateByColor); var aggregateByShapeAndColor = new ToolStripMenuItem("Aggregate All Nodes by Shape and Color"); aggregateByShapeAndColor.Click += (o, args) => AggregateAll(ShapeAndColorSelector, ShapeAndColorStyle); e.Menu.Items.Add(aggregateByShapeAndColor); var separateAllowed = Graph.Nodes.Any(n => AggregateGraph.IsAggregationItem(n)); var separateAll = new ToolStripMenuItem("Separate All") { Enabled = separateAllowed }; separateAll.Click += (o, args) => { AggregateGraph.SeparateAll(); RunLayout(); }; e.Menu.Items.Add(separateAll); } e.ShowMenu = true; e.Handled = true; }
public bool IsOriginalNodeOrPlaceHolder(INode node) { return(!AggregateGraph.IsAggregationItem(node) || ((AggregationNodeInfo)node.Tag).Aggregate.Node != null); }