/*
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;
}
}
}