366 lines
13 KiB
C#
366 lines
13 KiB
C#
|
|
/*
|
|
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.Calypso.Tools
|
|
{
|
|
/// <summary>
|
|
/// Deals with the plane representation of a graph.
|
|
/// </summary>
|
|
public class GraphLayout
|
|
{
|
|
/// <summary>
|
|
/// Drawing style.
|
|
/// </summary>
|
|
public enum Style
|
|
{
|
|
/// <summary>
|
|
/// Normal style.
|
|
/// </summary>
|
|
Normal,
|
|
/// <summary>
|
|
/// Bold drawing.
|
|
/// </summary>
|
|
Bold,
|
|
/// <summary>
|
|
/// Fill with color, mostly for nodes.
|
|
/// </summary>
|
|
Filled,
|
|
/// <summary>
|
|
/// Dotted line.
|
|
/// </summary>
|
|
Dotted,
|
|
/// <summary>
|
|
/// Solid edge.
|
|
/// </summary>
|
|
Solid,
|
|
}
|
|
|
|
/// <summary>
|
|
/// A node in the graph.
|
|
/// </summary>
|
|
public class GraphNode : IName
|
|
{
|
|
/// <summary>
|
|
/// One of the node shapes.
|
|
/// </summary>
|
|
public enum NodeShape
|
|
{
|
|
/// <summary>
|
|
/// Rectangle.
|
|
/// </summary>
|
|
Box,
|
|
/// <summary>
|
|
/// Ellipse.
|
|
/// </summary>
|
|
Ellipse,
|
|
/// <summary>
|
|
/// House upside-down.
|
|
/// </summary>
|
|
Invhouse,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Node name.
|
|
/// </summary>
|
|
public string ObjectName { get; set; }
|
|
/// <summary>
|
|
/// Node position and size.
|
|
/// </summary>
|
|
public Rectangle2D Position { get; protected set; }
|
|
/// <summary>
|
|
/// Line color.
|
|
/// </summary>
|
|
public Color LineColor { get; set; }
|
|
/// <summary>
|
|
/// Node style.
|
|
/// </summary>
|
|
public Style Style { get; set; }
|
|
/// <summary>
|
|
/// Node shape.
|
|
/// </summary>
|
|
public NodeShape Shape { get; set; }
|
|
/// <summary>
|
|
/// Node label.
|
|
/// </summary>
|
|
public string Label { get; set; }
|
|
/// <summary>
|
|
/// Name of stage described by the plan node.
|
|
/// </summary>
|
|
public string Stage { get; set; }
|
|
/// <summary>
|
|
/// For each color a percentage, showing how much of the node to fill with that color.
|
|
/// </summary>
|
|
public List<Tuple<double, Color>> FillColors { get; set; }
|
|
private static Pen blackPen = new Pen(Color.Black);
|
|
private static Pen thickBlackPen = new Pen(Color.Black, 4);
|
|
|
|
/// <summary>
|
|
/// Create a graph node at a specified position.
|
|
/// </summary>
|
|
/// <param name="x">X coordinate.</param>
|
|
/// <param name="y">Y coordinate.</param>
|
|
/// <param name="width">Width.</param>
|
|
/// <param name="height">Height.</param>
|
|
public GraphNode(double x, double y, double width, double height)
|
|
{
|
|
this.Position = new Rectangle2D(x, y, x + width, y + height);
|
|
this.FillColors = new List<Tuple<double, Color>>();
|
|
this.LineColor = Color.Black;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the node is selected on the plot.
|
|
/// </summary>
|
|
public bool Selected { get; set; }
|
|
|
|
/// <summary>
|
|
/// Draw the node on a panel surface.
|
|
/// </summary>
|
|
/// <param name="surface">Surface to draw on.</param>
|
|
internal void Draw(DrawingSurface2D surface)
|
|
{
|
|
if (this.Position.Width > 0)
|
|
{
|
|
// Fill colors in bands from left to right
|
|
double offset = 0;
|
|
foreach (Tuple<double, Color> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An edge in the graph.
|
|
/// </summary>
|
|
public class GraphEdge
|
|
{
|
|
/// <summary>
|
|
/// Name of tail node of edge.
|
|
/// </summary>
|
|
public string Tail { get; protected set; }
|
|
/// <summary>
|
|
/// Name of head node of edge.
|
|
/// </summary>
|
|
public string Head { get; protected set; }
|
|
/// <summary>
|
|
/// List of points.
|
|
/// </summary>
|
|
public List<Point2D> Spline { get; protected set; }
|
|
/// <summary>
|
|
/// Edge style.
|
|
/// </summary>
|
|
public Style Style { get; protected set; }
|
|
/// <summary>
|
|
/// Line color.
|
|
/// </summary>
|
|
public Color Color { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Create a simple one-segment edge.
|
|
/// </summary>
|
|
/// <param name="xstart">Start of edge x.</param>
|
|
/// <param name="ystart">Start of edge y.</param>
|
|
/// <param name="xend">End of edge x.</param>
|
|
/// <param name="yend">End of edge y.</param>
|
|
public GraphEdge(double xstart, double ystart, double xend, double yend)
|
|
{
|
|
this.Spline = new List<Point2D>();
|
|
this.Spline.Add(new Point2D(xstart, ystart));
|
|
this.Spline.Add(new Point2D(xend, yend));
|
|
this.Color = Color.Black;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the edge on the specified surface.
|
|
/// </summary>
|
|
/// <param name="surface">Surface to draw on.</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The nodes in the graph.
|
|
/// </summary>
|
|
List<GraphNode> nodes;
|
|
/// <summary>
|
|
/// The edges in the graph.
|
|
/// </summary>
|
|
List<GraphEdge> edges;
|
|
/// <summary>
|
|
/// Size of box containing graph layout.
|
|
/// </summary>
|
|
Point2D size;
|
|
|
|
/// <summary>
|
|
/// The list of all nodes in the graph.
|
|
/// </summary>
|
|
public IEnumerable<GraphNode> AllNodes { get { return this.nodes; } }
|
|
|
|
/// <summary>
|
|
/// Create an empty graph layout.
|
|
/// <param name="height">Height of graph to draw, in some arbitrary unit of measure; node and edge coordinates are relative to these.</param>
|
|
/// <param name="width">Width of graph to draw, in some arbitrary unit of measure; node and edge coordinates are relative to these.</param>
|
|
/// </summary>
|
|
public GraphLayout(double width, double height)
|
|
{
|
|
this.size = new Point2D(width, height);
|
|
this.edges = new List<GraphEdge>();
|
|
this.nodes = new List<GraphNode>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the graph on the specified surface.
|
|
/// </summary>
|
|
/// <param name="surface">Surface to draw graph on.</param>
|
|
public void Draw(DrawingSurface2D surface)
|
|
{
|
|
foreach (GraphNode n in this.nodes)
|
|
n.Draw(surface);
|
|
foreach (GraphEdge e in this.edges)
|
|
e.Draw(surface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coordinates of lower-right point for the layout.
|
|
/// </summary>
|
|
public Point2D Size { get { return this.size; } }
|
|
|
|
/// <summary>
|
|
/// Add a node to the graph.
|
|
/// </summary>
|
|
/// <param name="node">Node to add to the graph.</param>
|
|
public void Add(GraphNode node)
|
|
{
|
|
this.nodes.Add(node);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an edge to the graph.
|
|
/// </summary>
|
|
/// <param name="edge">Edge to add.</param>
|
|
public void Add(GraphEdge edge)
|
|
{
|
|
this.edges.Add(edge);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find node which contains the given coordinates. Return null if none.
|
|
/// </summary>
|
|
/// <param name="xo">X coordinate where mouse was clicked.</param>
|
|
/// <param name="yo">Y coordinate where mouse was clicked.</param>
|
|
/// <returns>Node which contains specified coordinates, or null.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the selection on all nodes.
|
|
/// </summary>
|
|
public void ClearSelectedNodes()
|
|
{
|
|
foreach (GraphNode n in this.nodes)
|
|
n.Selected = false;
|
|
}
|
|
}
|
|
}
|