Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4085 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
12 changed files with 526 additions and 78 deletions
@ -1,19 +0,0 @@
@@ -1,19 +0,0 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Description of Point.
|
||||
/// </summary>
|
||||
public struct Point |
||||
{ |
||||
public double X { get; set; } |
||||
public double Y { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Text; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Converts <see cref="PositionedGraph/> to Graphviz's string (dot format) and back (from plain format).
|
||||
/// </summary>
|
||||
public class DotGraph |
||||
{ |
||||
private PositionedGraph posGraph; |
||||
|
||||
NeatoPositionTransform transform; |
||||
|
||||
// state (node and edge names) needed for converting back
|
||||
private Dictionary<PositionedNode, string> nodeNames = new Dictionary<PositionedNode, string>(); |
||||
private Dictionary<PositionedEdge, string> edgeNames = new Dictionary<PositionedEdge, string>(); |
||||
|
||||
private CultureInfo formatCulture = CultureInfo.GetCultureInfo("en-US"); |
||||
|
||||
/// <summary>
|
||||
/// Used for generating node and edge names.
|
||||
/// </summary>
|
||||
private IdGenerator genId = new IdGenerator(); |
||||
|
||||
public DotGraph(PositionedGraph posGraph) |
||||
{ |
||||
if (posGraph.Nodes.Count() == 0) |
||||
{ |
||||
throw new ArgumentException("Cannot process empty graphs."); |
||||
} |
||||
this.posGraph = posGraph; |
||||
this.transform = new NeatoPositionTransform(this.posGraph.BoundingRect); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets Graphviz's dot format string for the positioned graph.
|
||||
/// </summary>
|
||||
public string DotGraphString |
||||
{ |
||||
get |
||||
{ |
||||
StringBuilder dotStringBuilder = new StringBuilder("digraph G { node [shape = box];"); |
||||
|
||||
foreach (PositionedNode posNode in this.posGraph.Nodes) |
||||
{ |
||||
appendPosNode(posNode, dotStringBuilder); |
||||
} |
||||
foreach (PositionedEdge posEdge in this.posGraph.Edges) |
||||
{ |
||||
appendPosEdge(posEdge, dotStringBuilder); |
||||
} |
||||
|
||||
dotStringBuilder.AppendLine("}"); |
||||
return dotStringBuilder.ToString(); |
||||
} |
||||
} |
||||
|
||||
private void appendPosNode(PositionedNode node, StringBuilder builder) |
||||
{ |
||||
string nodeName = genId.GetNextId().ToString(); |
||||
nodeNames[node] = nodeName; |
||||
|
||||
Rect neatoInput = transform.NodeToNeatoInput(node); |
||||
|
||||
string dotFormatNode = |
||||
string.Format(formatCulture, |
||||
"{0} [pos=\"{1},{2}!\" width=\"{3}\" height=\"{4}\"];", |
||||
nodeName, neatoInput.Location.X, neatoInput.Location.Y, neatoInput.Width, neatoInput.Height); |
||||
builder.AppendLine(dotFormatNode); |
||||
} |
||||
|
||||
private void appendPosEdge(PositionedEdge edge, StringBuilder builder) |
||||
{ |
||||
string sourceNodeName = nodeNames[edge.SourceNode]; |
||||
string targetNodeName = nodeNames[edge.TargetNode]; |
||||
|
||||
builder.AppendLine(string.Format("{0} -> {1}", sourceNodeName, targetNodeName)); |
||||
} |
||||
|
||||
private bool validateSplinePoints(PositionedEdge edge) |
||||
{ |
||||
// must have correct number of points: one start point and 3 points for every bezier segment
|
||||
return ((edge.SplinePoints.Count - 1) % 3) == 0; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Parses edge positions (from Graphviz's plain format) and sets these positions to underlying positioned graph.
|
||||
/// </summary>
|
||||
/// <param name="dotGraphString">Graph with positions in Graphviz's plain format</param>
|
||||
/// <returns><see cref="PositionedGraph"/> with edge positions filled.</returns>
|
||||
public PositionedGraph ParseEdgePositions(string dotGraphString) |
||||
{ |
||||
using (StringReader reader = new System.IO.StringReader(dotGraphString)) |
||||
{ |
||||
skipAfterPattern(reader, "node " + nodeNames[posGraph.Nodes.First()] + " "); |
||||
Point neatoFirstNodePos = readPoint(reader); |
||||
PositionedNode firstNode = posGraph.Nodes.First(); |
||||
Point firstNodePosOur = transform.AsNeato(firstNode.Center); |
||||
// determine how Neato shifted the nodes
|
||||
transform.NeatoShiftX = neatoFirstNodePos.X - firstNodePosOur.X; |
||||
transform.NeatoShiftY = neatoFirstNodePos.Y - firstNodePosOur.Y; |
||||
|
||||
foreach (PositionedEdge posEdge in posGraph.Edges) |
||||
{ |
||||
skipAfterPattern(reader, "edge "); |
||||
|
||||
readWord(reader); // source node name
|
||||
readWord(reader); // target node name
|
||||
|
||||
int splinePointCount = readInt(reader); |
||||
for (int i = 0; i < splinePointCount; i++) |
||||
{ |
||||
Point edgePoint = readPoint(reader); |
||||
edgePoint = transform.FromNeatoOutput(edgePoint); |
||||
posEdge.SplinePoints.Add(edgePoint); |
||||
} |
||||
|
||||
bool edgeOk = validateSplinePoints(posEdge); |
||||
if (!edgeOk) |
||||
throw new DebuggerVisualizerException("Parsed edge invalid"); |
||||
} |
||||
} |
||||
// return original graph with filled edge positions
|
||||
return this.posGraph; |
||||
} |
||||
|
||||
private Point readPoint(TextReader reader) |
||||
{ |
||||
double x = readDouble(reader); |
||||
double y = readDouble(reader); |
||||
|
||||
return new Point(x, y); |
||||
} |
||||
|
||||
private double readDouble(TextReader reader) |
||||
{ |
||||
return double.Parse(readWord(reader), formatCulture); |
||||
} |
||||
|
||||
private int readInt(TextReader reader) |
||||
{ |
||||
return int.Parse(readWord(reader)); |
||||
} |
||||
|
||||
private string readWord(TextReader reader) |
||||
{ |
||||
StringBuilder word = new StringBuilder(); |
||||
int ch = ' '; |
||||
while ((ch = reader.Read()) != ' ') |
||||
{ |
||||
if (ch == -1 || ch == '\n' || ch == '\t') |
||||
break; |
||||
|
||||
word.Append((char)ch); |
||||
} |
||||
return word.ToString(); |
||||
} |
||||
|
||||
private bool skipAfterPattern(StringReader reader, string pattern) |
||||
{ |
||||
int ch = -1; |
||||
int pIndex = 0; |
||||
int pTarget = pattern.Length; |
||||
while ((ch = reader.Read()) != -1) |
||||
{ |
||||
if (ch == pattern[pIndex]) |
||||
{ |
||||
pIndex++; |
||||
if (pIndex == pTarget) |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
pIndex = 0; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Windows; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Transforms positions to and from Neato.
|
||||
/// </summary>
|
||||
public class NeatoPositionTransform |
||||
{ |
||||
private Rect graphBoundingRect; |
||||
|
||||
// So that Neato works with smaller numbers. Always > 1
|
||||
double ourInputScale = 40; |
||||
// Neato itself scales input by this constant. Always > 1, 1 for plain output, 72 for .dot output
|
||||
double neatoOutputScale = 1; |
||||
|
||||
public NeatoPositionTransform(Rect graphBoundingRectangle) |
||||
{ |
||||
graphBoundingRect = graphBoundingRectangle; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// X shift, in Neato coords.
|
||||
/// </summary>
|
||||
public double NeatoShiftX { get; set; } |
||||
/// <summary>
|
||||
/// Y shift, in Neato coords.
|
||||
/// </summary>
|
||||
public double NeatoShiftY { get; set; } |
||||
|
||||
public Point ToNeatoInput(Point ourPoint) |
||||
{ |
||||
// invert Y axis, as Neato expects this
|
||||
return new Point(ourPoint.X / ourInputScale, (graphBoundingRect.Bottom - ourPoint.Y) / ourInputScale); |
||||
} |
||||
|
||||
public System.Windows.Point FromNeatoOutput(Point neatoPoint) |
||||
{ |
||||
// Neato multiplies coords by 72 and adds arbitrary shift (which has to be parsed)
|
||||
double ourX = (neatoPoint.X - NeatoShiftX) / neatoOutputScale * ourInputScale; |
||||
double ourYInverted = (neatoPoint.Y - NeatoShiftY) / neatoOutputScale * ourInputScale; |
||||
// invert back - our Y axis grows down
|
||||
return new Point(ourX, graphBoundingRect.Bottom - ourYInverted); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Transform points as Neato would transform it if Neato used no shift
|
||||
/// </summary>
|
||||
/// <param name="ourPoint"></param>
|
||||
/// <returns></returns>
|
||||
public System.Windows.Point AsNeato(Point ourPoint) |
||||
{ |
||||
|
||||
return new Point(ourPoint.X * neatoOutputScale / ourInputScale, (graphBoundingRect.Bottom - ourPoint.Y) * neatoOutputScale / ourInputScale); |
||||
} |
||||
|
||||
public Rect NodeToNeatoInput(PositionedNode node) |
||||
{ |
||||
// don't transform size
|
||||
return new Rect(ToNeatoInput(node.Center), |
||||
new Size(node.Width / ourInputScale, node.Height / ourInputScale)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Encapsulates Neato.exe.
|
||||
/// </summary>
|
||||
public class NeatoProcess |
||||
{ |
||||
private System.Diagnostics.Process neatoProcess; |
||||
|
||||
/// <summary>
|
||||
/// Creates new NeatoProcess.
|
||||
/// </summary>
|
||||
/// <param name="neatoProcess">Underlying neato.exe process</param>
|
||||
private NeatoProcess(System.Diagnostics.Process neatoProcess) |
||||
{ |
||||
this.neatoProcess = neatoProcess; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Starts neato.exe
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static NeatoProcess Start() |
||||
{ |
||||
System.Diagnostics.Process neatoProcess = new System.Diagnostics.Process(); |
||||
string neatoPath = getNeatoExePath(); |
||||
neatoProcess.StartInfo.FileName = neatoPath; |
||||
neatoProcess.StartInfo.RedirectStandardInput = true; |
||||
neatoProcess.StartInfo.RedirectStandardError = true; |
||||
neatoProcess.StartInfo.RedirectStandardOutput = true; |
||||
neatoProcess.StartInfo.UseShellExecute = false; |
||||
neatoProcess.StartInfo.Arguments = " -Gsplines=true -Tplain"; |
||||
//p.EnableRaisingEvents = true;
|
||||
neatoProcess.Exited += delegate { |
||||
neatoProcess.Dispose(); |
||||
}; |
||||
/*p.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e) { |
||||
}; |
||||
p.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs e) { |
||||
};*/ |
||||
|
||||
neatoProcess.Start(); |
||||
|
||||
return new NeatoProcess(neatoProcess); |
||||
} |
||||
|
||||
private static string getCurrentAssemblyPath() |
||||
{ |
||||
return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); |
||||
} |
||||
|
||||
private static string getNeatoExePath() |
||||
{ |
||||
return System.IO.Path.Combine(getCurrentAssemblyPath(), "neato.exe"); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Passes given graph to neato and reads output.
|
||||
/// </summary>
|
||||
/// <param name="dotGraph">Graph in Graphviz dot format.</param>
|
||||
/// <returns>Same graph in Graphviz plain with position information added.</returns>
|
||||
public string CalculatePositions(string dotGraph) |
||||
{ |
||||
neatoProcess.StandardInput.Write(dotGraph); |
||||
neatoProcess.StandardInput.Flush(); |
||||
neatoProcess.StandardInput.Close(); |
||||
|
||||
StringBuilder output = new StringBuilder(); |
||||
while(true) |
||||
{ |
||||
string line = neatoProcess.StandardOutput.ReadLine(); |
||||
if (line == null) |
||||
{ |
||||
// happens if neato.exe is killed
|
||||
throw new DebuggerVisualizerException("Problem getting layout information from neato.exe"); |
||||
} |
||||
if (line == "stop") |
||||
{ |
||||
break; |
||||
} |
||||
output.AppendLine(line); |
||||
} |
||||
|
||||
return output.ToString(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue