/* Copyright (c) Microsoft Corporation All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; namespace Microsoft.Research.Tools { /// /// Deals with the plane representation of a graph. /// public class GraphLayout { /// /// Drawing style. /// public enum Style { /// /// Normal style. /// Normal, /// /// Bold drawing. /// Bold, /// /// Fill with color, mostly for nodes. /// Filled, /// /// Dotted line. /// Dotted, /// /// Solid edge. /// Solid, } /// /// A node in the graph. /// public class GraphNode : IName { /// /// One of the node shapes. /// public enum NodeShape { /// /// Rectangle. /// Box, /// /// Ellipse. /// Ellipse, /// /// House upside-down. /// Invhouse, }; /// /// Node name. /// public string ObjectName { get; set; } /// /// Node position and size. /// public Rectangle2D Position { get; protected set; } /// /// Line color. /// public Color LineColor { get; set; } /// /// Node style. /// public Style Style { get; set; } /// /// Node shape. /// public NodeShape Shape { get; set; } /// /// Node label. /// public string Label { get; set; } /// /// Name of stage described by the plan node. /// public string Stage { get; set; } /// /// For each color a percentage, showing how much of the node to fill with that color. /// public List> FillColors { get; set; } private static Pen blackPen = new Pen(Color.Black); private static Pen thickBlackPen = new Pen(Color.Black, 4); /// /// Create a graph node at a specified position. /// /// X coordinate. /// Y coordinate. /// Width. /// Height. public GraphNode(double x, double y, double width, double height) { this.Position = new Rectangle2D(x, y, x + width, y + height); this.FillColors = new List>(); this.LineColor = Color.Black; } /// /// True if the node is selected on the plot. /// public bool Selected { get; set; } /// /// Draw the node on a panel surface. /// /// Surface to draw on. internal void Draw(DrawingSurface2D surface) { if (this.Position.Width > 0) { // Fill colors in bands from left to right double offset = 0; foreach (Tuple band in this.FillColors) { if (band.Item1 <= 0) continue; Color c = band.Item2; var brush = new SolidBrush(c); Point2D left = new Point2D(this.Position.Corner1.X + offset * this.Position.Width, this.Position.Corner1.Y); offset += band.Item1; Point2D right = new Point2D(this.Position.Corner1.X + offset * this.Position.Width, this.Position.Corner2.Y); surface.FillRectangle(brush, new Rectangle2D(left, right)); } Font font = new Font("Arial", 12, FontStyle.Regular); // let's shrink the position for the text to leave some border if (this.Label != null) { const double borderFraction = 8; // reserve 1/8 of the rectangle for border Rectangle2D box = new Rectangle2D( this.Position.Corner1.Translate(this.Position.Width / borderFraction, this.Position.Height / borderFraction), this.Position.Corner2.Translate(-this.Position.Width / borderFraction, -this.Position.Height / borderFraction)); surface.DrawTextInRectangle(this.Label, Brushes.Black, font, box); } } Pen pen = this.Selected ? thickBlackPen : blackPen; switch (this.Shape) { default: case NodeShape.Box: surface.DrawRectangle(this.Position, pen); break; case NodeShape.Ellipse: surface.DrawEllipse(this.LineColor, this.Position, pen, false); break; case NodeShape.Invhouse: { Point2D[] points = new Point2D[5]; points[0] = new Point2D(this.Position.Corner1.X, this.Position.Corner1.Y + this.Position.Height / 3); points[1] = new Point2D(this.Position.Corner1.X, this.Position.Corner2.Y - this.Position.Height / 8); points[2] = new Point2D(this.Position.Corner2.X, this.Position.Corner2.Y - this.Position.Height / 8); points[3] = new Point2D(this.Position.Corner2.X, this.Position.Corner1.Y + this.Position.Height / 3); points[4] = new Point2D(this.Position.Corner1.X + this.Position.Width / 2, this.Position.Corner1.Y); surface.DrawPolygon(this.LineColor, points, pen, false); break; } } } } /// /// An edge in the graph. /// public class GraphEdge { /// /// Name of tail node of edge. /// public string Tail { get; protected set; } /// /// Name of head node of edge. /// public string Head { get; protected set; } /// /// List of points. /// public List Spline { get; protected set; } /// /// Edge style. /// public Style Style { get; protected set; } /// /// Line color. /// public Color Color { get; protected set; } /// /// Create a simple one-segment edge. /// /// Start of edge x. /// Start of edge y. /// End of edge x. /// End of edge y. public GraphEdge(double xstart, double ystart, double xend, double yend) { this.Spline = new List(); this.Spline.Add(new Point2D(xstart, ystart)); this.Spline.Add(new Point2D(xend, yend)); this.Color = Color.Black; } /// /// Draw the edge on the specified surface. /// /// Surface to draw on. internal void Draw(DrawingSurface2D surface) { var previous = Spline[0]; int width = this.Style == Style.Bold ? 3 : 1; Pen pen = new Pen(this.Color); pen.Width = width; for (int i = 1; i < Spline.Count; i++) { Point2D point = Spline[i]; if (i == Spline.Count - 1) { // adjust for arrow size at the end of the edge. const double absoluteArrowSize = 0.15; double dx = point.X - previous.X; double dy = point.Y - previous.Y; double len = Math.Sqrt(dx * dx + dy * dy); double adx = absoluteArrowSize * dx / len; double ady = absoluteArrowSize * dy / len; point = new Point2D(point.X + adx, point.Y + ady); //pen.EndCap = LineCap.ArrowAnchor; AdjustableArrowCap cap = new AdjustableArrowCap(4, 6); pen.CustomEndCap = cap; } surface.DrawLine(pen, previous, point, false); previous = point; } } } /// /// The nodes in the graph. /// List nodes; /// /// The edges in the graph. /// List edges; /// /// Size of box containing graph layout. /// Point2D size; /// /// The list of all nodes in the graph. /// public IEnumerable AllNodes { get { return this.nodes; } } /// /// Create an empty graph layout. /// Height of graph to draw, in some arbitrary unit of measure; node and edge coordinates are relative to these. /// Width of graph to draw, in some arbitrary unit of measure; node and edge coordinates are relative to these. /// public GraphLayout(double width, double height) { this.size = new Point2D(width, height); this.edges = new List(); this.nodes = new List(); } /// /// Draw the graph on the specified surface. /// /// Surface to draw graph on. public void Draw(DrawingSurface2D surface) { foreach (GraphNode n in this.nodes) n.Draw(surface); foreach (GraphEdge e in this.edges) e.Draw(surface); } /// /// Coordinates of lower-right point for the layout. /// public Point2D Size { get { return this.size; } } /// /// Add a node to the graph. /// /// Node to add to the graph. public void Add(GraphNode node) { this.nodes.Add(node); } /// /// Add an edge to the graph. /// /// Edge to add. public void Add(GraphEdge edge) { this.edges.Add(edge); } /// /// Find node which contains the given coordinates. Return null if none. /// /// X coordinate where mouse was clicked. /// Y coordinate where mouse was clicked. /// Node which contains specified coordinates, or null. public GraphNode FindNode(double xo, double yo) { Point2D p = new Point2D(xo, yo); foreach (GraphNode node in this.nodes) if (node.Position.Inside(p)) return node; return null; } /// /// Clear the selection on all nodes. /// public void ClearSelectedNodes() { foreach (GraphNode n in this.nodes) n.Selected = false; } } }