Skip to content

Instantly share code, notes, and snippets.

@amadeu01
Created November 11, 2024 16:59
Show Gist options
  • Save amadeu01/c37eda6df2b22bbf81253bd0e462a3a9 to your computer and use it in GitHub Desktop.
Save amadeu01/c37eda6df2b22bbf81253bd0e462a3a9 to your computer and use it in GitHub Desktop.
Animated graph for swiftUI
import SwiftUI
import PlaygroundSupport
struct GraphView: View {
@State private var adjacencyMatrix: [[Int]] = [
[0, 1, 0, 0],
[1, 0, 1, 0],
[0, 1, 0, 1],
[0, 0, 1, 0]
]
var body: some View {
VStack {
GeometryReader { geometry in
ZStack {
let positions = calculateNodePositions(
nodeCount: adjacencyMatrix.count,
in: geometry.size
)
// Draw edges
ForEach(0..<adjacencyMatrix.count, id: \.self) { i in
ForEach(0..<adjacencyMatrix[i].count, id: \.self) { j in
if adjacencyMatrix[i][j] == 1 {
AnimatedLineView(start: positions[i], end: positions[j])
.transition(.opacity)
}
}
}
// Draw nodes
ForEach(0..<positions.count, id: \.self) { index in
Circle()
.fill(Color.red)
.frame(width: 30, height: 30)
.position(positions[index])
.animation(
.easeInOut,
value: positions[index]
)
}
}
.animation(.easeInOut(duration: 1.0), value: adjacencyMatrix)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
// Add Node Button
Button(action: {
addNode()
}) {
Text("Add Random Node")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
.padding()
}
}
func addNode() {
var adjacencyMatrixCopy = adjacencyMatrix
withAnimation {
adjacencyMatrix = []
}
Task {
try? await Task.sleep(for: .seconds(1))
updateAdjacencyMatrix(using: adjacencyMatrixCopy)
}
}
private func updateAdjacencyMatrix(using baseMatrix: [[Int]]) {
withAnimation(Animation.easeOut(duration: 1.0)) {
var adjacencyMatrix = baseMatrix
let newNodeIndex = adjacencyMatrix.count
// Update the adjacency matrix with the new node (initially no edges)
for i in 0..<adjacencyMatrix.count {
adjacencyMatrix[i].append(0)
}
adjacencyMatrix.append(Array(repeating: 0, count: newNodeIndex + 1))
// Add a random connection to an existing node
if newNodeIndex > 0 {
let randomExistingNode = Int.random(in: 0..<newNodeIndex)
adjacencyMatrix[newNodeIndex][randomExistingNode] = 1
adjacencyMatrix[randomExistingNode][newNodeIndex] = 1
}
self.adjacencyMatrix = adjacencyMatrix
}
}
func calculateNodePositions(nodeCount: Int, in size: CGSize) -> [CGPoint] {
let radius = min(size.width, size.height) * 0.4
let centerX = size.width / 2
let centerY = size.height / 2
var positions: [CGPoint] = []
for i in 0..<nodeCount {
let angle = 2 * .pi * CGFloat(i) / CGFloat(nodeCount)
let x = centerX + radius * cos(angle)
let y = centerY + radius * sin(angle)
positions.append(CGPoint(x: x, y: y))
}
return positions
}
}
struct AnimatedLineView: View {
var start: CGPoint
var end: CGPoint
@State private var animationAmount: CGFloat = 0.0
var body: some View {
LineView(start: start, end: end)
.trim(from: 0, to: animationAmount)
.stroke(Color.blue, lineWidth: 2)
.animation(
.easeInOut(duration: 1.0),
value: animationAmount
)
.onAppear {
animationAmount = 1.0
}
}
}
struct LineView: Shape {
var start: CGPoint
var end: CGPoint
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: start)
path.addLine(to: end)
return path
}
}
struct ContentView: View {
var body: some View {
GraphView()
.frame(width: 600, height: 600)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.setLiveView(ContentView())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment