一个用于图表可视化和高效力模拟的 Swift 库。
这是一个力导向图,可视化了悲惨世界中人物共同出现的网络。仔细看看动画
源代码:Miserables.swift。
这与第一个示例中的图相同,在 RealityView
中渲染
源代码:ForceDirectedGraph3D/ContentView.swift。
基于您输入的动态图结构,支持点击和拖动手势,全部在 100 行视图代码内完成。
源代码:MermaidVisualization.swift
要通过将 Grape 作为包添加到 Xcode 项目中使用它
https://github.com/swiftgraphs/Grape
要在 SwiftPM 项目中使用 Grape,请将其添加到您的 Package.swift
中
dependencies: [
.package(url: "https://github.com/swiftgraphs/Grape", from: "1.1.0")
]
.product(name: "Grape", package: "Grape"),
注意
Grape
模块依赖于 Observation
框架。可以使用社区提供的 shim(例如 swift-perception
)进行向后部署。
在 1.0 版本发布之前,Grape
模块可能会在小版本更改中引入破坏性的 API 更改。
ForceSimulation
模块现在在公共 API 方面是稳定的。
Grape 包含 2 个模块
Grape
模块允许您在 SwiftUI 视图中创建力导向图。ForceSimulation
模块是 Grape
的底层机制,它可以帮助您创建更复杂或自定义的力模拟。它还包含一个以性能为导向构建的 KDTree
数据结构,该结构可用于空间分割任务。有关详细用法,请参阅文档。这里是一个快速示例
import Grape
struct MyGraph: View {
// States including running status, transformation, etc.
// Gives you a handle to control the states.
@State var graphStates = ForceDirectedGraphState()
var body: some View {
ForceDirectedGraph(states: graphStates) {
// Declare nodes and links like you would do in Swift Charts.
NodeMark(id: 0).foregroundStyle(.green)
NodeMark(id: 1).foregroundStyle(.blue)
NodeMark(id: 2).foregroundStyle(.yellow)
Series(0..<2) { i in
LinkMark(from: i, to: i+1)
}
} force: {
.link()
.center()
.manyBody()
}
}
}
ForceSimulation
模块主要包含 3 个概念,Kinetics
、ForceProtocol
和 Simulation
。
Kinetics
描述了系统的所有运动状态,即位置、速度、链接连接以及描述系统“活跃”程度的变量 alpha
。ForceProtocol
的类型。 此模块提供了您在力导向图中使用的大多数力。 您也可以创建自己的力。 他们应负责 2 项任务bindKinetics(_ kinetics: Kinetics<Vector>)
:绑定到 Kinetics
。 在大多数情况下,力应保留对 Kinetics
的引用,以便他们知道在调用 apply
时要改变什么。apply()
:改变 Kinetics
的状态。 例如,重力应在此函数中添加到每个节点上的速度。Simulation
是一个您与之交互的外壳类,使您能够通过速度 Verlet 积分创建任何维度的模拟。 它管理一个 Kinetics
和一个符合 ForceProtocol
的力。 由于 Simulation
仅存储一种力,因此您有责任将多种力组合成一种。KDTree
用于通过 Barnes-Hut Approximation 加速力模拟。模拟和力的基本概念可以在这里找到:力模拟 - D3。 您可以通过像这样使用 Simulation
简单地创建模拟
import simd
import ForceSimulation
// assuming you’re simulating 4 nodes
let nodeCount = 4
// Connect them
let links = [(0, 1), (1, 2), (2, 3), (3, 0)]
/// Create a 2D force composited with 4 primitive forces.
let myForce = SealedForce2D {
// Forces are namespaced under `Kinetics<Vector>`
// here we only use `Kinetics<SIMD2<Double>>`, i.e. `Kinetics2D`
Kinetics2D.ManyBodyForce(strength: -30)
Kinetics2D.LinkForce(
stiffness: .weightedByDegree(k: { _, _ in 1.0 }),
originalLength: .constant(35)
)
Kinetics2D.CenterForce(center: .zero, strength: 1)
Kinetics2D.CollideForce(radius: .constant(3))
}
/// Create a simulation, the dimension is inferred from the force.
let mySimulation = Simulation(
nodeCount: nodeCount,
links: links.map { EdgeID(source: $0.0, target: $0.1) },
forceField: myForce
)
/// Force is ready to start! run `tick` to iterate the simulation.
for mySimulation in 0..<120 {
mySimulation.tick()
let positions = mySimulation.kinetics.position.asArray()
/// Do something with the positions.
}
有关更多详细信息,请参见示例。
2D simd | ND simd | Metal | |
---|---|---|---|
NdTree | ✅ | ✅ | |
Simulation | ✅ | ✅ | |
LinkForce | ✅ | ✅ | |
ManyBodyForce | ✅ | ✅ | |
CenterForce | ✅ | ✅ | |
CollideForce | ✅ | ✅ | |
PositionForce | ✅ | ✅ | |
RadialForce | ✅ | ✅ | |
SwiftUI View | ✅ | ||
Basic Visualization | ✅ | ||
Gestures | ✅ | ||
Node Styling | ✅ | ||
Link Styling | 🚧 | ||
Animatable Transition | 🚧 |
Grape 使用 simd 来计算位置和速度。目前,对示例图(2D)迭代 120 次需要 ~0.005 秒。(77 个顶点,254 条边,具有多体、中心、碰撞和链接力。在 M1 Max 上发布版本,使用命令 swift test -c release
测试)
对于 3D 模拟,相同的图和相同的配置需要 ~0.008 秒。
重要提示
由于大量使用泛型(某些在调试模式下未专门化),调试版本中的性能比发布版本慢约 100 倍。
根据此测试用例,此软件包中的 BufferedKDTree
比 Apple 的 GameKit 中的 GKQuadtree
快 ~22 倍。 但是,请注意,将 Swift 结构与 NSObject 进行比较是不公平的,并且它们的行为也不同。
该库深受 D3.js(Data-Driven Documents) 的杰出工作的影响。