Created
August 4, 2025 14:26
-
-
Save bambuh/51f11583091bc798936c7ebe6532c5ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:async'; | |
| import 'package:flutter/material.dart'; | |
| class GraphView extends StatefulWidget { | |
| const GraphView({super.key}); | |
| @override | |
| State<GraphView> createState() => _GraphViewState(); | |
| } | |
| class _GraphViewState extends State<GraphView> | |
| with SingleTickerProviderStateMixin { | |
| List<Offset> nodes = [ | |
| const Offset(100, 100), | |
| const Offset(200, 150), | |
| const Offset(300, 100), | |
| const Offset(400, 200), | |
| const Offset(150, 300), | |
| ]; | |
| List<Offset> velocities = [ | |
| Offset.zero, | |
| Offset.zero, | |
| Offset.zero, | |
| Offset.zero, | |
| Offset.zero, | |
| ]; | |
| List<List<int>> edges = [ | |
| [0, 1], | |
| [1, 2], | |
| [2, 3], | |
| [3, 4], | |
| [4, 0], | |
| ]; | |
| late List<double> initialEdgeLengths; | |
| Offset? draggingNode; | |
| int? draggingNodeIndex; | |
| late Timer timer; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| initialEdgeLengths = | |
| edges.map((e) => (nodes[e[0]] - nodes[e[1]]).distance).toList(); | |
| timer = Timer.periodic( | |
| const Duration(milliseconds: 16), (timer) => _updateNodes()); | |
| } | |
| @override | |
| void dispose() { | |
| timer.cancel(); | |
| super.dispose(); | |
| } | |
| void _updateNodes() { | |
| setState(() { | |
| const double springConstant = 0.01; | |
| const double damping = 0.85; | |
| for (int i = 0; i < nodes.length; i++) { | |
| if (i == draggingNodeIndex) continue; // Skip the dragged node | |
| Offset force = Offset.zero; | |
| for (int j = 0; j < edges.length; j++) { | |
| List<int> edge = edges[j]; | |
| if (edge[0] == i || edge[1] == i) { | |
| int connectedNodeIndex = (edge[0] == i) ? edge[1] : edge[0]; | |
| Offset displacement = nodes[connectedNodeIndex] - nodes[i]; | |
| double currentLength = displacement.distance; | |
| double initialLength = initialEdgeLengths[j]; | |
| double deltaLength = currentLength - initialLength; | |
| // Apply Hooke's law: F = -k * x | |
| force += | |
| (displacement / currentLength) * springConstant * deltaLength; | |
| } | |
| } | |
| velocities[i] += force; | |
| velocities[i] *= damping; | |
| nodes[i] += velocities[i]; | |
| } | |
| }); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return GestureDetector( | |
| onPanStart: (details) { | |
| for (int i = 0; i < nodes.length; i++) { | |
| if ((nodes[i] - details.localPosition).distance < 20.0) { | |
| draggingNode = nodes[i]; | |
| draggingNodeIndex = i; | |
| break; | |
| } | |
| } | |
| }, | |
| onPanUpdate: (details) { | |
| if (draggingNode != null && draggingNodeIndex != null) { | |
| setState(() { | |
| nodes[draggingNodeIndex!] = details.localPosition; | |
| }); | |
| } | |
| }, | |
| onPanEnd: (details) { | |
| draggingNode = null; | |
| draggingNodeIndex = null; | |
| }, | |
| child: CustomPaint( | |
| key: UniqueKey(), | |
| painter: GraphPainter(nodes: nodes, edges: edges), | |
| size: const Size(double.infinity, double.infinity), | |
| ), | |
| ); | |
| } | |
| } | |
| class GraphPainter extends CustomPainter { | |
| final List<Offset> nodes; | |
| final List<List<int>> edges; | |
| GraphPainter({required this.nodes, required this.edges}); | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final Paint edgePaint = Paint() | |
| ..color = Colors.grey | |
| ..strokeWidth = 2.0; | |
| final Paint nodePaint = Paint() | |
| ..color = Colors.blue | |
| ..style = PaintingStyle.fill; | |
| final Paint nodeStrokePaint = Paint() | |
| ..color = Colors.black | |
| ..strokeWidth = 2.0 | |
| ..style = PaintingStyle.stroke; | |
| // Draw edges | |
| for (List<int> edge in edges) { | |
| final Offset p1 = nodes[edge[0]]; | |
| final Offset p2 = nodes[edge[1]]; | |
| canvas.drawLine(p1, p2, edgePaint); | |
| } | |
| // Draw nodes | |
| for (Offset node in nodes) { | |
| canvas.drawCircle(node, 10.0, nodePaint); | |
| canvas.drawCircle(node, 10.0, nodeStrokePaint); | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(covariant GraphPainter oldDelegate) { | |
| return true; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment