/// <summary> /// Creates a new <see cref="FilteredGraphWrapper"/> that shows the currently visible original nodes. /// </summary> /// <remarks> /// Nodes without currently visible edges are also filtered out. /// </remarks> private FilteredGraphWrapper CreateFilteredView() { // create a new FilteredGraphWrapper that filters the original graph and shows only the currently visible nodes var filteredGraph = new FilteredGraphWrapper(OriginalGraph, node => { node = aggregationHelper.GetPlaceholder(node); if (!AggregateGraph.Contains(node)) { return(false); } // also filter nodes without edges return(AggregateGraph.EdgesAt(node).Count(edge => !aggregationHelper.IsHierarchyEdge(edge)) > 0); }); // set the node layouts for a smooth transition foreach (var node in filteredGraph.Nodes) { filteredGraph.SetNodeLayout(node, aggregationHelper.GetPlaceholder(node).Layout.ToRectD()); } // reset any rotated labels foreach (var label in filteredGraph.Labels) { filteredGraph.SetLabelLayoutParameter(label, FreeNodeLabelModel.Instance.CreateDefaultParameter()); } return(filteredGraph); }
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> /// Copies the labels from <paramref name="source"/> to <paramref name="target"/>. /// </summary> /// <param name="source"></param> /// <param name="target"></param> private void CopyLabels(INode source, INode target) { foreach (var label in source.Labels) { AggregateGraph.AddLabel(target, label.Text, FreeNodeLabelModel.Instance.CreateDefaultParameter(), label.Style); } }
/// <summary> /// Aggregates the nodes to a new aggregation node. /// </summary> /// <remarks> /// Adds a label with the number of aggregated nodes and adds labels /// to all created aggregation edges with the number of replaced original edges. /// </remarks> private void Aggregate <TKey>(IList <INode> nodes, TKey key, Func <TKey, INodeStyle> styleFactory) { var size = Graph.NodeDefaults.Size * (1 + nodes.Count * 0.2); var layout = RectD.FromCenter(PointD.Origin, size); AggregateGraph.Aggregate(new ListEnumerable <INode>(nodes), layout, styleFactory(key)); }
/// <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> /// 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); }
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> /// 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()); }
/// <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> /// Runs the smart <see cref="NodeAggregation"/> algorithm with the settings from the properties panel. /// </summary> private async void RunButtonClick(object sender, RoutedEventArgs e) { SetUiEnabled(false); GraphControl.Graph = new DefaultGraph(); AggregateGraph.Dispose(); await RunAggregationAndReplaceGraph(OriginalGraph); SetUiEnabled(true); OnInfoPanelPropertiesChanged(); }
/// <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> /// Aggregates all nodes of the original graph by the selector and runs the layout. /// </summary> /// <remarks> /// Before aggregating the nodes, all existing aggregations are /// <see cref="AggregateGraphWrapper.SeparateAll">separated</see>. /// </remarks> private void AggregateAll <TKey>(Func <INode, TKey> selector, Func <TKey, INodeStyle> styleFactory) { AggregateGraph.SeparateAll(); foreach (var grouping in Graph.Nodes.GroupBy(selector).ToList()) { Aggregate(grouping.ToList(), grouping.Key, styleFactory); } 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)); }
/// <summary> /// Replaces original edges adjacent to a placeholder node with aggregation edges when source and target are /// currently visible. /// </summary> private void ReplaceEdges(INode node) { INode originalNode; if (node.Tag is AggregationNodeInfo aggregationInfo) { originalNode = aggregationInfo.Aggregate.Node; } else { originalNode = node; } if (originalNode == null) { return; } foreach (var edge in AggregateGraph.WrappedGraph.EdgesAt(originalNode).ToList()) { if (edge.TargetPort.Owner == originalNode) { var sourceNode = (INode)edge.SourcePort.Owner; if (AggregateGraph.Contains(sourceNode)) { CreateReplacementEdge(sourceNode, node, edge); } else if (placeholderMap.TryGetValue(sourceNode, out var placeholderSource) && AggregateGraph.Contains(placeholderSource)) { CreateReplacementEdge(placeholderSource, node, edge); } } else { var targetNode = (INode)edge.TargetPort.Owner; if (AggregateGraph.Contains(targetNode)) { CreateReplacementEdge(node, targetNode, edge); } else if (placeholderMap.TryGetValue(targetNode, out var placeholderTarget) && AggregateGraph.Contains(placeholderTarget)) { CreateReplacementEdge(node, placeholderTarget, edge); } } } }
/// <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> /// Replaces a separated node and its hierarchy children with a new aggregation node. /// </summary> /// <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> Aggregate(INode node) { var aggregationInfo = (AggregationNodeInfo)node.Tag; var aggregate = aggregationInfo.Aggregate; var aggregationNode = AggregateRecursively(aggregate); var affectedNodes = new List <INode> { aggregationNode }; if (aggregate.Parent != null && aggregateToNode.TryGetValue(aggregate.Parent, out var parentNode)) { AggregateGraph.CreateEdge(parentNode, aggregationNode, HierarchyEdgeStyle, true); affectedNodes.Add(parentNode); } if (aggregate.Node != null) { ReplaceEdges(aggregationNode); } return(new ListEnumerable <INode>(affectedNodes)); }
public static void Generate(string dlcName, Manifest.Manifest manifest, AggregateGraph.AggregateGraph aggregateGraph, Stream outStream) { var game = new Game(); game.Entities = new List<Entity>(); var ent = new Entity() { Id = IdGenerator.Guid().ToString().Replace("-", ""), Name = "SoundScene0", Iterations = 1, ModelName = "SoundScene", Properties = new List<Property>() { CreateMultiItemProperty("SoundBanks", new string[1] { aggregateGraph.SoundBank.Name + ".bnk" }) } }; game.Entities.Add(ent); foreach (var x in manifest.Entries) { var entry = x.Value["Attributes"]; var entity = new Entity(); bool isVocal = entry.ArrangementName == "Vocals"; bool isBass = entry.ArrangementName == "Bass"; entity.Id = entry.PersistentID.ToLower(); entity.Name = String.Format("GRSong_Asset_{0}_{1}", dlcName, entry.ArrangementName); entity.ModelName = "GRSong_Asset"; entity.Iterations = 46; game.Entities.Add(entity); var properties = new List<Property>(); var addProperty = new Action<string, object>((a, b) => properties.Add(CreateProperty(a, b.ToString()))); if (isBass || isVocal) addProperty("BinaryVersion", entry.BinaryVersion); addProperty("SongKey", entry.SongKey); addProperty("SongAsset", entry.SongAsset); addProperty("SongXml", entry.SongXml); addProperty("ForceUseXML", entry.ForceUseXML); addProperty("Shipping", entry.Shipping); addProperty("DisplayName", entry.DisplayName); addProperty("SongEvent", entry.SongEvent); if (isVocal) addProperty("InputEvent", entry.InputEvent); addProperty("ArrangementName", entry.ArrangementName); addProperty("RepresentativeArrangement", entry.RepresentativeArrangement); if (!isVocal && !String.IsNullOrEmpty(entry.VocalsAssetId)) { addProperty("VocalsAssetId", entry.VocalsAssetId.Split(new string[1] { "|" }, StringSplitOptions.RemoveEmptyEntries)[0]); var dynVisDen = new List<object>(); foreach (var y in entry.DynamicVisualDensity) dynVisDen.Add(y); properties.Add(CreateMultiItemProperty("DynamicVisualDensity", dynVisDen)); } addProperty("ArtistName", entry.ArtistName); addProperty("ArtistNameSort", entry.ArtistNameSort); addProperty("SongName", entry.SongName); addProperty("SongNameSort", entry.SongNameSort); addProperty("AlbumName", entry.AlbumName); addProperty("AlbumNameSort", entry.AlbumNameSort); addProperty("SongYear", entry.SongYear); if (!isVocal) { addProperty("RelativeDifficulty", entry.RelativeDifficulty); addProperty("AverageTempo", entry.AverageTempo);//fix this addProperty("NonStandardChords", true);//fix this addProperty("DoubleStops", entry.DoubleStops); addProperty("PowerChords", entry.PowerChords); addProperty("OpenChords", entry.OpenChords); addProperty("BarChords", entry.BarChords); addProperty("Sustain", entry.Sustain); addProperty("Bends", entry.Bends); addProperty("Slides", entry.Slides); addProperty("HOPOs", entry.HOPOs); addProperty("PalmMutes", entry.PalmMutes); addProperty("Vibrato", entry.Vibrato); addProperty("MasterID_Xbox360", entry.MasterID_Xbox360); addProperty("EffectChainName", entry.EffectChainName); addProperty("CrowdTempo", "Fast");//fix this } addProperty("AlbumArt", entry.AlbumArt); entity.Properties = properties; } game.Serialize(outStream); }
/// <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); }