Skip to content
Snippets Groups Projects
Commit 72c7591b authored by Timon Römer's avatar Timon Römer
Browse files

Adds Animation

parent b6179d80
No related branches found
No related tags found
No related merge requests found
...@@ -6,6 +6,8 @@ public class FlowEdge : Edge<FlowNode> ...@@ -6,6 +6,8 @@ public class FlowEdge : Edge<FlowNode>
{ {
public double MaxFlow { get; set; } public double MaxFlow { get; set; }
public double Residual { get; set; } public double Residual { get; set; }
public double CurrentFlow { get; set; }
public bool IsBackwards { get; set; } public bool IsBackwards { get; set; }
public FlowEdge(FlowNode source, FlowNode target, double maxFlow) public FlowEdge(FlowNode source, FlowNode target, double maxFlow)
......
...@@ -41,4 +41,35 @@ public class FlowGraph ...@@ -41,4 +41,35 @@ public class FlowGraph
{ {
return Graph.Edges; return Graph.Edges;
} }
// Clone method
public FlowGraph Clone()
{
var clonedGraph = new FlowGraph();
// Clone all vertices
foreach (var vertex in Graph.Vertices)
{
clonedGraph.AddVertex(vertex.Id, vertex.Label);
}
// Clone all edges
foreach (var edge in Graph.Edges)
{
var source = clonedGraph.GetVertexById(edge.Source.Id);
var target = clonedGraph.GetVertexById(edge.Target.Id);
if (source != null && target != null)
{
var clonedEdge = new FlowEdge(source, target, edge.MaxFlow)
{
Residual = edge.Residual,
IsBackwards = edge.IsBackwards
};
clonedGraph.Graph.AddEdge(clonedEdge);
}
}
return clonedGraph;
}
} }
\ No newline at end of file
...@@ -13,6 +13,9 @@ public class FordFulkersonAlgorithm ...@@ -13,6 +13,9 @@ public class FordFulkersonAlgorithm
} }
private readonly FlowGraph _flowGraph; private readonly FlowGraph _flowGraph;
private readonly List<FlowGraph> _graphStates = new();
public IReadOnlyList<FlowGraph> GraphStates => _graphStates;
public FordFulkersonAlgorithm(FlowGraph flowGraph) public FordFulkersonAlgorithm(FlowGraph flowGraph)
{ {
...@@ -39,6 +42,7 @@ public class FordFulkersonAlgorithm ...@@ -39,6 +42,7 @@ public class FordFulkersonAlgorithm
} }
Console.WriteLine("Start of Ford-Fulkerson Algorithm..."); Console.WriteLine("Start of Ford-Fulkerson Algorithm...");
SaveGraphState();
// execute as long as there is an augmenting path // execute as long as there is an augmenting path
while (FindAugmentingPath(source, target, pathFlow, strategy)) while (FindAugmentingPath(source, target, pathFlow, strategy))
...@@ -53,13 +57,18 @@ public class FordFulkersonAlgorithm ...@@ -53,13 +57,18 @@ public class FordFulkersonAlgorithm
} }
Console.WriteLine($"Bottleneck (minimum flow in path): {pathMinFlow}"); Console.WriteLine($"Bottleneck (minimum flow in path): {pathMinFlow}");
// add flow along the augmenting path // add forward flow along the augmenting path
foreach (var edge in pathFlow.Keys) foreach (var edge in pathFlow.Keys)
{ {
edge.Residual -= pathMinFlow; edge.Residual -= pathMinFlow;
Console.WriteLine($"Updating forward edge {edge.Source.Id} -> {edge.Target.Id}, New Residual: {edge.Residual}"); Console.WriteLine($"Updating forward edge {edge.Source.Id} -> {edge.Target.Id}, New Residual: {edge.Residual}");
}
SaveGraphState();
// create residual flow along the augmenting path
foreach (var edge in pathFlow.Keys)
{
// check if backwards edge exists // check if backwards edge exists
FlowEdge? reverseEdge; FlowEdge? reverseEdge;
if (!_flowGraph.Graph.TryGetEdge(edge.Target, edge.Source, out reverseEdge)) if (!_flowGraph.Graph.TryGetEdge(edge.Target, edge.Source, out reverseEdge))
...@@ -86,6 +95,8 @@ public class FordFulkersonAlgorithm ...@@ -86,6 +95,8 @@ public class FordFulkersonAlgorithm
// add to total flow // add to total flow
maxFlow += pathMinFlow; maxFlow += pathMinFlow;
Console.WriteLine($"Current max flow: {maxFlow}\n"); Console.WriteLine($"Current max flow: {maxFlow}\n");
SaveGraphState();
} }
Console.WriteLine("No Augmenting Path found anymore!\n"); Console.WriteLine("No Augmenting Path found anymore!\n");
...@@ -178,7 +189,7 @@ public class FordFulkersonAlgorithm ...@@ -178,7 +189,7 @@ public class FordFulkersonAlgorithm
} }
} }
public static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow) private static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow)
{ {
Console.WriteLine("Augmenting Path found:"); Console.WriteLine("Augmenting Path found:");
...@@ -203,4 +214,12 @@ public class FordFulkersonAlgorithm ...@@ -203,4 +214,12 @@ public class FordFulkersonAlgorithm
Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Residual Capacity: {pathFlow[edge]}"); Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Residual Capacity: {pathFlow[edge]}");
} }
} }
private void SaveGraphState()
{
// Deep copy the current graph state
var snapshot = _flowGraph.Clone();
_graphStates.Add(snapshot);
}
} }
\ No newline at end of file
<Window x:Class="FlowForge.MainWindow" <Window x:Class="FlowForge.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Flow-Forge" Height="600" Width="800"> Title="Flow-Forge" Height="600" Width="900">
<Grid x:Name="MainGrid"> <Grid x:Name="MainGrid">
<!-- Define two rows: one fixed height for the button and one for the graph viewer --> <!-- Define two rows: one fixed height for the button and one for the graph viewer -->
<Grid.RowDefinitions> <Grid.RowDefinitions>
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<!-- Button in the first row --> <!-- Button in the first row -->
<Button x:Name="InitializeGraphButton" <Button x:Name="InitializeGraphButton"
Content="Initialize Graph" Content="Initialize Graph"
Width="150" Width="120"
Height="40" Height="40"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Left" HorizontalAlignment="Left"
...@@ -24,36 +24,51 @@ ...@@ -24,36 +24,51 @@
<!-- Second Button in the first row --> <!-- Second Button in the first row -->
<Button x:Name="RunFordFulkersonButton" <Button x:Name="RunFordFulkersonButton"
Content="Run Ford-Fulkerson" Content="Run Ford-Fulkerson"
Width="150" Width="120"
Height="40" Height="40"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="350,10,0,10" Margin="310,10,0,10"
Grid.Row="0" Grid.Row="0"
Click="FordFulkersonButton_Click"/> Click="FordFulkersonButton_Click"/>
<!-- Second Button in the first row --> <!-- Second Button in the first row -->
<Button x:Name="RunEdmondsKarpButton" <Button x:Name="RunEdmondsKarpButton"
Content="Run Edmonds–Karp" Content="Run Edmonds–Karp"
Width="150" Width="120"
Height="40" Height="40"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="180,10,0,10" Margin="160,10,0,10"
Grid.Row="0" Grid.Row="0"
Click="EdmondsKarpButton_Click"/> Click="EdmondsKarpButton_Click"/>
<!-- Second Button in the first row --> <!-- Second Button in the first row -->
<Button x:Name="Export" <Button x:Name="Export"
Content="Export" Content="Export"
Width="150" Width="120"
Height="40" Height="40"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="520,10,0,10" Margin="460,10,0,10"
Grid.Row="0" Grid.Row="0"
Click="ExportToLatexButton_Click"/> Click="ExportToLatexButton_Click"/>
<!-- Checkboxes in the first row -->
<CheckBox x:Name="IncludeAnimationCheckbox"
Content="Include Animation"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="620,10,0,10"
Grid.Row="0"/>
<CheckBox x:Name="StandaloneCheckbox"
Content="Standalone"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="770,10,0,10"
Grid.Row="0"
IsChecked="True"/>
<!-- Placeholder for graph viewer --> <!-- Placeholder for graph viewer -->
<Grid x:Name="GraphViewerGrid" Grid.Row="1"/> <Grid x:Name="GraphViewerGrid" Grid.Row="1"/>
......
 using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
...@@ -25,7 +24,9 @@ namespace FlowForge ...@@ -25,7 +24,9 @@ namespace FlowForge
private FlowGraph? _flowGraph; private FlowGraph? _flowGraph;
private FordFulkersonAlgorithm? _fordFulkerson; private FordFulkersonAlgorithm? _fordFulkerson;
private Dictionary<string, Microsoft.Msagl.Core.Geometry.Point> _nodePositions = new Dictionary<string, Microsoft.Msagl.Core.Geometry.Point>(); private readonly Dictionary<string, Microsoft.Msagl.Core.Geometry.Point> _nodePositions =
new Dictionary<string, Microsoft.Msagl.Core.Geometry.Point>();
private MsaglDrawing.Graph _msaglGraph; private MsaglDrawing.Graph _msaglGraph;
public MainWindow() public MainWindow()
...@@ -60,19 +61,12 @@ namespace FlowForge ...@@ -60,19 +61,12 @@ namespace FlowForge
// Event handler for "Run Ford-Fulkerson" button // Event handler for "Run Ford-Fulkerson" button
private void FordFulkersonButton_Click(object sender, RoutedEventArgs e) private void FordFulkersonButton_Click(object sender, RoutedEventArgs e)
{ {
/*
// Move node "1" to position (100, 200)
_msaglGraph.FindNode("1").GeometryNode.Center = new Microsoft.Msagl.Core.Geometry.Point(20,700);
SetStraightLineEdges(_msaglGraph);
_gViewer.NeedToCalculateLayout = false;
_gViewer.Graph = _msaglGraph;*/
if (_gViewer != null) _gViewer.NeedToCalculateLayout = true; if (_gViewer != null) _gViewer.NeedToCalculateLayout = true;
SaveNodePositions(_gViewer?.Graph); // Save positions before running the algorithm SaveNodePositions(_gViewer?.Graph); // Save positions before running the algorithm
if (_fordFulkerson == null) if (_fordFulkerson == null)
{ {
MessageBox.Show("Please initialize the graph first by clicking the 'Initialize Graph' button!", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Please initialize the graph first by clicking the 'Initialize Graph' button!", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
return; return;
} }
...@@ -89,7 +83,8 @@ namespace FlowForge ...@@ -89,7 +83,8 @@ namespace FlowForge
{ {
if (_fordFulkerson == null) if (_fordFulkerson == null)
{ {
MessageBox.Show("Please initialize the graph first by clicking the 'Initialize Graph' button!", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Please initialize the graph first by clicking the 'Initialize Graph' button!", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
return; return;
} }
...@@ -164,27 +159,8 @@ namespace FlowForge ...@@ -164,27 +159,8 @@ namespace FlowForge
// Assign MSAGL graph to viewer for display // Assign MSAGL graph to viewer for display
if (_gViewer != null) if (_gViewer != null)
{ {
_gViewer.Graph = _msaglGraph;
// Set the graph and check which layout settings are applied by default
_gViewer.Graph = _msaglGraph; _gViewer.Graph = _msaglGraph;
// Print layout information to debug output
if (_msaglGraph.LayoutAlgorithmSettings is SugiyamaLayoutSettings)
{
Console.WriteLine("Using Sugiyama Layout");
}
else if (_msaglGraph.LayoutAlgorithmSettings is MdsLayoutSettings)
{
Console.WriteLine("Using MDS Layout");
}
else
{
Console.WriteLine("Using another layout algorithm");
}
if (_nodePositions.Any()) if (_nodePositions.Any())
{ {
ApplyNodePositions(_msaglGraph); ApplyNodePositions(_msaglGraph);
...@@ -197,7 +173,8 @@ namespace FlowForge ...@@ -197,7 +173,8 @@ namespace FlowForge
sugiyamaSettings.RepetitionCoefficientForOrdering = 100; sugiyamaSettings.RepetitionCoefficientForOrdering = 100;
sugiyamaSettings.EdgeRoutingSettings.KeepOriginalSpline = false; sugiyamaSettings.EdgeRoutingSettings.KeepOriginalSpline = false;
Microsoft.Msagl.Miscellaneous.LayoutHelpers.RouteAndLabelEdges(_msaglGraph.GeometryGraph, sugiyamaSettings, _msaglGraph.GeometryGraph.Edges, 100, new CancelToken()); Microsoft.Msagl.Miscellaneous.LayoutHelpers.RouteAndLabelEdges(_msaglGraph.GeometryGraph,
sugiyamaSettings, _msaglGraph.GeometryGraph.Edges, 100, new CancelToken());
/* /*
// Step 3: Apply layout settings to the graph // Step 3: Apply layout settings to the graph
...@@ -232,6 +209,7 @@ namespace FlowForge ...@@ -232,6 +209,7 @@ namespace FlowForge
private void SaveNodePositions(MsaglDrawing.Graph? msaglGraph) private void SaveNodePositions(MsaglDrawing.Graph? msaglGraph)
{ {
_nodePositions.Clear(); _nodePositions.Clear();
if (msaglGraph == null) return;
foreach (var node in msaglGraph.Nodes) foreach (var node in msaglGraph.Nodes)
{ {
var position = node.GeometryNode.Center; var position = node.GeometryNode.Center;
...@@ -272,125 +250,6 @@ namespace FlowForge ...@@ -272,125 +250,6 @@ namespace FlowForge
} }
} }
private string GenerateTikzCode_Curved()
{
if (_flowGraph == null)
throw new InvalidOperationException("Flow graph is not initialized.");
var tikzBuilder = new System.Text.StringBuilder();
tikzBuilder.AppendLine("\\documentclass[tikz,border=3mm]{standalone}");
tikzBuilder.AppendLine("\\usetikzlibrary{arrows.meta,positioning}");
tikzBuilder.AppendLine("\\usepackage{amsmath}");
tikzBuilder.AppendLine("\\begin{document}");
// Global scaling and styles for smaller nodes and text
tikzBuilder.AppendLine("\\begin{tikzpicture}[scale=3,->,>=stealth,shorten >=1pt,auto,node distance=2cm,thick,");
tikzBuilder.AppendLine("main node/.style={circle,draw,minimum size=5mm,inner sep=1pt,font=\\scriptsize}]");
// Nodes
foreach (var vertex in _flowGraph.Graph.Vertices)
{
if (_nodePositions.ContainsKey(vertex.Id))
{
var position = _nodePositions[vertex.Id];
// Format x and y as floating-point values with two decimal places
string formattedX = (position.X / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
string formattedY = (position.Y / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
tikzBuilder.AppendLine($"\\node[main node] ({vertex.Id}) at ({formattedX},{formattedY}) {{{vertex.Id}}};");
}
}
// Edges
foreach (var edge in _flowGraph.GetEdges())
{
string label = $"{edge.Residual}/{edge.MaxFlow}";
if (edge.IsBackwards)
{
// Reverse edge styling (curved, dashed, different color)
tikzBuilder.AppendLine($"\\path[every node/.style={{font=\\tiny}},bend right,dashed,red] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
else
{
// Normal edge styling
tikzBuilder.AppendLine($"\\path[every node/.style={{font=\\tiny}}] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
}
tikzBuilder.AppendLine("\\end{tikzpicture}");
tikzBuilder.AppendLine("\\end{document}");
return tikzBuilder.ToString();
}
private string GenerateTikzCode()
{
if (_flowGraph == null)
throw new InvalidOperationException("Flow graph is not initialized.");
var tikzBuilder = new System.Text.StringBuilder();
tikzBuilder.AppendLine("\\documentclass[tikz,border=3mm]{standalone}");
tikzBuilder.AppendLine("\\usetikzlibrary{arrows.meta,positioning}");
tikzBuilder.AppendLine("\\usepackage{amsmath}");
tikzBuilder.AppendLine("\\begin{document}");
// Global scaling and styles for smaller nodes and text
tikzBuilder.AppendLine("\\begin{tikzpicture}[scale=3,->,>=stealth,shorten >=1pt,auto,node distance=2cm,thick,");
tikzBuilder.AppendLine("main node/.style={circle,draw,minimum size=1mm,inner sep=1pt,font=\\scriptsize}]");
// Nodes
foreach (var vertex in _flowGraph.Graph.Vertices)
{
if (_nodePositions.ContainsKey(vertex.Id))
{
var position = _nodePositions[vertex.Id];
// Format x and y as floating-point values with two decimal places
string formattedX = (position.X / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
string formattedY = (position.Y / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
tikzBuilder.AppendLine($"\\node[main node] ({vertex.Id}) at ({formattedX},{formattedY}) {{{vertex.Id}}};");
}
}
// Edges
foreach (var edge in _flowGraph.GetEdges())
{
string label = $"{edge.Residual}/{edge.MaxFlow}";
if (edge.IsBackwards)
{
// Reverse edge styling: offset slightly above forward edges
tikzBuilder.AppendLine($"\\path[every node/.style={{font=\\tiny}},dashed,red] ({edge.Source.Id}) edge [pos=0.5,above] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
else
{
// Normal edge styling
tikzBuilder.AppendLine($"\\path[every node/.style={{font=\\tiny}}] ({edge.Source.Id}) edge [pos=0.5,below] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
}
tikzBuilder.AppendLine("\\end{tikzpicture}");
tikzBuilder.AppendLine("\\end{document}");
return tikzBuilder.ToString();
}
private void ExportGraphToLatex(string filePath)
{
try
{
string tikzCode = GenerateTikzCode_Curved();
System.IO.File.WriteAllText(filePath, tikzCode);
MessageBox.Show($"Graph successfully exported to LaTeX file:\n{filePath}", "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Error exporting graph: {ex.Message}", "Export Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ExportToLatexButton_Click(object sender, RoutedEventArgs e) private void ExportToLatexButton_Click(object sender, RoutedEventArgs e)
{ {
if (_flowGraph == null) if (_flowGraph == null)
...@@ -399,16 +258,8 @@ namespace FlowForge ...@@ -399,16 +258,8 @@ namespace FlowForge
return; return;
} }
var saveFileDialog = new Microsoft.Win32.SaveFileDialog TikzCodeGenerator tikzCodeGenerator = new TikzCodeGenerator(_flowGraph, _nodePositions, _fordFulkerson.GraphStates);
{ tikzCodeGenerator.ExportGraphToLatex(IncludeAnimationCheckbox.IsChecked, StandaloneCheckbox.IsChecked);
Filter = "TeX files (*.tex)|*.tex",
Title = "Export Graph to LaTeX"
};
if (saveFileDialog.ShowDialog() == true)
{
ExportGraphToLatex(saveFileDialog.FileName);
}
} }
} }
} }
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace FlowForge;
public class TikzCodeGenerator
{
private readonly FlowGraph _flowGraph;
private readonly Dictionary<string, Microsoft.Msagl.Core.Geometry.Point> _nodePositions;
private IReadOnlyList<FlowGraph> _graphStates;
public TikzCodeGenerator(FlowGraph flowGraph, Dictionary<string, Microsoft.Msagl.Core.Geometry.Point> nodePositions, IReadOnlyList<FlowGraph> graphStates)
{
this._flowGraph = flowGraph;
this._nodePositions = nodePositions;
this._graphStates = graphStates;
}
private string GenerateTikzCode_Curved(bool includeAnimations, bool isStandalone)
{
if (_flowGraph == null)
{
throw new InvalidOperationException("Flow graph is not initialized.");
}
var tikzBuilder = new System.Text.StringBuilder();
if (isStandalone)
{
BuildTikzHeader(tikzBuilder);
}
BuildTikzSettings(tikzBuilder);
BuildTikzNodes(tikzBuilder);
if (includeAnimations)
{
BuildTikzEdgesForAnimation(tikzBuilder);
}
else
{
BuildTikzEdges(tikzBuilder);
}
if (isStandalone)
{
BuildTikzFooter(tikzBuilder);
}
return tikzBuilder.ToString();
}
private static void BuildTikzFooter(StringBuilder tikzBuilder)
{
tikzBuilder.AppendLine("\\end{tikzpicture}");
tikzBuilder.AppendLine("\\end{frame} ");
tikzBuilder.AppendLine("\\end{document}");
}
private void BuildTikzEdges(StringBuilder tikzBuilder)
{
// Edges
foreach (var edge in _flowGraph.GetEdges())
{
if (edge.IsBackwards)
{
string label = $"0/{edge.Residual}";
// Reverse edge styling (curved, dashed, different color)
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}},bend right,dashed,red] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
else
{
string label = $"{edge.MaxFlow - edge.Residual}/{edge.MaxFlow}";
// Normal edge styling
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}}] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
}
}
private void BuildTikzEdgesForAnimation(StringBuilder tikzBuilder)
{
if (_graphStates == null || !_graphStates.Any())
throw new InvalidOperationException("No graph states available for animation.");
for (int frame = 0; frame < _graphStates.Count; frame++)
{
var stateGraph = _graphStates[frame];
// Begin the frame visibility block
tikzBuilder.AppendLine($"\\only<{frame + 1}>{{");
foreach (var edge in stateGraph.GetEdges())
{
string label = $"{edge.Residual}/{edge.MaxFlow}";
if (edge.IsBackwards)
{
// Reverse edge styling (curved, dashed, different color)
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}},bend right,dashed,red] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
else
{
// Normal edge styling
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}}] ({edge.Source.Id}) edge [pos=0.5,sloped] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
}
// End the frame visibility block
tikzBuilder.AppendLine("}");
}
}
private void BuildTikzNodes(StringBuilder tikzBuilder)
{
// Nodes
foreach (var vertex in _flowGraph.Graph.Vertices)
{
if (_nodePositions.ContainsKey(vertex.Id))
{
var position = _nodePositions[vertex.Id];
// Format x and y as floating-point values with two decimal places
string formattedX =
(position.X / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
string formattedY =
(position.Y / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
tikzBuilder.AppendLine(
$"\\node[main node] ({vertex.Id}) at ({formattedX},{formattedY}) {{{vertex.Id}}};");
}
}
}
private static void BuildTikzSettings(StringBuilder tikzBuilder)
{
// Global scaling and styles for smaller nodes and text
tikzBuilder.AppendLine(
"\\begin{tikzpicture}[scale=3,->,>=stealth,shorten >=1pt,auto,node distance=2cm,thick,");
tikzBuilder.AppendLine("main node/.style={circle,draw,minimum size=5mm,inner sep=1pt,font=\\scriptsize}]");
}
private static void BuildTikzHeader(StringBuilder tikzBuilder)
{
tikzBuilder.AppendLine("\\documentclass[tikz,border=3mm]{beamer}");
tikzBuilder.AppendLine("\\usepackage{tikz}");
tikzBuilder.AppendLine("\\usetikzlibrary{arrows.meta,positioning}");
tikzBuilder.AppendLine("\\usepackage{amsmath}");
tikzBuilder.AppendLine("\\begin{document}");
tikzBuilder.AppendLine("\\begin{frame}{Graph Animation}");
}
public void ExportGraphToLatex(bool? includeAnimation, bool? standalone)
{
bool includeAnimationChecked = includeAnimation ?? false;
bool standaloneChecked = standalone ?? true;
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "TeX files (*.tex)|*.tex",
Title = "Export Graph to LaTeX"
};
if (saveFileDialog.ShowDialog() != true) return;
string filePath = saveFileDialog.FileName;
try
{
string tikzCode = GenerateTikzCode_Curved(includeAnimationChecked, standaloneChecked);
System.IO.File.WriteAllText(filePath, tikzCode);
// Dynamic message based on options
string message = "Graph successfully exported to LaTeX file:\n" + filePath + "\n\nOptions:\n";
message += includeAnimationChecked ? "- Includes animation\n" : "- No animation\n";
message += standaloneChecked ? "- Standalone document\n" : "- TikZ code only (no standalone)";
MessageBox.Show(message, "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Error exporting graph: {ex.Message}", "Export Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private string GenerateTikzCode_WithAnimation()
{
throw new NotImplementedException();
}
private string StripStandaloneWrapper(string tikzCode)
{
var lines = tikzCode.Split('\n').ToList();
lines.RemoveAll(line => line.StartsWith("\\documentclass") || line.StartsWith("\\usepackage") || line.StartsWith("\\begin{document}") || line.StartsWith("\\end{document}"));
return string.Join("\n", lines);
}
private string GenerateTikzCode()
{
if (_flowGraph == null)
throw new InvalidOperationException("Flow graph is not initialized.");
var tikzBuilder = new System.Text.StringBuilder();
tikzBuilder.AppendLine("\\documentclass[tikz,border=3mm]{standalone}");
tikzBuilder.AppendLine("\\usetikzlibrary{arrows.meta,positioning}");
tikzBuilder.AppendLine("\\usepackage{amsmath}");
tikzBuilder.AppendLine("\\begin{document}");
// Global scaling and styles for smaller nodes and text
tikzBuilder.AppendLine(
"\\begin{tikzpicture}[scale=3,->,>=stealth,shorten >=1pt,auto,node distance=2cm,thick,");
tikzBuilder.AppendLine("main node/.style={circle,draw,minimum size=1mm,inner sep=1pt,font=\\scriptsize}]");
// Nodes
foreach (var vertex in _flowGraph.Graph.Vertices)
{
if (_nodePositions.ContainsKey(vertex.Id))
{
var position = _nodePositions[vertex.Id];
// Format x and y as floating-point values with two decimal places
string formattedX =
(position.X / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
string formattedY =
(position.Y / 100.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
tikzBuilder.AppendLine(
$"\\node[main node] ({vertex.Id}) at ({formattedX},{formattedY}) {{{vertex.Id}}};");
}
}
// Edges
foreach (var edge in _flowGraph.GetEdges())
{
string label = $"{edge.Residual}/{edge.MaxFlow}";
if (edge.IsBackwards)
{
// Reverse edge styling: offset slightly above forward edges
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}},dashed,red] ({edge.Source.Id}) edge [pos=0.5,above] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
else
{
// Normal edge styling
tikzBuilder.AppendLine(
$"\\path[every node/.style={{font=\\tiny}}] ({edge.Source.Id}) edge [pos=0.5,below] node {{\\texttt{{{label}}}}} ({edge.Target.Id});");
}
}
tikzBuilder.AppendLine("\\end{tikzpicture}");
tikzBuilder.AppendLine("\\end{document}");
return tikzBuilder.ToString();
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment