Skip to content

Commit

Permalink
kruskals
Browse files Browse the repository at this point in the history
  • Loading branch information
MG-LSJ committed Jun 13, 2024
1 parent 33a1093 commit 5ff5bb9
Show file tree
Hide file tree
Showing 20 changed files with 1,153 additions and 318 deletions.
142 changes: 142 additions & 0 deletions lib/algorithms/mst/kruskal.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:visual_graphs/graph_editor/globals.dart';
import 'package:visual_graphs/graph_editor/models/graph.dart';
import 'package:visual_graphs/helpers/data_structures/pair.dart';
import 'package:visual_graphs/helpers/material_color_generator.dart';
import 'package:visual_graphs/helpers/notifiers/set_notifier.dart';

class Kruskal {
int weight = 0;
UnionFind<Vertex> unionFind = UnionFind();
final SetNotifier<Edge> includedEdges = SetNotifier<Edge>();
ValueNotifier<Edge?> currentEdge = ValueNotifier<Edge?>(null);

void start() async {
greyOut();
updateColors();

List<Edge> edges = Globals.game.graph.edges
..sort((a, b) => a.weight.compareTo(b.weight));

for (var edge in edges) {
await Future.delayed(const Duration(milliseconds: 500));
await checkEdge(currentEdge.value = edge);
}
currentEdge.value = null;
}

bool makesCycle(Edge edge) {
return unionFind.find(edge.from).first == unionFind.find(edge.to).first;
}

Future checkEdge(Edge edge) async {
edge.component
..edgeWidth += 2
..setColors(Colors.white, Colors.white);

edge.from.component
..radius = Globals.defaultVertexRadius + 5
..drawBorder = true;

edge.to.component
..radius = Globals.defaultVertexRadius + 5
..drawBorder = true;

await Future.delayed(const Duration(milliseconds: 500));

edge.component.edgeWidth = Globals.defaultEdgeWidth;
edge.from.component
..radius = Globals.defaultVertexRadius
..drawBorder = false;
edge.to.component
..radius = Globals.defaultVertexRadius
..drawBorder = false;

if (!edge.isSelfEdge && !makesCycle(edge)) {
includeEdge(edge);
} else {
edge.component.setColors(Colors.black, Colors.black);
}
updateColors();
}

void includeEdge(Edge edge) {
unionFind.union(edge.from, edge.to);
includedEdges.add(edge);
weight += edge.weight;
}

void updateColors() {
for (var edge in includedEdges.set) {
var color = unionFind.find(edge.from).second ?? Colors.white;
edge.component.setColors(color, color);
edge.from.component.setColors(color, color);
edge.to.component.setColors(color, color);
}
}

void greyOut() {
for (var vertex in Globals.game.graph.vertices) {
vertex.component.setColors(Colors.grey.shade800, Colors.grey.shade800);
}
for (var edge in Globals.game.graph.edges) {
edge.component.setColors(Colors.grey.shade800, Colors.grey.shade800);
}
}

void clear() {
weight = 0;
unionFind.clear();
includedEdges.clear();
}
}

class UnionFind<T> {
final MaterialColorGenerator materialColorGenerator =
MaterialColorGenerator();

Map<T, Pair<T, Color?>> parent = {};

union(T a, T b) {
var aParent = find(a);
var bParent = find(b);

if (aParent.first == bParent.first) {
return;
}

switch ((aParent.second != null, bParent.second != null)) {
case (true, true):
parent[aParent.first] = bParent;
break;
case (true, false):
parent[bParent.first] = aParent;
break;
case (false, true):
parent[aParent.first] = bParent;
break;
case (false, false):
var color = materialColorGenerator.next();
parent[aParent.first] = Pair(bParent.first, color);
parent[bParent.first] = Pair(bParent.first, color);
break;
}
}

Pair<T, Color?> find(T a) {
if (parent[a] == null) {
parent[a] = Pair(a, null);
return parent[a]!;
} else {
if (parent[a]!.first == a) {
return parent[a]!;
} else {
return parent[a] = find(parent[a]!.first);
}
}
}

void clear() {
parent.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:visual_graphs/algorithms/traversal.dart';
import 'package:visual_graphs/algorithms/traversal/traversal.dart';
import 'package:visual_graphs/graph_editor/models/graph.dart';
import 'package:visual_graphs/helpers/data_structures/pair.dart';
import 'package:visual_graphs/helpers/notifiers/queue_notifier.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:visual_graphs/algorithms/traversal.dart';
import 'package:visual_graphs/algorithms/traversal/traversal.dart';
import 'package:visual_graphs/graph_editor/models/graph.dart';
import 'package:visual_graphs/helpers/data_structures/pair.dart';
import 'package:visual_graphs/helpers/notifiers/stack_notifier.dart';
Expand Down
File renamed without changes.
7 changes: 5 additions & 2 deletions lib/graph_editor/components/edge_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class EdgeComponent extends ShapeComponent with HasGameRef<GraphGame> {
final PaintingStyle _paintPaintingStyle = PaintingStyle.stroke;
double _paintStrokeWidth = 2;

double edgeWidth = Globals.defaultEdgeWidth;
double edgeHoverWidth = Globals.defaultEdgeHoverWidth;

EdgeComponent(this.edge) {
_paintColor = color;
anchor = Anchor.topLeft;
Expand Down Expand Up @@ -71,14 +74,14 @@ class EdgeComponent extends ShapeComponent with HasGameRef<GraphGame> {
_paintColor = gameRef.gameMode == GameMode.deleteComponent
? Globals.defaultDeleteColor
: hoverColor;
_paintStrokeWidth = 3;
_paintStrokeWidth = edgeHoverWidth;

labelTextSize = 16;
}

void hoverOut() {
_paintColor = color;
_paintStrokeWidth = 2;
_paintStrokeWidth = edgeWidth;

labelTextSize = 14;
}
Expand Down
212 changes: 212 additions & 0 deletions lib/graph_editor/editor_controls.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import 'package:flutter/material.dart';
import 'package:visual_graphs/graph_editor/globals.dart';
import 'package:visual_graphs/graph_editor/widgets/game_info_box.dart';

class EditorControls extends StatefulWidget {
const EditorControls({super.key});

@override
State<EditorControls> createState() => _EditorControlsState();
}

class _EditorControlsState extends State<EditorControls> {
@override
Widget build(BuildContext context) {
bool smallScreen = MediaQuery.of(context).size.width < 600;

if (Globals.game.gameMode == GameMode.pickVertex) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(width: 40),
const Spacer(),
const Text(
"Pick a vertex",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
const Spacer(),
SizedBox(
width: 40,
child: IconButton.filledTonal(
tooltip: "Cancel",
onPressed: () => Globals.game.restorePreviousGameMode(),
icon: const Icon(Icons.cancel),
style: squareIconButtonStyle,
),
)
],
),
);
}

return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Globals.game.gameMode == GameMode.lockedMode)
IconButton.filled(
onPressed: () => Globals.game.gameMode = GameMode.defaultMode,
icon: const Icon(Icons.edit),
style: squareIconButtonStyle,
),
if (Globals.game.gameMode != GameMode.lockedMode)
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton.filledTonal(
onPressed: () =>
Globals.game.gameMode = GameMode.lockedMode,
icon: const Icon(Icons.done),
style: squareIconButtonStyle,
),
const Spacer(),
ValueListenableBuilder(
valueListenable: Globals.game.undoStack.stackSizeNotifier,
builder: (context, size, child) {
return IconButton.filled(
icon: const Icon(Icons.undo),
onPressed: size == 0 ? null : Globals.game.undo,
tooltip: "Undo",
disabledColor: Colors.grey,
);
},
),
],
),
const SizedBox(height: 10),
SegmentedButton<GameMode>(
segments: [
ButtonSegment(
value: GameMode.defaultMode,
label: !smallScreen ? const Text("Default") : null,
icon: const Icon(Icons.mouse),
tooltip: smallScreen ? "Default Mode" : null,
),
ButtonSegment(
value: GameMode.addVertex,
label: !smallScreen ? const Text("Add Vertex") : null,
icon: const Icon(Icons.add),
tooltip: smallScreen ? "Add Vertex" : null,
),
ButtonSegment(
value: GameMode.addEdge,
label: !smallScreen ? const Text("Add Edge") : null,
icon: const Icon(Icons.linear_scale),
tooltip: smallScreen ? "Add Edge" : null,
),
ButtonSegment(
value: GameMode.deleteComponent,
label: !smallScreen ? const Text("Delete") : null,
icon: const Icon(Icons.delete),
tooltip: smallScreen ? "Delete" : null,
),
],
style: SegmentedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface,
foregroundColor: Theme.of(context).colorScheme.primary,
selectedForegroundColor:
Theme.of(context).colorScheme.onPrimary,
selectedBackgroundColor:
Theme.of(context).colorScheme.primary,
),
showSelectedIcon: false,
selected: {Globals.game.gameMode},
onSelectionChanged: (p0) => Globals.game.gameMode = p0.first,
),
const SizedBox(height: 10),
if (Globals.game.gameMode == GameMode.addEdge)
Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Checkbox.adaptive(
visualDensity: VisualDensity.compact,
value: Globals.game.addEdgeModeIsDirected,
onChanged: (value) => setState(() =>
Globals.game.addEdgeModeIsDirected = value!),
),
const Text("Directed"),
const SizedBox(width: 20),
Checkbox.adaptive(
value: Globals.game.addEdgeModeIsWeighted,
visualDensity: VisualDensity.compact,
onChanged: (value) => setState(() =>
Globals.game.addEdgeModeIsWeighted = value!),
),
const Text("Weighted"),
],
),
),
),
if (Globals.game.gameMode == GameMode.addEdge)
const SizedBox(height: 10),
ElevatedButton.icon(
onPressed: () => Globals.game.clearGraph(),
label: const Text("Clear Graph"),
icon: const Icon(Icons.clear),
),
],
),
const Spacer(),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
onPressed: () => Globals.game.centerCamera(),
icon: const Icon(
Icons.center_focus_strong,
color: Colors.white,
),
tooltip: "Center Camera",
),
const Spacer(),
FloatingActionButton.small(
tooltip: "Help",
onPressed: () {
showDialog(
context: context,
builder: (context) => const GameInfoBox(),
);
},
child: const Icon(Icons.help),
),
],
),
],
),
);
}
}

final squareIconButtonStyle = ButtonStyle(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
Loading

0 comments on commit 5ff5bb9

Please sign in to comment.