// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Xml; using System.Xml.Serialization; namespace UnrealBuildTool { /// /// Represents a color in the displayed graph. Values are between 0.0 and 1.0 inclusive. /// struct GraphColor { public float R; public float G; public float B; public float A; } /// /// A single "node" in a directed graph /// class GraphNode { /// ID number, unique for all nodes. This must also be the array index into the main nodes array public int Id; /// Optional text label for this node public string Label; // Color of the node public GraphColor Color = new GraphColor() { R = 1.0f, G = 1.0f, B = 1.0f, A = 1.0f }; // Size public float Size = 1.0f; // Other attributes public Dictionary Attributes = new Dictionary(StringComparer.InvariantCultureIgnoreCase); } /// /// Describes a graph "edge" in a directed graph /// class GraphEdge { /// ID number, unique for all edges. This must also be the array index into the main edges array. public int Id; /// Source node (directed graph) public GraphNode Source; /// Target node (directed graph) public GraphNode Target; /// Optional edge weight public double Weight = 1.0f; // Edge color public GraphColor Color = new GraphColor() { R = 0.0f, G = 0.0f, B = 0.0f, A = 0.25f }; // Thickness public float Thickness = 0.1f; } /// /// Describes a type of attribute. This is derived internally from the attribute data on nodes. /// class GraphAttribute { /// Gexf ID for this attribute public int ID; /// Name of the attribute public string Name; /// Gexf type name public string TypeName; } static class GraphVisualization { /// /// Writes a GEXF graph file for the specified graph nodes and edges /// /// The file name to write /// The description to include in the graph file's metadata /// List of all graph nodes. Index order is important and must match with the individual node Id members! /// List of all graph edges. Index order is important and must match with the individual edge Id members! public static void WriteGraphFile(string Filename, string Description, List GraphNodes, List GraphEdges) { XmlWriterSettings Settings = new XmlWriterSettings(); Settings.Indent = true; Settings.IndentChars = " "; // Figure out all of the custom attribute types we're dealing with Dictionary AllAttributes = new Dictionary(StringComparer.InvariantCultureIgnoreCase); int NextAttributeID = 0; foreach (GraphNode GraphNode in GraphNodes) { foreach (string AttributeName in GraphNode.Attributes.Keys) { object AttributeValue = GraphNode.Attributes[AttributeName]; string AttributeTypeName; if (AttributeValue.GetType() == typeof(int)) { AttributeTypeName = "integer"; } else if (AttributeValue.GetType() == typeof(float)) { AttributeTypeName = "float"; } else if (AttributeValue.GetType() == typeof(double)) { AttributeTypeName = "double"; } else if (AttributeValue.GetType() == typeof(string)) { AttributeTypeName = "string"; } else if (AttributeValue.GetType() == typeof(bool)) { AttributeTypeName = "boolean"; } else { // No other types supported yet! throw new InvalidOperationException("Unsupported attribute data type encountered on graph node!"); } GraphAttribute Attribute; if (!AllAttributes.TryGetValue(AttributeName, out Attribute)) { Attribute = new GraphAttribute(); Attribute.ID = NextAttributeID++; Attribute.Name = AttributeName; Attribute.TypeName = AttributeTypeName; AllAttributes[AttributeName] = Attribute; } else { if (!Attribute.TypeName.Equals(AttributeTypeName)) { throw new InvalidOperationException("Multiple graph nodes with the same attribute name but different types encountered!"); } } } } using (XmlWriter Writer = XmlWriter.Create(Filename, Settings)) { // NOTE: The GEXF XML format is defined here: http://gexf.net/1.2draft/gexf-12draft-primer.pdf string GEXFNamespace = "http://www.gexf.net/1.2-draft"; string SchemaNamespace = "http://www.w3.org/2001/XMLSchema-instance"; string VizNamespace = "http://www.gexf.net/1.2draft/viz"; Writer.WriteStartElement("gexf", GEXFNamespace); Writer.WriteAttributeString("xmlns", "xsi", null, SchemaNamespace); Writer.WriteAttributeString("schemaLocation", SchemaNamespace, "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"); Writer.WriteAttributeString("xmlns", "viz", null, VizNamespace); Writer.WriteAttributeString("version", "1.2"); Writer.WriteStartElement("meta"); { Writer.WriteAttributeString("creator", "UnrealBuildTool"); Writer.WriteAttributeString("description", Description); } Writer.WriteEndElement(); // meta { Writer.WriteStartElement("graph"); { Writer.WriteAttributeString("mode", "static"); Writer.WriteAttributeString("defaultedgetype", "directed"); if (AllAttributes.Count > 0) { Writer.WriteStartElement("attributes"); { // @todo: Add support for edge attributes, not just node attributes Writer.WriteAttributeString("class", "node"); // Node attributes, not edges! foreach (GraphAttribute Attribute in AllAttributes.Values) { Writer.WriteStartElement("attribute"); { Writer.WriteAttributeString("id", Attribute.ID.ToString()); Writer.WriteAttributeString("title", Attribute.Name); Writer.WriteAttributeString("type", Attribute.TypeName); } Writer.WriteEndElement(); // attribute } // @todo: Add support for attribute type default values } Writer.WriteEndElement(); // attributes } Writer.WriteStartElement("nodes"); { foreach (GraphNode GraphNode in GraphNodes) { Writer.WriteStartElement("node"); { Writer.WriteAttributeString("id", GraphNode.Id.ToString()); Writer.WriteAttributeString("label", GraphNode.Label); Writer.WriteStartElement("color", VizNamespace); { Writer.WriteAttributeString("r", ((int)(GraphNode.Color.R * 255.0f)).ToString()); Writer.WriteAttributeString("g", ((int)(GraphNode.Color.G * 255.0f)).ToString()); Writer.WriteAttributeString("b", ((int)(GraphNode.Color.B * 255.0f)).ToString()); Writer.WriteAttributeString("a", GraphNode.Color.A.ToString()); } Writer.WriteEndElement(); // viz:color Writer.WriteStartElement("size", VizNamespace); { Writer.WriteAttributeString("value", GraphNode.Size.ToString()); } Writer.WriteEndElement(); // viz:size Writer.WriteStartElement("shape", VizNamespace); { // NOTE: Valid shapes are: disc, square, triangle, diamond, image Writer.WriteAttributeString("value", "disc"); } Writer.WriteEndElement(); // viz:shape if (GraphNode.Attributes.Count > 0) { Writer.WriteStartElement("attvalues"); { foreach (KeyValuePair AttributeHashEntry in GraphNode.Attributes) { string AttributeName = AttributeHashEntry.Key; object AttributeValue = AttributeHashEntry.Value; GraphAttribute Attribute = AllAttributes[AttributeName]; Writer.WriteStartElement("attvalue"); { Writer.WriteAttributeString("for", Attribute.ID.ToString()); Writer.WriteAttributeString("value", AttributeValue.ToString()); } Writer.WriteEndElement(); } } Writer.WriteEndElement(); // attvalues } } Writer.WriteEndElement(); // node } } Writer.WriteEndElement(); // nodes Writer.WriteStartElement("edges"); { foreach (GraphEdge GraphEdge in GraphEdges) { Writer.WriteStartElement("edge"); { Writer.WriteAttributeString("id", GraphEdge.Id.ToString()); Writer.WriteAttributeString("source", GraphEdge.Source.Id.ToString()); Writer.WriteAttributeString("target", GraphEdge.Target.Id.ToString()); Writer.WriteAttributeString("weight", GraphEdge.Weight.ToString()); Writer.WriteStartElement("color", VizNamespace); { Writer.WriteAttributeString("r", ((int)(GraphEdge.Color.R * 255.0f)).ToString()); Writer.WriteAttributeString("g", ((int)(GraphEdge.Color.G * 255.0f)).ToString()); Writer.WriteAttributeString("b", ((int)(GraphEdge.Color.B * 255.0f)).ToString()); Writer.WriteAttributeString("a", GraphEdge.Color.A.ToString()); } Writer.WriteEndElement(); // viz:color Writer.WriteStartElement("thickness", VizNamespace); { Writer.WriteAttributeString("value", GraphEdge.Thickness.ToString()); } Writer.WriteEndElement(); // viz:thickness Writer.WriteStartElement("shape", VizNamespace); { // NOTE: Valid shapes are: solid, dotted, dashed, double Writer.WriteAttributeString("value", "solid"); } Writer.WriteEndElement(); // viz:shape } Writer.WriteEndElement(); // edge } } Writer.WriteEndElement(); // nodes } Writer.WriteEndElement(); // graph } Writer.WriteEndElement(); // gexf Writer.Flush(); } } } }