diff --git a/FlowEdge.cs b/FlowEdge.cs index f16855d837bc7c2791552b609992db58bfb5c40b..5e5e251f8d7070394bab472d7c93250ac193a478 100644 --- a/FlowEdge.cs +++ b/FlowEdge.cs @@ -6,6 +6,8 @@ public class FlowEdge : Edge<FlowNode> { public double MaxFlow { get; set; } public double Residual { get; set; } + + public double CurrentFlow { get; set; } public bool IsBackwards { get; set; } public FlowEdge(FlowNode source, FlowNode target, double maxFlow) diff --git a/FlowGraph.cs b/FlowGraph.cs index ba1def8ff97315e2d2b8f4ecc02589163143897f..71e3195b797f0aec9c0f12aa74c3196915750546 100644 --- a/FlowGraph.cs +++ b/FlowGraph.cs @@ -41,4 +41,35 @@ public class FlowGraph { 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 diff --git a/FordFulkersonAlgorithm.cs b/FordFulkersonAlgorithm.cs index a4803a4e232846232681cda05e1f498e337b0355..3071aa8b6b3a9963f62752be4df3e609c9fe38a2 100644 --- a/FordFulkersonAlgorithm.cs +++ b/FordFulkersonAlgorithm.cs @@ -13,6 +13,9 @@ public class FordFulkersonAlgorithm } private readonly FlowGraph _flowGraph; + private readonly List<FlowGraph> _graphStates = new(); + + public IReadOnlyList<FlowGraph> GraphStates => _graphStates; public FordFulkersonAlgorithm(FlowGraph flowGraph) { @@ -39,6 +42,7 @@ public class FordFulkersonAlgorithm } Console.WriteLine("Start of Ford-Fulkerson Algorithm..."); + SaveGraphState(); // execute as long as there is an augmenting path while (FindAugmentingPath(source, target, pathFlow, strategy)) @@ -53,13 +57,18 @@ public class FordFulkersonAlgorithm } 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) { edge.Residual -= pathMinFlow; 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 FlowEdge? reverseEdge; if (!_flowGraph.Graph.TryGetEdge(edge.Target, edge.Source, out reverseEdge)) @@ -86,6 +95,8 @@ public class FordFulkersonAlgorithm // add to total flow maxFlow += pathMinFlow; Console.WriteLine($"Current max flow: {maxFlow}\n"); + + SaveGraphState(); } Console.WriteLine("No Augmenting Path found anymore!\n"); @@ -177,8 +188,8 @@ public class FordFulkersonAlgorithm currentNode = pathEdge.Source; } } - - public static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow) + + private static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow) { Console.WriteLine("Augmenting Path found:"); @@ -203,4 +214,12 @@ public class FordFulkersonAlgorithm 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 diff --git a/MainWindow.xaml b/MainWindow.xaml index 2bd60b0d59c15040c0c6b14d1cd0b551e53dad48..7c214ac2f7ed190b32396245e1dc8a940d6d1b06 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -1,7 +1,7 @@ <Window x:Class="FlowForge.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 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"> <!-- Define two rows: one fixed height for the button and one for the graph viewer --> <Grid.RowDefinitions> @@ -12,7 +12,7 @@ <!-- Button in the first row --> <Button x:Name="InitializeGraphButton" Content="Initialize Graph" - Width="150" + Width="120" Height="40" VerticalAlignment="Center" HorizontalAlignment="Left" @@ -24,36 +24,51 @@ <!-- Second Button in the first row --> <Button x:Name="RunFordFulkersonButton" Content="Run Ford-Fulkerson" - Width="150" + Width="120" Height="40" VerticalAlignment="Center" HorizontalAlignment="Left" - Margin="350,10,0,10" + Margin="310,10,0,10" Grid.Row="0" Click="FordFulkersonButton_Click"/> <!-- Second Button in the first row --> <Button x:Name="RunEdmondsKarpButton" Content="Run Edmonds–Karp" - Width="150" + Width="120" Height="40" VerticalAlignment="Center" HorizontalAlignment="Left" - Margin="180,10,0,10" + Margin="160,10,0,10" Grid.Row="0" Click="EdmondsKarpButton_Click"/> <!-- Second Button in the first row --> <Button x:Name="Export" Content="Export" - Width="150" + Width="120" Height="40" VerticalAlignment="Center" HorizontalAlignment="Left" - Margin="520,10,0,10" + Margin="460,10,0,10" Grid.Row="0" 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 --> <Grid x:Name="GraphViewerGrid" Grid.Row="1"/> diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index e94b17551f979481f3ff884b9c3adf1f088cfbe1..c0044b1e3f4e67101bf362a2630a9ef6407f73cf 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Windows; @@ -24,14 +23,16 @@ namespace FlowForge private GViewer? _gViewer; private FlowGraph? _flowGraph; 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; public MainWindow() { InitializeComponent(); - + SetupGraphViewer(); } @@ -56,40 +57,34 @@ namespace FlowForge InitializeGraph(); DisplayGraph(); } - + // Event handler for "Run Ford-Fulkerson" button 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; SaveNodePositions(_gViewer?.Graph); // Save positions before running the algorithm 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; } - + //SaveNodePositions(_gViewer.Graph); // Save positions before running the algorithm - + double maxFlow = _fordFulkerson.Run("1", "4", FordFulkersonAlgorithm.SearchStrategy.DepthFirstSearch); - - + + DisplayGraph(); MessageBox.Show($"Maximum flow from source to sink: {maxFlow}"); } - + private void EdmondsKarpButton_Click(object sender, RoutedEventArgs e) { 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; } @@ -100,12 +95,12 @@ namespace FlowForge DisplayGraph(); MessageBox.Show($"Maximum flow from source to sink: {maxFlow}"); } - + private void InitializeGraph() { if (_gViewer != null) _gViewer.NeedToCalculateLayout = true; _nodePositions.Clear(); - + // Initialize a FlowGraph _flowGraph = new FlowGraph(); @@ -134,7 +129,7 @@ namespace FlowForge // Convert QuickGraph to MSAGL Graph _msaglGraph = GraphVisualizer.ConvertToMsaglGraph(_flowGraph); _msaglGraph.Attr.LayerDirection = MsaglDrawing.LayerDirection.TB; - + // Farbe für Knoten 1 und 4 festlegen var node1 = _msaglGraph.FindNode("1"); if (node1 != null) @@ -150,9 +145,9 @@ namespace FlowForge foreach (var node in _msaglGraph.Nodes) { - node.Attr.Shape = MsaglDrawing.Shape.Circle; // Knoten als Kreis + node.Attr.Shape = MsaglDrawing.Shape.Circle; // Knoten als Kreis } - + foreach (var edge in _msaglGraph.Edges) { if (edge.Label != null) @@ -160,35 +155,16 @@ namespace FlowForge edge.Label.FontSize = 4; // Setze die Schriftgröße des Kantenlabels auf 4 } } - + // Assign MSAGL graph to viewer for display if (_gViewer != null) { - - _gViewer.Graph = _msaglGraph; - - // Set the graph and check which layout settings are applied by default _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()) { ApplyNodePositions(_msaglGraph); - + var sugiyamaSettings = new SugiyamaLayoutSettings(); sugiyamaSettings.NodeSeparation = 10; //sugiyamaSettings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.RectilinearToCenter; @@ -197,20 +173,21 @@ namespace FlowForge sugiyamaSettings.RepetitionCoefficientForOrdering = 100; 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 _msaglGraph.LayoutAlgorithmSettings = sugiyamaSettings; - + _msaglGraph.LayoutAlgorithmSettings.EdgeRoutingSettings.RouteMultiEdgesAsBundles = false; - + var router = new SplineRouter( _msaglGraph.GeometryGraph, _msaglGraph.LayoutAlgorithmSettings.EdgeRoutingSettings ); router.Run(); - + foreach (var edge in _msaglGraph.Edges) { if (edge.GeometryEdge != null && edge.Label != null) @@ -219,26 +196,27 @@ namespace FlowForge edge.Label.GeometryLabel.Center = edge.GeometryEdge.Curve.BoundingBox.Center; } }*/ - + //SetStraightLineEdges(_msaglGraph); - + //_gViewer.Invalidate(); _gViewer.NeedToCalculateLayout = false; _gViewer.Graph = _msaglGraph; } } } - + private void SaveNodePositions(MsaglDrawing.Graph? msaglGraph) { _nodePositions.Clear(); + if (msaglGraph == null) return; foreach (var node in msaglGraph.Nodes) { var position = node.GeometryNode.Center; _nodePositions[node.Id] = new Microsoft.Msagl.Core.Geometry.Point(position.X, position.Y); } } - + private void ApplyNodePositions(MsaglDrawing.Graph msaglGraph) { foreach (var node in msaglGraph.Nodes) @@ -249,7 +227,7 @@ namespace FlowForge } } } - + private void SetStraightLineEdges(MsaglDrawing.Graph msaglGraph) { var geometryGraph = msaglGraph.GeometryGraph; @@ -264,7 +242,7 @@ namespace FlowForge edge.Curve = new LineSegment(sourceCenter, targetCenter); edge.LineWidth = 2; } - + foreach (var edge in msaglGraph.Edges) { edge.Attr.ArrowheadAtTarget = MsaglDrawing.ArrowStyle.Normal; // Ensure arrowhead is displayed @@ -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) { if (_flowGraph == null) @@ -399,16 +258,8 @@ namespace FlowForge return; } - var saveFileDialog = new Microsoft.Win32.SaveFileDialog - { - Filter = "TeX files (*.tex)|*.tex", - Title = "Export Graph to LaTeX" - }; - - if (saveFileDialog.ShowDialog() == true) - { - ExportGraphToLatex(saveFileDialog.FileName); - } + TikzCodeGenerator tikzCodeGenerator = new TikzCodeGenerator(_flowGraph, _nodePositions, _fordFulkerson.GraphStates); + tikzCodeGenerator.ExportGraphToLatex(IncludeAnimationCheckbox.IsChecked, StandaloneCheckbox.IsChecked); } } } \ No newline at end of file diff --git a/TikzCodeGenerator.cs b/TikzCodeGenerator.cs new file mode 100644 index 0000000000000000000000000000000000000000..6021d3094209dd6062be4f525ccd8744f3ec8b11 --- /dev/null +++ b/TikzCodeGenerator.cs @@ -0,0 +1,264 @@ +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